commit 26677972a638034d07ac7ff3a117fb03fe579dbf Author: 陈子默 <925456043@qq.com> Date: Sun Feb 22 18:56:10 2026 +0800 初始化 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7ef1f56 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# Git +.git +.gitignore + +# Maven +target/ +*.jar +*.war +.mvn/ +mvnw +mvnw.cmd + +# Node.js +**/node_modules +**/dist +**/pnpm-lock.yaml +.npmrc + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Docker +docker-compose.yml +Dockerfile +.dockerignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e4edcd --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.flattened-pom.xml +application-me.yml + +### IntelliJ IDEA ### +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/.logs/ +/.idea/ +.logs +.idea + +.jlsp/ +.arts/ +luceneKnowledge + +# v1 +/easyflow-ui-react \ No newline at end of file diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..b901097 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.workflow/branch-pipeline.yml b/.workflow/branch-pipeline.yml new file mode 100644 index 0000000..eb26722 --- /dev/null +++ b/.workflow/branch-pipeline.yml @@ -0,0 +1,53 @@ +version: '1.0' +name: branch-pipeline +displayName: BranchPipeline +stages: + - stage: + name: compile + displayName: 编译 + steps: + - step: build@maven + name: build_maven + displayName: Maven 构建 + # 支持6、7、8、9、10、11六个版本 + jdkVersion: 8 + # 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本 + mavenVersion: 3.3.9 + # 构建命令 + commands: + - mvn -B clean package -Dmaven.test.skip=true + # 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除 + artifacts: + # 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址 + - name: BUILD_ARTIFACT + # 构建产物获取路径,是指代码编译完毕之后构建物的所在路径,如通常jar包在target目录下。当前目录为代码库根目录 + path: + - ./easyflow-starter/target + - step: publish@general_artifacts + name: publish_general_artifacts + displayName: 上传制品 + # 上游构建任务定义的产物名,默认BUILD_ARTIFACT + dependArtifact: BUILD_ARTIFACT + # 上传到制品库时的制品命名,默认output + artifactName: output + dependsOn: build_maven + - stage: + name: release + displayName: 发布 + steps: + - step: publish@release_artifacts + name: publish_release_artifacts + displayName: '发布' + # 上游上传制品任务的产出 + dependArtifact: output + # 发布制品版本号 + version: '1.0.0' + # 是否开启版本号自增,默认开启 + autoIncrement: true +triggers: + push: + branches: + exclude: + - master + include: + - .* diff --git a/.workflow/master-pipeline.yml b/.workflow/master-pipeline.yml new file mode 100644 index 0000000..720e2ac --- /dev/null +++ b/.workflow/master-pipeline.yml @@ -0,0 +1,51 @@ +version: '1.0' +name: master-pipeline +displayName: MasterPipeline +stages: + - stage: + name: compile + displayName: 编译 + steps: + - step: build@maven + name: build_maven + displayName: Maven 构建 + # 支持6、7、8、9、10、11六个版本 + jdkVersion: 8 + # 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本 + mavenVersion: 3.3.9 + # 构建命令 + commands: + - mvn -B clean package -Dmaven.test.skip=true + # 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除 + artifacts: + # 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址 + - name: BUILD_ARTIFACT + # 构建产物获取路径,是指代码编译完毕之后构建物的所在路径,如通常jar包在target目录下。当前目录为代码库根目录 + path: + - ./easyflow-starter/target + - step: publish@general_artifacts + name: publish_general_artifacts + displayName: 上传制品 + # 上游构建任务定义的产物名,默认BUILD_ARTIFACT + dependArtifact: BUILD_ARTIFACT + # 上传到制品库时的制品命名,默认output + artifactName: output + dependsOn: build_maven + - stage: + name: release + displayName: 发布 + steps: + - step: publish@release_artifacts + name: publish_release_artifacts + displayName: '发布' + # 上游上传制品任务的产出 + dependArtifact: output + # 发布制品版本号 + version: '1.0.0.0' + # 是否开启版本号自增,默认开启 + autoIncrement: true +triggers: + push: + branches: + include: + - master diff --git a/.workflow/pipeline-sbom.yml b/.workflow/pipeline-sbom.yml new file mode 100644 index 0000000..e6f8c25 --- /dev/null +++ b/.workflow/pipeline-sbom.yml @@ -0,0 +1,24 @@ +version: '1.0' +name: pipeline-sbom +displayName: pipeline-sbom +triggers: + trigger: manual + push: + branches: + prefix: + - '' +stages: + - name: stage-4d6a9b8b + displayName: 未命名 + strategy: naturally + trigger: auto + executor: [] + steps: + - step: sc@sbom + name: sbom + displayName: SBOM 扫描 + scan: code + codePath: ./ + notify: [] + strategy: + retry: '0' diff --git a/.workflow/pr-pipeline.yml b/.workflow/pr-pipeline.yml new file mode 100644 index 0000000..8a04209 --- /dev/null +++ b/.workflow/pr-pipeline.yml @@ -0,0 +1,40 @@ +version: '1.0' +name: pr-pipeline +displayName: PRPipeline +stages: + - stage: + name: compile + displayName: 编译 + steps: + - step: build@maven + name: build_maven + displayName: Maven 构建 + # 支持6、7、8、9、10、11六个版本 + jdkVersion: 8 + # 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本 + mavenVersion: 3.3.9 + # 构建命令 + commands: + - mvn -B clean package -Dmaven.test.skip=true + # 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除 + artifacts: + # 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址 + - name: BUILD_ARTIFACT + # 构建产物获取路径,是指代码编译完毕之后构建物的所在路径,如通常jar包在target目录下。当前目录为代码库根目录 + path: + - ./easyflow-starter/target + - step: publish@general_artifacts + name: publish_general_artifacts + displayName: 上传制品 + # 上游构建任务定义的产物名,默认BUILD_ARTIFACT + dependArtifact: BUILD_ARTIFACT + # 构建产物制品库,默认default,系统默认创建 + artifactRepository: default + # 上传到制品库时的制品命名,默认output + artifactName: output + dependsOn: build_maven +triggers: + pr: + branches: + include: + - master diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..744d098 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# 第一阶段:构建阶段 +FROM maven:3.9-eclipse-temurin-17 AS builder + +WORKDIR /build + +# 复制 pom.xml 并下载依赖(利用 Docker 缓存) +COPY pom.xml . +COPY easyflow-api/pom.xml easyflow-api/ +COPY easyflow-commons/pom.xml easyflow-commons/ +COPY easyflow-modules/pom.xml easyflow-modules/ +COPY easyflow-starter/pom.xml easyflow-starter/ +COPY easyflow-starter/easyflow-starter-all/pom.xml easyflow-starter/easyflow-starter-all/ + +# 注意:这里需要复制所有模块的 pom.xml 才能正确解析依赖 +# 如果模块很多,可能需要更精细的复制,但这里先简单处理 +COPY . . + +# 执行构建 +RUN mvn clean package -DskipTests + +# 第二阶段:运行阶段 +FROM eclipse-temurin:17-jre + +LABEL maintainer="Cennac " + +ARG VERSION=2.0.4 +ARG SERVICE_NAME=easyflow-starter-all +ARG SERVICE_PORT=8080 + +ENV VERSION ${VERSION} +ENV SERVICE_NAME ${SERVICE_NAME} +ENV SERVICE_PORT ${SERVICE_PORT} +ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 +ENV JAVA_OPTS="" +ENV TZ=Asia/Shanghai + +WORKDIR /app + +# 安装必要的字体和工具 +RUN apt-get update && \ + apt-get install -y --no-install-recommends fonts-dejavu-core fontconfig && \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ + rm -rf /var/lib/apt/lists/* + +# 从构建阶段复制 jar 包 +COPY --from=builder /build/easyflow-starter/easyflow-starter-all/target/${SERVICE_NAME}-*.jar app.jar + +VOLUME /tmp +EXPOSE ${SERVICE_PORT} + +ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar ./app.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f7a7cd --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# EasyFlow + +EasyFlow 是一个面向企业场景的 Java AI 应用开发平台,提供智能体、工作流编排、知识库(RAG)、插件调用、MCP 接入和系统管理能力。 +当前后端已基于 `easy-agents` 与 `easy-agents-flow` 生态进行整合。 + +## 核心模块 + +### 后端聚合模块 +- `easyflow-api`:对外 API 层,包含 admin/public/usercenter/mcp 接口。 +- `easyflow-commons`:公共能力层(基础工具、AI 公共组件、缓存、文件存储、验证码、鉴权等)。 +- `easyflow-modules`:业务模块层(AI、系统、日志、任务、数据中心、认证等)。 +- `easyflow-starter`:启动层,`easyflow-starter-all` 为完整可运行服务。 + +### 前端项目 +- `easyflow-ui-admin`:管理后台前端。 +- `easyflow-ui-usercenter`:用户中心前端。 +- `easyflow-ui-websdk`:Web SDK 示例/开发工程。 + +## 环境要求 + +- JDK 17+ +- Maven 3.8+ +- MySQL 8+ +- Redis 6+ +- Node.js >= 20.10.0 +- pnpm >= 9.12.0 + +## 快速启动(开发环境) + +### 1. 初始化数据库 +在 MySQL 中导入: +- `sql/01-easyflow-v2.ddl.sql` +- `sql/02-easyflow-v2.data.sql` + +### 2. 启动后端 +在项目根目录执行: + +```bash +mvn -DskipTests clean package +java -jar easyflow-starter/easyflow-starter-all/target/easyflow-starter-all-0.0.1.jar --spring.profiles.active=dev +``` + +默认端口:`8111`(见 `easyflow-starter/easyflow-starter-all/src/main/resources/application.yml`)。 + +### 3. 启动前端 +管理后台: + +```bash +cd easyflow-ui-admin +pnpm install +pnpm dev +``` + +用户中心: + +```bash +cd easyflow-ui-usercenter +pnpm install +pnpm dev +``` + +默认测试账号:`admin / 123456` + +## 后端 Jar 包构建与部署 + +### 构建 Jar + +```bash +mvn -DskipTests -Dmaven.javadoc.skip=true clean package +``` + +产物路径: +- `easyflow-starter/easyflow-starter-all/target/easyflow-starter-all-0.0.1.jar` + +### 启动 Jar(生产建议) + +```bash +java -jar easyflow-starter/easyflow-starter-all/target/easyflow-starter-all-0.0.1.jar --spring.profiles.active=prod +``` + +可通过环境变量覆盖关键配置(示例): + +```bash +SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/easyflow?useInformationSchema=true&characterEncoding=utf-8 +SPRING_DATASOURCE_USERNAME=easyflow +SPRING_DATASOURCE_PASSWORD=123456 +SPRING_REDIS_HOST=127.0.0.1 +SPRING_REDIS_PASSWORD=your_redis_password +``` + +## 前端 dist 包构建与部署 + +### 构建 dist +管理后台: + +```bash +cd easyflow-ui-admin +pnpm install +pnpm run build +``` + +用户中心: + +```bash +cd easyflow-ui-usercenter +pnpm install +pnpm run build +``` + +构建产物: +- `easyflow-ui-admin/app/dist` +- `easyflow-ui-usercenter/app/dist` + +### 部署方式(Nginx) +将 dist 目录部署到 Nginx 静态目录,并配置反向代理: +- `/api/` -> 后端服务 +- `/userCenter/` -> 后端服务 + +可直接参考: +- `easyflow-ui-admin/scripts/deploy/nginx.conf` +- `easyflow-ui-usercenter/scripts/deploy/nginx.conf` + +## Docker Compose 一键部署(可选) + +在项目根目录执行: + +```bash +docker compose up --build -d +``` + +默认端口: +- 后端 API:`8080` +- Admin:`8081` +- UserCenter:`8082` + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3179c94 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +services: + # 后端服务 + easyflow-api: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" + environment: + - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/easyflow?useInformationSchema=true&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai + - SPRING_DATASOURCE_USERNAME=root + - SPRING_DATASOURCE_PASSWORD=123456 + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD:-easyflow_redis_2026} + - SPRING_WEB_RESOURCES_STATIC_LOCATIONS=file:/www/easyflow/attachment/ + - EASYFLOW_STORAGE_LOCAL_ROOT=/www/easyflow/attachment + networks: + - easyflow-net + volumes: + - ./attachment:/www/easyflow/attachment + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_started + + # 管理后台前端 + easyflow-ui-admin: + build: + context: ./easyflow-ui-admin + dockerfile: scripts/deploy/Dockerfile + ports: + - "8081:8080" + networks: + - easyflow-net + depends_on: + - easyflow-api + + # 用户中心前端 + easyflow-ui-usercenter: + build: + context: ./easyflow-ui-usercenter + dockerfile: scripts/deploy/Dockerfile + ports: + - "8082:8080" + networks: + - easyflow-net + depends_on: + - easyflow-api + + # 数据库 + mysql: + image: mysql:8.0 + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --lower-case-table-names=1 + environment: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: easyflow + ports: + - "3306:3306" + volumes: + - ./sql:/docker-entrypoint-initdb.d + # 这个命令在默认 entrypoint 运行前修复权限 + entrypoint: sh -c "chown -R mysql:mysql /docker-entrypoint-initdb.d && docker-entrypoint.sh mysqld" + networks: + - easyflow-net + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-u", "root", "-p$MYSQL_ROOT_PASSWORD", "-h", "localhost"] + timeout: 5s + retries: 5 + + # Redis (后端可能需要) + redis: + image: redis:alpine + command: redis-server --requirepass ${REDIS_PASSWORD:-easyflow_redis_2026} + networks: + - easyflow-net + +networks: + easyflow-net: + driver: bridge diff --git a/easyflow-api/easyflow-api-admin/pom.xml b/easyflow-api/easyflow-api-admin/pom.xml new file mode 100644 index 0000000..c4d8911 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + tech.easyflow + easyflow-api + ${revision} + + + easyflow-api-admin + + + + tech.easyflow + easyflow-module-ai + + + tech.easyflow + easyflow-module-auth + + + tech.easyflow + easyflow-module-job + + + tech.easyflow + easyflow-common-captcha + + + \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java new file mode 100644 index 0000000..f9f0855 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.ai; + +import tech.easyflow.ai.entity.BotCategory; +import tech.easyflow.ai.service.BotCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * bot分类 控制层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@RestController +@RequestMapping("/api/v1/botCategory") +@UsePermission(moduleName = "/api/v1/bot") +public class BotCategoryController extends BaseCurdController { + public BotCategoryController(BotCategoryService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java new file mode 100644 index 0000000..f2386c0 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java @@ -0,0 +1,372 @@ + +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaIgnore; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.chat.ChatOptions; +import com.alicp.jetcache.Cache; +import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener; +import tech.easyflow.ai.entity.*; +import tech.easyflow.ai.service.*; +import tech.easyflow.ai.service.impl.BotServiceImpl; +import tech.easyflow.common.audio.core.AudioServiceManager; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; +import tech.easyflow.core.chat.protocol.sse.ChatSseUtil; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/bot") +public class BotController extends BaseCurdController { + + private final ModelService modelService; + private final BotWorkflowService botWorkflowService; + private final BotDocumentCollectionService botDocumentCollectionService; + private final BotMessageService botMessageService; + @Resource + private BotService botService; + @Autowired + @Qualifier("defaultCache") // 指定 Bean 名称 + private Cache cache; + @Resource + private AudioServiceManager audioServiceManager; + + public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService, + BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) { + super(service); + this.modelService = modelService; + this.botWorkflowService = botWorkflowService; + this.botDocumentCollectionService = botDocumentCollectionService; + this.botMessageService = botMessageService; + } + + @Resource + private BotPluginService botPluginService; + + @GetMapping("/generateConversationId") + public Result generateConversationId() { + long nextId = new SnowFlakeIDKeyGenerator().nextId(); + return Result.ok(nextId); + } + + @PostMapping("updateOptions") + @SaCheckPermission("/api/v1/bot/save") + public Result updateOptions(@JsonBody("id") BigInteger id, + @JsonBody("options") Map options) { + Bot aiBot = service.getById(id); + Map existOptions = aiBot.getOptions(); + if (existOptions == null) { + existOptions = new HashMap<>(); + } + if (options != null) { + existOptions.putAll(options); + } + aiBot.setOptions(existOptions); + service.updateById(aiBot); + return Result.ok(); + } + + @PostMapping("updateLlmOptions") + @SaCheckPermission("/api/v1/bot/save") + public Result updateLlmOptions(@JsonBody("id") + BigInteger id, @JsonBody("llmOptions") + Map llmOptions) { + Bot aiBot = service.getById(id); + Map existLlmOptions = aiBot.getModelOptions(); + if (existLlmOptions == null) { + existLlmOptions = new HashMap<>(); + } + if (llmOptions != null) { + existLlmOptions.putAll(llmOptions); + } + aiBot.setModelOptions(existLlmOptions); + service.updateById(aiBot); + return Result.ok(); + } + + @PostMapping("voiceInput") + @SaIgnore + public Result voiceInput(@RequestParam("audio") + MultipartFile audioFile) { + + String recognize = null; + try { + recognize = audioServiceManager.audioToText(audioFile.getInputStream()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return Result.ok("", recognize); + } + + /** + * 处理聊天请求的接口方法 + * + * @param prompt 用户输入的聊天内容,必须提供 + * @param botId 聊天机器人的唯一标识符,必须提供 + * @param conversationId 会话ID,用于标识当前对话会话,必须提供 + * @param messages 历史消息,用于提供上下文,可选 + * @return 返回SseEmitter对象,用于服务器向客户端推送聊天响应数据 + */ + @PostMapping("chat") + @SaIgnore + public SseEmitter chat( + @JsonBody(value = "prompt", required = true) String prompt, + @JsonBody(value = "botId", required = true) BigInteger botId, + @JsonBody(value = "conversationId", required = true) BigInteger conversationId, + @JsonBody(value = "messages") List> messages, + @JsonBody(value = "attachments") List attachments + + ) { + BotServiceImpl.ChatCheckResult chatCheckResult = new BotServiceImpl.ChatCheckResult(); + + // 前置校验:失败则直接返回错误SseEmitter + SseEmitter errorEmitter = botService.checkChatBeforeStart(botId, prompt, conversationId.toString(), chatCheckResult); + if (errorEmitter != null) { + return errorEmitter; + } + return botService.startChat(botId, prompt, conversationId, messages, chatCheckResult, attachments); + } + + @PostMapping("updateLlmId") + @SaCheckPermission("/api/v1/bot/save") + public Result updateBotLlmId(@RequestBody + Bot aiBot) { + service.updateBotLlmId(aiBot); + return Result.ok(); + } + + @GetMapping("getDetail") + @SaIgnore + public Result getDetail(String id) { + return Result.ok(botService.getDetail(id)); + } + + @Override + @SaIgnore + public Result detail(String id) { + Bot data = botService.getDetail(id); + if (data == null) { + return Result.ok(data); + } + + Map llmOptions = data.getModelOptions(); + if (llmOptions == null) { + llmOptions = new HashMap<>(); + } + + if (data.getModelId() == null) { + return Result.ok(data); + } + + BigInteger llmId = data.getModelId(); + Model llm = modelService.getById(llmId); + + if (llm == null) { + data.setModelId(null); + return Result.ok(data); + } + + Map options = llm.getOptions(); + + if (options != null && !options.isEmpty()) { + + // 获取是否多模态 + Boolean multimodal = (Boolean) options.get("multimodal"); + llmOptions.put("multimodal", multimodal != null && multimodal); + + } + + return Result.ok(data); + } + + @Override + protected Result onSaveOrUpdateBefore(Bot entity, boolean isSave) { + + String alias = entity.getAlias(); + + if (StringUtils.hasLength(alias)) { + Bot aiBot = service.getByAlias(alias); + + + if (aiBot != null && isSave) { + throw new BusinessException("别名已存在!"); + } + + if (aiBot != null && aiBot.getId().compareTo(entity.getId()) != 0) { + throw new BusinessException("别名已存在!"); + } + + } + + + if (isSave) { + // 设置默认值 + entity.setModelOptions(getDefaultLlmOptions()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + private ChatOptions getChatOptions(Map llmOptions) { + ChatOptions defaultOptions = new ChatOptions(); + if (llmOptions != null) { + Object topK = llmOptions.get("topK"); + Object maxReplyLength = llmOptions.get("maxReplyLength"); + Object temperature = llmOptions.get("temperature"); + Object topP = llmOptions.get("topP"); + Object thinkingEnabled = llmOptions.get("thinkingEnabled"); + + if (topK != null) { + defaultOptions.setTopK(Integer.parseInt(String.valueOf(topK))); + } + if (maxReplyLength != null) { + defaultOptions.setMaxTokens(Integer.parseInt(String.valueOf(maxReplyLength))); + } + if (temperature != null) { + defaultOptions.setTemperature(Float.parseFloat(String.valueOf(temperature))); + } + if (topP != null) { + defaultOptions.setTopP(Float.parseFloat(String.valueOf(topP))); + } + if (thinkingEnabled != null) { + defaultOptions.setThinkingEnabled(Boolean.parseBoolean(String.valueOf(thinkingEnabled))); + } + + } + return defaultOptions; + } + + private Map getDefaultLlmOptions() { + Map defaultLlmOptions = new HashMap<>(); + defaultLlmOptions.put("temperature", 0.7); + defaultLlmOptions.put("topK", 4); + defaultLlmOptions.put("maxReplyLength", 2048); + defaultLlmOptions.put("topP", 0.7); + defaultLlmOptions.put("maxMessageCount", 10); + return defaultLlmOptions; + } + + private Map errorRespnseMsg(int errorCode, String message) { + HashMap result = new HashMap<>(); + result.put("error", errorCode); + result.put("message", message); + return result; + } + + @Override + protected Result onRemoveBefore(Collection ids) { + QueryWrapper queryWrapperKnowledge = QueryWrapper.create().in(BotDocumentCollection::getBotId, ids); + botDocumentCollectionService.remove(queryWrapperKnowledge); + QueryWrapper queryWrapperBotWorkflow = QueryWrapper.create().in(BotWorkflow::getBotId, ids); + botWorkflowService.remove(queryWrapperBotWorkflow); + QueryWrapper queryWrapperBotPlugins = QueryWrapper.create().in(BotPlugin::getBotId, ids); + botPluginService.remove(queryWrapperBotPlugins); + return super.onRemoveBefore(ids); + } + + /** + * 系统提示词优化 + * + * @param prompt + * @return + */ + @PostMapping("prompt/chore/chat") + @SaIgnore + public SseEmitter chat( + @JsonBody(value = "prompt", required = true) String prompt, + @JsonBody(value = "botId", required = true) BigInteger botId + ){ + if (!StringUtils.hasLength(prompt)) { + throw new BusinessException("提示词不能为空!"); + } + String promptChore = "# 角色与目标\n" + + "\n" + + "你是一位专业的提示词工程师(Prompt Engineer)。你的任务是,分析我提供的“用户原始提示词”,并将其优化成一个结构清晰、指令明确、效果最优的“系统提示词(System Prompt)”。\n" + + "\n" + + "这个优化后的系统提示词将直接用于引导一个AI助手,使其能够精准、高效地完成用户的请求。\n" + + "\n" + + "# 优化指南 (请严格遵循)\n" + + "\n" + + "在优化过程中,请遵循以下原则,以确保最终提示词的质量:\n" + + "\n" + + "1. **角色定义 (Role Definition)**:\n" + + " * 为AI助手明确一个具体、专业的角色。这个角色应该与任务高度相关。\n" + + " * 例如:“你是一位资深的软件架构师”、“你是一位经验丰富的产品经理”。\n" + + "\n" + + "2. **任务与目标 (Task & Goal)**:\n" + + " * 清晰、具体地描述AI需要完成的任务。\n" + + " * 明确指出期望的最终输出是什么,以及输出的目标和用途。\n" + + " * 避免模糊不清的指令。\n" + + "\n" + + "3. **输入输出格式 (Input/Output Format)**:\n" + + " * 如果任务涉及到处理特定格式的数据,请明确说明输入数据的格式。\n" + + " * **至关重要**:请为AI的输出指定一个清晰、结构化的格式。这能极大地提升结果的可用性。\n" + + " * 例如:“请以Markdown表格的形式输出”、“请分点列出,每点不超过20字”。\n" + + "\n" + + "4. **背景与上下文 (Context & Background)**:\n" + + " * 提供完成任务所必需的背景信息。\n" + + " * 例如:项目的阶段、目标用户、使用的技术栈、相关的约束条件等。\n" + + "\n" + + "5. **语气与风格 (Tone & Style)**:\n" + + " * 指定AI回答时应采用的语气和风格。\n" + + " * 例如:“专业且简洁”、“通俗易懂,避免使用专业术语”。\n" + + "\n" + + "6. **约束与规则 (Constraints & Rules)**:\n" + + " * 设定AI在回答时必须遵守的规则和限制。\n" + + " * 例如:“回答必须基于提供的文档”、“禁止猜测用户未提及的信息”。\n" + + "\n" + + "7. **思考过程 (Chain-of-Thought)**:\n" + + " * 对于复杂的推理任务,可以引导AI展示其思考过程。\n" + + " * 例如:“请先分析问题的关键点,然后给出解决方案,并解释你的推理步骤。”\n" + + "\n" + + "# 输出要求\n" + + "\n" + + "请你直接输出优化后的完整系统提示词。不要包含任何额外的解释或说明。\n" + + "\n" + + "---\n" + + "\n" + + "## 用户原始提示词\n" + + "[" + prompt + "]\n"; + Bot aiBot = service.getById(botId); + if (aiBot == null) { + return ChatSseUtil.sendSystemError(null, "聊天助手不存在"); + } + ChatSseEmitter sseEmitter = new ChatSseEmitter(); + Model model = modelService.getModelInstance(aiBot.getModelId()); + if (model == null) { + return ChatSseUtil.sendSystemError(null, "模型不存在"); + } + ChatModel chatModel = model.toChatModel(); + if (chatModel == null) { + return ChatSseUtil.sendSystemError(null, "模型不存在"); + } + PromptChoreChatStreamListener promptChoreChatStreamListener = new PromptChoreChatStreamListener(sseEmitter); + chatModel.chatStream(promptChore, promptChoreChatStreamListener); + return sseEmitter.getEmitter(); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotDocumentCollectionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotDocumentCollectionController.java new file mode 100644 index 0000000..2fecb48 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotDocumentCollectionController.java @@ -0,0 +1,46 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.ai.entity.BotDocumentCollection; +import tech.easyflow.ai.service.BotDocumentCollectionService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import com.mybatisflex.core.query.QueryWrapper; +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.web.jsonbody.JsonBody; + +import java.math.BigInteger; +import java.util.List; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-28 + */ +@RestController +@RequestMapping("/api/v1/botKnowledge") +@UsePermission(moduleName = "/api/v1/bot") +public class BotDocumentCollectionController extends BaseCurdController { + public BotDocumentCollectionController(BotDocumentCollectionService service) { + super(service); + } + + @GetMapping("list") + @Override + public Result> list(BotDocumentCollection entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List botDocumentCollections = service.getMapper().selectListWithRelationsByQuery(queryWrapper); + return Result.ok(botDocumentCollections); + } + + @PostMapping("updateBotKnowledgeIds") + public Result save(@JsonBody("botId") BigInteger botId, @JsonBody("knowledgeIds") BigInteger [] knowledgeIds) { + service.saveBotAndKnowledge(botId, knowledgeIds); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMcpController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMcpController.java new file mode 100644 index 0000000..e3380e4 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMcpController.java @@ -0,0 +1,40 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.ai.entity.BotMcp; +import tech.easyflow.ai.service.BotMcpService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2026-01-05 + */ +@RestController +@RequestMapping("/api/v1/botMcp") +@UsePermission(moduleName = "/api/v1/bot") +public class BotMcpController extends BaseCurdController { + public BotMcpController(BotMcpService service) { + super(service); + } + + + @PostMapping("updateBotMcpToolIds") + public Result save(@JsonBody("botId") BigInteger botId, + @JsonBody("mcpSelectedData") List>>> mcpSelectedData) { + service.updateBotMcpToolIds(botId, mcpSelectedData); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMessageController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMessageController.java new file mode 100644 index 0000000..d8873d4 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotMessageController.java @@ -0,0 +1,35 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.ai.service.BotMessageService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +/** + * Bot 消息记录表 控制层。 + * + * @author michael + * @since 2024-11-04 + */ +@RestController +@RequestMapping("/api/v1/botMessage") +@UsePermission(moduleName = "/api/v1/bot") +public class BotMessageController extends BaseCurdController { + + private final BotMessageService botMessageService; + + public BotMessageController(BotMessageService service, BotMessageService botMessageService) { + super(service); + this.botMessageService = botMessageService; + } + + @Override + protected Result onSaveOrUpdateBefore(BotMessage entity, boolean isSave) { + entity.setAccountId(SaTokenUtil.getLoginAccount().getId()); + return super.onSaveOrUpdateBefore(entity, isSave); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotModelController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotModelController.java new file mode 100644 index 0000000..98362d3 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotModelController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.ai; + +import tech.easyflow.ai.entity.BotModel; +import tech.easyflow.ai.service.BotModelService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-28 + */ +@RestController +@RequestMapping("/api/v1/botLlm") +@UsePermission(moduleName = "/api/v1/bot") +public class BotModelController extends BaseCurdController { + public BotModelController(BotModelService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java new file mode 100644 index 0000000..a957a37 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java @@ -0,0 +1,74 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.tree.Tree; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.ai.service.BotPluginService; +import com.mybatisflex.core.query.QueryWrapper; +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.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.List; + +/** + * 控制层。 + * + * @author michael + * @since 2025-04-07 + */ +@RestController +@RequestMapping("/api/v1/botPlugins") +@UsePermission(moduleName = "/api/v1/bot") +public class BotPluginController extends BaseCurdController { + + public BotPluginController(BotPluginService service) { + super(service); + } + + @Resource + private BotPluginService botPluginService; + + @GetMapping("list") + public Result> list(BotPlugin entity, Boolean asTree, String sortKey, String sortType){ + + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + + List botPlugins = service.getMapper().selectListWithRelationsByQuery(queryWrapper); + + List list = Tree.tryToTree(botPlugins, asTree); + + return Result.ok(list); + } + + @PostMapping("/getList") + public Result> getList(@JsonBody(value = "botId", required = true) String botId){ + return Result.ok(botPluginService.getList(botId)); + } + + @PostMapping("/getBotPluginToolIds") + public Result> getBotPluginToolIds(@JsonBody(value = "botId", required = true) String botId){ + return Result.ok(botPluginService.getBotPluginToolIds(botId)); + } + + @PostMapping("/doRemove") + public Result doRemove(@JsonBody(value = "botId", required = true) String botId, + @JsonBody(value = "pluginToolId", required = true) String pluginToolId){ + return Result.ok(botPluginService.doRemove(botId, pluginToolId)); + } + + @PostMapping("updateBotPluginToolIds") + public Result save(@JsonBody("botId") BigInteger botId, @JsonBody("pluginToolIds") BigInteger [] pluginToolIds) { + service.saveBotAndPluginTool(botId, pluginToolIds); + return Result.ok(); + } + +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotRecentlyUsedController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotRecentlyUsedController.java new file mode 100644 index 0000000..8b32f35 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotRecentlyUsedController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.ai; + +import tech.easyflow.ai.entity.BotRecentlyUsed; +import tech.easyflow.ai.service.BotRecentlyUsedService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 最近使用 控制层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@RestController +@RequestMapping("/api/v1/botRecentlyUsed") +@UsePermission(moduleName = "/api/v1/bot") +public class BotRecentlyUsedController extends BaseCurdController { + public BotRecentlyUsedController(BotRecentlyUsedService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotWorkflowController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotWorkflowController.java new file mode 100644 index 0000000..4cef75e --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotWorkflowController.java @@ -0,0 +1,48 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.ai.entity.BotWorkflow; +import tech.easyflow.ai.service.BotWorkflowService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.tree.Tree; +import tech.easyflow.common.web.controller.BaseCurdController; +import com.mybatisflex.core.query.QueryWrapper; +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.web.jsonbody.JsonBody; + +import java.math.BigInteger; +import java.util.List; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-28 + */ +@RestController +@RequestMapping("/api/v1/botWorkflow") +@UsePermission(moduleName = "/api/v1/bot") +public class BotWorkflowController extends BaseCurdController { + public BotWorkflowController(BotWorkflowService service) { + super(service); + } + + @GetMapping("list") + @Override + public Result> list(BotWorkflow entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List botWorkflows = service.getMapper().selectListWithRelationsByQuery(queryWrapper); + List list = Tree.tryToTree(botWorkflows, asTree); + return Result.ok(list); + } + + @PostMapping("updateBotWorkflowIds") + public Result save(@JsonBody("botId") BigInteger botId, @JsonBody("workflowIds") BigInteger [] workflowIds) { + service.saveBotAndWorkflowTool(botId, workflowIds); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentChunkController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentChunkController.java new file mode 100644 index 0000000..92d1abf --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentChunkController.java @@ -0,0 +1,118 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.easyagents.core.model.embedding.EmbeddingModel; +import tech.easyflow.ai.entity.DocumentChunk; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.service.DocumentChunkService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; +import com.easyagents.core.document.Document; +import com.easyagents.core.store.DocumentStore; +import com.easyagents.core.store.StoreOptions; +import com.easyagents.core.store.StoreResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/documentChunk") +@UsePermission(moduleName = "/api/v1/documentCollection") +public class DocumentChunkController extends BaseCurdController { + + @Resource + DocumentCollectionService documentCollectionService; + + @Resource + ModelService modelService; + + @Resource + DocumentChunkService documentChunkService; + + public DocumentChunkController(DocumentChunkService service) { + super(service); + } + + @PostMapping("update") + @SaCheckPermission("/api/v1/documentCollection/save") + public Result update(@JsonBody DocumentChunk documentChunk) { + boolean success = service.updateById(documentChunk); + if (success){ + DocumentChunk record = documentChunkService.getById(documentChunk.getId()); + DocumentCollection knowledge = documentCollectionService.getById(record.getDocumentCollectionId()); + if (knowledge == null) { + return Result.fail(1, "知识库不存在"); + } + DocumentStore documentStore = knowledge.toDocumentStore(); + if (documentStore == null) { + return Result.fail(2, "知识库没有配置向量库"); + } + // 设置向量模型 + Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId()); + if (model == null) { + return Result.fail(3, "知识库没有配置向量模型"); + } + EmbeddingModel embeddingModel = model.toEmbeddingModel(); + documentStore.setEmbeddingModel(embeddingModel); + StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection()); + Document document = Document.of(documentChunk.getContent()); + document.setId(documentChunk.getId()); + Map metadata = new HashMap<>(); + metadata.put("keywords", documentChunk.getMetadataKeyWords()); + metadata.put("questions", documentChunk.getMetadataQuestions()); + document.setMetadataMap(metadata); + StoreResult result = documentStore.update(document, options); // 更新已有记录 + return Result.ok(result); + } + return Result.ok(false); + } + + @PostMapping("removeChunk") + @SaCheckPermission("/api/v1/documentCollection/remove") + public Result remove(@JsonBody(value = "id", required = true) BigInteger chunkId) { + DocumentChunk docChunk = documentChunkService.getById(chunkId); + if (docChunk == null) { + return Result.fail(1, "记录不存在"); + } + DocumentCollection knowledge = documentCollectionService.getById(docChunk.getDocumentCollectionId()); + if (knowledge == null) { + return Result.fail(2, "知识库不存在"); + } + DocumentStore documentStore = knowledge.toDocumentStore(); + if (documentStore == null) { + return Result.fail(3, "知识库没有配置向量库"); + } + // 设置向量模型 + Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId()); + if (model == null) { + return Result.fail(4, "知识库没有配置向量模型"); + } + EmbeddingModel embeddingModel = model.toEmbeddingModel(); + documentStore.setEmbeddingModel(embeddingModel); + StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection()); + List deleteList = new ArrayList<>(); + deleteList.add(chunkId); + documentStore.delete(deleteList, options); + documentChunkService.removeChunk(knowledge, chunkId); + + return super.remove(chunkId); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java new file mode 100644 index 0000000..8f53c91 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java @@ -0,0 +1,54 @@ +package tech.easyflow.admin.controller.ai; + +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.entity.DocumentCollectionCategory; +import tech.easyflow.ai.entity.WorkflowCategory; +import tech.easyflow.ai.mapper.DocumentCollectionMapper; +import tech.easyflow.ai.service.DocumentCollectionCategoryService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.WorkflowCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2026-01-23 + */ +@RestController +@RequestMapping("/api/v1/documentCollectionCategory") +@UsePermission(moduleName = "/api/v1/documentCollection") +public class DocumentCollectionCategoryController extends BaseCurdController { + + @Resource + private DocumentCollectionMapper documentCollectionMapper; + + public DocumentCollectionCategoryController(DocumentCollectionCategoryService service) { + super(service); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + ids.forEach(id -> { + QueryWrapper queryWrapper = QueryWrapper.create().eq(DocumentCollection::getCategoryId, id); + List documentCollections = documentCollectionMapper.selectListByQuery(queryWrapper); + if (!documentCollections.isEmpty()) { + throw new BusinessException("请先删除该分类下的所有知识库"); + } + }); + + return super.onRemoveBefore(ids); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java new file mode 100644 index 0000000..b9461c3 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionController.java @@ -0,0 +1,108 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.easyagents.core.document.Document; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.util.StringUtils; +import tech.easyflow.ai.entity.BotDocumentCollection; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.service.BotDocumentCollectionService; +import tech.easyflow.ai.service.DocumentChunkService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/documentCollection") +public class DocumentCollectionController extends BaseCurdController { + + private final DocumentChunkService chunkService; + private final ModelService llmService; + + @Resource + private BotDocumentCollectionService botDocumentCollectionService; + + public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) { + super(service); + this.chunkService = chunkService; + this.llmService = llmService; + } + + @Override + protected Result onSaveOrUpdateBefore(DocumentCollection entity, boolean isSave) { + + String alias = entity.getAlias(); + + if (StringUtils.hasLength(alias)){ + DocumentCollection knowledge = service.getByAlias(alias); + + if (knowledge != null && isSave){ + throw new BusinessException("别名已存在!"); + } + + if (knowledge != null && knowledge.getId().compareTo(entity.getId()) != 0){ + throw new BusinessException("别名已存在!"); + } + + } else { + entity.setAlias(null); + } + + + if (isSave){ + Map options = new HashMap<>(); + if (entity.getSearchEngineEnable() == null){ + entity.setSearchEngineEnable(false); + } + options.put("canUpdateEmbeddingModel", true); + entity.setOptions(options); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @GetMapping("search") + @SaCheckPermission("/api/v1/documentCollection/query") + public Result> search(@RequestParam BigInteger knowledgeId, @RequestParam String keyword) { + return Result.ok(service.search(knowledgeId, keyword)); + } + + + @Override + protected Result onRemoveBefore(Collection ids) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.in(BotDocumentCollection::getId, ids); + + boolean exists = botDocumentCollectionService.exists(queryWrapper); + if (exists){ + throw new BusinessException("此知识库还关联着bot,请先取消关联!"); + } + + return null; + } + + @Override + public Result detail(String id) { + return Result.ok(service.getDetail(id)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java new file mode 100644 index 0000000..5a70399 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentController.java @@ -0,0 +1,201 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.entity.Document; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.entity.DocumentCollectionSplitParams; +import tech.easyflow.ai.service.DocumentChunkService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.DocumentService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.tree.Tree; +import tech.easyflow.common.util.RequestUtil; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/document") +@UsePermission(moduleName = "/api/v1/documentCollection") +public class DocumentController extends BaseCurdController { + + private final DocumentCollectionService knowledgeService; + + + @Autowired + private DocumentService documentService; + + + @Value("${easyflow.storage.local.root}") + private String fileUploadPath; + + + public DocumentController(DocumentService service, + DocumentCollectionService knowledgeService, + DocumentChunkService documentChunkService, ModelService modelService) { + super(service); + this.knowledgeService = knowledgeService; + } + @PostMapping("removeDoc") + @Transactional + @SaCheckPermission("/api/v1/documentCollection/remove") + public Result remove(@JsonBody(value = "id", required = true) String id) { + List ids = Collections.singletonList(id); + Result result = onRemoveBefore(ids); + if (result != null) return result; + boolean isSuccess = documentService.removeDoc(id); + if (!isSuccess){ + return Result.ok(false); + } + boolean success = service.removeById(id); + onRemoveAfter(ids); + return Result.ok(success); + } + + + /** + * 查询所有所有数据 + * + * @param entity + * @param asTree + * @param sortKey + * @param sortType + * @return 所有数据 + */ + @GetMapping("list") + @Override + @SaCheckPermission("/api/v1/documentCollection/query") + public Result> list(Document entity, Boolean asTree, String sortKey, String sortType) { + String kbSlug = RequestUtil.getParamAsString("id"); + if (StringUtil.noText(kbSlug)) { + throw new BusinessException("知识库id不能为空"); + } + + DocumentCollection knowledge = StringUtil.isNumeric(kbSlug) + ? knowledgeService.getById(kbSlug) + : knowledgeService.getOne(QueryWrapper.create().eq(DocumentCollection::getSlug, kbSlug)); + + if (knowledge == null) { + throw new BusinessException("知识库不存在"); + } + + QueryWrapper queryWrapper = QueryWrapper.create() + .eq(Document::getCollectionId, knowledge.getId()); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List documents = service.list(queryWrapper); + List list = Tree.tryToTree(documents, asTree); + return Result.ok(list); + } + + @GetMapping("documentList") + @SaCheckPermission("/api/v1/documentCollection/query") + public Result> documentList(@RequestParam(name="title", required = false) String fileName, @RequestParam(name="pageSize") int pageSize, @RequestParam(name = "pageNumber") int pageNumber) { + String kbSlug = RequestUtil.getParamAsString("id"); + if (StringUtil.noText(kbSlug)) { + throw new BusinessException("知识库id不能为空"); + } + Page documentList = documentService.getDocumentList(kbSlug, pageSize, pageNumber,fileName); + return Result.ok(documentList); + } + + + @Override + protected String getDefaultOrderBy() { + return "order_no asc"; + } + + + @PostMapping("update") + @Override + @SaCheckPermission("/api/v1/documentCollection/save") + public Result update(@JsonBody Document entity) { + super.update(entity); + return Result.ok(updatePosition(entity)); + } + + /** + * 文本拆分/保存 + * + */ + @PostMapping(value = {"textSplit", "/saveText"}) + @SaCheckPermission("/api/v1/documentCollection/save") + public Result textSplit(@JsonBody DocumentCollectionSplitParams documentCollectionSplitParams) { + return documentService.textSplit(documentCollectionSplitParams); + } + + /** + * 更新 entity + * + * @param entity + * @return Result + */ + private boolean updatePosition(Document entity) { + Integer orderNo = entity.getOrderNo(); + if (orderNo != null) { + if (orderNo <= 0) orderNo = 0; + BigInteger knowledgeId = service.getById(entity.getId()).getCollectionId(); + List list = service.list(QueryWrapper.create() + .eq(Document::getCollectionId, knowledgeId) + .orderBy(getDefaultOrderBy()) + ); + + list.removeIf(item -> item.getId().equals(entity.getId())); + if (orderNo >= list.size()) { + list.add(entity); + } else { + list.add(orderNo, entity); + } + + List updateList = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + Document updateItem = new Document(); + updateItem.setId(list.get(i).getId()); + updateItem.setOrderNo(i); + updateList.add(updateItem); + } + + service.updateBatch(updateList); + } + + return true; + } + + + public String getRootPath() { + if (StringUtil.hasText(this.fileUploadPath)) { + return this.fileUploadPath; + } + ClassPathResource fileResource = new ClassPathResource("/"); + try { + return new File(fileResource.getFile(), "/public").getAbsolutePath(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentHistoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentHistoryController.java new file mode 100644 index 0000000..0a673a1 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentHistoryController.java @@ -0,0 +1,21 @@ +package tech.easyflow.admin.controller.ai; + +import tech.easyflow.ai.entity.DocumentHistory; +import tech.easyflow.ai.service.DocumentHistoryService; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/documentHistory") +public class DocumentHistoryController extends BaseCurdController { + public DocumentHistoryController(DocumentHistoryService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FilePreviewController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FilePreviewController.java new file mode 100644 index 0000000..403d963 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FilePreviewController.java @@ -0,0 +1,73 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaIgnore; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.HandlerMapping; +import tech.easyflow.common.util.StringUtil; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +@RestController +public class FilePreviewController { + + // 定义图片存储的基础路径 + @Value("${easyflow.storage.local.root}") + private String fileUploadPath; + + @SaIgnore + @GetMapping("/api/images/**") + public ResponseEntity getImage(HttpServletRequest request) throws IOException { + // 获取完整的路径 + String fullPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + String basePath = "/api/images/"; + String filePath = fullPath.substring(basePath.length()-1); + String imagePath = getRootPath() + filePath; + File imageFile = new File(imagePath); + + if (!imageFile.exists()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + // 读取文件内容 + byte[] fileContent = Files.readAllBytes(imageFile.toPath()); + + // 返回文件内容 + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_TYPE, getContentType(imageFile)) + .body(fileContent); + } + + // 根据文件扩展名获取 MIME 类型 + private String getContentType(File file) { + String fileName = file.getName().toLowerCase(); + if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (fileName.endsWith(".png")) { + return "image/png"; + } else if (fileName.endsWith(".gif")) { + return "image/gif"; + } + return "application/octet-stream"; // 默认类型 + } + + public String getRootPath() { + if (StringUtil.hasText(this.fileUploadPath)) { + return this.fileUploadPath; + } + ClassPathResource fileResource = new ClassPathResource("/"); + try { + return new File(fileResource.getFile(), "/public").getAbsolutePath(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java new file mode 100644 index 0000000..a7244b3 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/McpController.java @@ -0,0 +1,83 @@ +package tech.easyflow.admin.controller.ai; + +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotMcp; +import tech.easyflow.ai.entity.Mcp; +import tech.easyflow.ai.service.BotMcpService; +import tech.easyflow.ai.service.McpService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.io.Serializable; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2026-01-04 + */ +@RestController +@RequestMapping("/api/v1/mcp") +public class McpController extends BaseCurdController { + public McpController(McpService service) { + super(service); + } + + @Resource + private BotMcpService botMcpService; + @Override + public Result save(Mcp entity) { + return service.saveMcp(entity); + } + + @Override + public Result update(Mcp entity) { + return service.updateMcp(entity); + } + + @Override + @Transactional + public Result remove(Serializable id) { + service.removeMcp(id); + botMcpService.remove(QueryWrapper.create().eq(BotMcp::getMcpId, id)); + return Result.ok(); + } + + @Override + public Result> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) { + Result> page = super.page(request, sortKey, sortType, pageNumber, pageSize); + return service.pageMcp(page); + } + + @PostMapping("/getMcpTools") + public Result getMcpTools(@JsonBody("id") String id) { + + return Result.ok(service.getMcpTools(id)); + } + + + @GetMapping("pageTools") + public Result> pageTools(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) { + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1L; + } + if (pageSize == null || pageSize < 1) { + pageSize = 10L; + } + + QueryWrapper queryWrapper = buildQueryWrapper(request); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + Page mcpPage = queryPage(new Page<>(pageNumber, pageSize), queryWrapper); + + return Result.ok(service.pageTools(mcpPage)); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java new file mode 100644 index 0000000..95b22da --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelController.java @@ -0,0 +1,174 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.BaseMapper; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.entity.ModelProvider; +import tech.easyflow.ai.entity.table.ModelTableDef; +import tech.easyflow.ai.mapper.ModelMapper; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.tree.Tree; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/model") +public class ModelController extends BaseCurdController { + + public ModelController(ModelService service) { + super(service); + } + + @Autowired + ModelService modelService; + + @Resource + ModelMapper modelMapper; + + @GetMapping("list") + @SaCheckPermission("/api/v1/model/query") + public Result> list(Model entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List list = Tree.tryToTree(modelMapper.selectListWithRelationsByQuery(queryWrapper), asTree); + list.forEach(item -> { + String providerName = Optional.ofNullable(item.getModelProvider()) + .map(ModelProvider::getProviderName) + .orElse("-"); + item.setTitle(providerName + "/" + item.getTitle()); + }); + return Result.ok(list); + } + + @GetMapping("getList") + @SaCheckPermission("/api/v1/model/query") + public Result>>> getList(Model entity) { + return Result.ok(modelService.getList(entity)); + } + + @PostMapping("/addAiLlm") + @SaCheckPermission("/api/v1/model/save") + public Result addAiLlm(Model entity) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + commonFiled(entity, account.getId(), account.getTenantId(), account.getDeptId()); + return Result.ok(modelService.addAiLlm(entity)); + } + + + @GetMapping("verifyLlmConfig") + @SaCheckPermission("/api/v1/model/save") + public Result verifyLlmConfig(@RequestParam BigInteger id) { + Model model = service.getModelInstance(id); + return Result.ok(modelService.verifyModelConfig(model)); + } + + @PostMapping("/removeByEntity") + @SaCheckPermission("/api/v1/model/remove") + public Result removeByEntity(@RequestBody Model entity) { + modelService.removeByEntity(entity); + return Result.ok(); + } + + @PostMapping("/updateByEntity") + @SaCheckPermission("/api/v1/model/save") + public Result updateByEntity(@RequestBody Model entity) { + modelService.updateByEntity(entity); + return Result.ok(); + } + + @GetMapping("/selectLlmByProviderCategory") + @SaCheckPermission("/api/v1/model/query") + public Result>> selectLlmByProviderCategory(Model entity, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + BaseMapper mapper = service.getMapper(); + List totalList = mapper.selectListWithRelationsByQuery(queryWrapper); + Map> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName)); + return Result.ok(groupList); + } + + @GetMapping("/selectLlmByProviderAndModelType") + @SaCheckPermission("/api/v1/model/query") + public Result>> selectLlmByProviderAndModelType( + @RequestParam String modelType, + @RequestParam BigInteger providerId, + @RequestParam(required = false) String selectText + ) { + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(Model::getProviderId, providerId); + queryWrapper.eq(Model::getModelType, modelType); + if (StringUtils.hasLength(selectText)) { + queryWrapper.and(ModelTableDef.MODEL.TITLE.like(selectText).or(ModelTableDef.MODEL.MODEL_NAME.like(selectText))); + } + List totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper); + Map> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName)); + return Result.ok(groupList); + } + + + /** + * 添加所有模型 + * + * @param entity + * @return + */ + @PostMapping("addAllLlm") + public Result addAllLlm(@JsonBody Model entity) { + QueryWrapper queryWrapper = QueryWrapper.create().eq(Model::getProviderId, entity.getProviderId()); + service.update(entity, queryWrapper); + return Result.ok(); + } + + @GetMapping("/selectLlmList") + @SaCheckPermission("/api/v1/model/query") + public Result> selectLlmList(Model entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List totalList = Tree.tryToTree(modelMapper.selectListWithRelationsByQuery(queryWrapper), asTree); + totalList.forEach(aiLlm -> { + aiLlm.setTitle(aiLlm.getModelProvider().getProviderName() + "/" + aiLlm.getTitle()); + }); + return Result.ok(totalList); + } + + @Override + protected Result onSaveOrUpdateBefore(Model entity, boolean isSave) { + if (isSave) { + entity.setWithUsed(true); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + + @PostMapping("removeLlmByIds") + @Transactional + public Result removeLlm(@JsonBody(value = "id", required = true) Serializable id) { + List ids = Collections.singletonList(id); + QueryWrapper queryWrapper = QueryWrapper.create().in(Model::getId, ids); + service.remove(queryWrapper); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java new file mode 100644 index 0000000..6a6df63 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ModelProviderController.java @@ -0,0 +1,48 @@ +package tech.easyflow.admin.controller.ai; + +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.entity.ModelProvider; +import tech.easyflow.ai.service.ModelProviderService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import java.io.Serializable; + +/** + * 控制层。 + * + * @author 12076 + * @since 2025-12-16 + */ +@RestController +@RequestMapping("/api/v1/modelProvider") +@UsePermission(moduleName = "/api/v1/model") +public class ModelProviderController extends BaseCurdController { + private final ModelService modelService; + + public ModelProviderController(ModelProviderService service, ModelService modelService) { + super(service); + this.modelService = modelService; + } + + @Override + @PostMapping("remove") + @Transactional + public Result remove(@JsonBody(value = "id", required = true) Serializable id) { + QueryWrapper modelQueryWrapper = QueryWrapper.create() + .in(Model::getProviderId, id); + if (modelService.count(modelQueryWrapper) > 0) { + throw new BusinessException("Delete all child models before removing this Model Provider"); + } + return Result.ok(service.removeById(id)); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java new file mode 100644 index 0000000..45fd284 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java @@ -0,0 +1,40 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.PluginCategory; +import tech.easyflow.ai.service.PluginCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; + +import javax.annotation.Resource; +import java.math.BigInteger; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2025-05-21 + */ +@RestController +@RequestMapping("/api/v1/pluginCategory") +@UsePermission(moduleName = "/api/v1/plugin") +public class PluginCategoryController extends BaseCurdController { + public PluginCategoryController(PluginCategoryService service) { + super(service); + } + + @Resource + private PluginCategoryService pluginCategoryService; + + @GetMapping("/doRemoveCategory") + @SaCheckPermission("/api/v1/plugin/remove") + public Result doRemoveCategory(@RequestParam("id") BigInteger id){ + + return Result.ok(pluginCategoryService.doRemoveCategory(id)); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryMappingController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryMappingController.java new file mode 100644 index 0000000..0cf2fea --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryMappingController.java @@ -0,0 +1,45 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.entity.PluginCategory; +import tech.easyflow.ai.entity.PluginCategoryMapping; +import tech.easyflow.ai.service.PluginCategoryMappingService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2025-05-21 + */ +@RestController +@RequestMapping("/api/v1/pluginCategoryMapping") +public class PluginCategoryMappingController extends BaseCurdController { + public PluginCategoryMappingController(PluginCategoryMappingService service) { + super(service); + } + + @Resource + private PluginCategoryMappingService relationService; + + @PostMapping("/updateRelation") + public Result updateRelation( + @JsonBody(value="pluginId") BigInteger pluginId, + @JsonBody(value="categoryIds") ArrayList categoryIds + ){ + return Result.ok(relationService.updateRelation(pluginId, categoryIds)); + } + + @GetMapping("/getPluginCategories") + public Result> getPluginCategories(@RequestParam(value="pluginId") BigInteger pluginId + ){ + return Result.ok(relationService.getPluginCategories(pluginId)); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java new file mode 100644 index 0000000..3f39cc4 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java @@ -0,0 +1,90 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.ai.service.PluginService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 控制层。 + * + * @author Administrator + * @since 2025-04-25 + */ +@RestController +@RequestMapping("/api/v1/plugin") +public class PluginController extends BaseCurdController { + public PluginController(PluginService service) { + super(service); + } + + @Resource + PluginService pluginService; + + @Override + protected Result onSaveOrUpdateBefore(Plugin entity, boolean isSave) { + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @PostMapping("/plugin/save") + @SaCheckPermission("/api/v1/plugin/save") + public Result savePlugin(@JsonBody Plugin plugin){ + + return Result.ok(pluginService.savePlugin(plugin)); + } + + @PostMapping("/plugin/update") + @SaCheckPermission("/api/v1/plugin/save") + public Result updatePlugin(@JsonBody Plugin plugin){ + + return Result.ok(pluginService.updatePlugin(plugin)); + } + + @PostMapping("/plugin/remove") + @SaCheckPermission("/api/v1/plugin/remove") + public Result removePlugin(@JsonBody(value = "id", required = true) String id){ + + return Result.ok(pluginService.removePlugin(id)); + } + + @PostMapping("/getList") + @SaCheckPermission("/api/v1/plugin/query") + public Result> getList(){ + return Result.ok(pluginService.getList()); + } + + @GetMapping("/pageByCategory") + @SaCheckPermission("/api/v1/plugin/query") + public Result> pageByCategory(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize, int category) { + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1L; + } + if (pageSize == null || pageSize < 1) { + pageSize = 10L; + } + if (category == 0){ + QueryWrapper queryWrapper = buildQueryWrapper(request); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper)); + } else { + return pluginService.pageByCategory(pageNumber, pageSize, category); + } + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + return service.getMapper().paginateWithRelations(page, queryWrapper); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java new file mode 100644 index 0000000..4da6e18 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginItemController.java @@ -0,0 +1,150 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.ai.entity.PluginItem; +import tech.easyflow.ai.service.BotPluginService; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.List; + +/** + * 控制层。 + * + * @author WangGangqiang + * @since 2025-04-27 + */ +@RestController +@RequestMapping("/api/v1/pluginItem") +@UsePermission(moduleName = "/api/v1/plugin") +public class PluginItemController extends BaseCurdController { + public PluginItemController(PluginItemService service) { + super(service); + } + + @Resource + private PluginItemService pluginItemService; + + @Resource + private BotPluginService botPluginService; + + @PostMapping("/tool/save") + @SaCheckPermission("/api/v1/plugin/save") + public Result savePlugin(@JsonBody PluginItem pluginItem){ + + return Result.ok(pluginItemService.savePluginTool(pluginItem)); + } + + // 插件工具修改页面查询 + @PostMapping("/tool/search") + @SaCheckPermission("/api/v1/plugin/query") + public Result searchPlugin(@JsonBody(value = "aiPluginToolId", required = true) BigInteger aiPluginToolId){ + return pluginItemService.searchPlugin(aiPluginToolId); + } + + @PostMapping("/toolsList") + @SaCheckPermission("/api/v1/plugin/query") + public Result> searchPluginToolByPluginId(@JsonBody(value = "pluginId", required = true) BigInteger pluginId, + @JsonBody(value = "botId", required = false) BigInteger botId){ + return Result.ok(pluginItemService.searchPluginToolByPluginId(pluginId, botId)); + } + + @PostMapping("/tool/update") + @SaCheckPermission("/api/v1/plugin/save") + public Result updatePlugin(@JsonBody PluginItem pluginItem){ + return Result.ok(pluginItemService.updatePlugin(pluginItem)); + } + + @PostMapping("/tool/list") + @SaCheckPermission("/api/v1/plugin/query") + public Result> getPluginToolList(@JsonBody(value = "botId", required = true) BigInteger botId){ + return Result.ok(pluginItemService.getPluginToolList(botId)); + } + + @GetMapping("/getTinyFlowData") + @SaCheckPermission("/api/v1/plugin/query") + public Result getTinyFlowData(BigInteger id) { + JSONObject nodeData = new JSONObject(); + PluginItem record = pluginItemService.getById(id); + if (record == null) { + return Result.ok(nodeData); + } + nodeData.put("pluginId", record.getId().toString()); + nodeData.put("pluginName", record.getName()); + + JSONArray parameters = new JSONArray(); + JSONArray outputDefs = new JSONArray(); + String inputData = record.getInputData(); + if (StrUtil.isNotEmpty(inputData)) { + JSONArray array = JSON.parseArray(inputData); + handleArray(array); + parameters = array; + } + String outputData = record.getOutputData(); + if (StrUtil.isNotEmpty(outputData)) { + JSONArray array = JSON.parseArray(outputData); + handleArray(array); + outputDefs = array; + } + nodeData.put("parameters", parameters); + nodeData.put("outputDefs", outputDefs); + return Result.ok(nodeData); + } + + /** + * 插件试运行接口 + * @return + */ + @PostMapping("/test") + public Result pluginToolTest(@JsonBody(value = "inputData", required = true) String inputData, + @JsonBody(value = "pluginToolId", required = true) BigInteger pluginToolId){ + return pluginItemService.pluginToolTest(inputData, pluginToolId); + } + + private void handleArray(JSONArray array) { + for (Object o : array) { + JSONObject obj = (JSONObject) o; + obj.put("id", IdUtil.simpleUUID()); + obj.put("nameDisabled", true); + obj.put("dataTypeDisabled", true); + obj.put("deleteDisabled", true); + obj.put("addChildDisabled", true); + JSONArray children = obj.getJSONArray("children"); + if (children != null) { + handleArray(children); + } + } + } + + @Override + protected Result onRemoveBefore(Collection ids) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.in(BotPlugin::getPluginItemId, ids); + + boolean exists = botPluginService.exists(queryWrapper); + if (exists){ + return Result.fail(1, "此工具还关联着bot,请先取消关联!"); + } + + return null; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java new file mode 100644 index 0000000..8275b96 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java @@ -0,0 +1,22 @@ +package tech.easyflow.admin.controller.ai; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.ResourceCategory; +import tech.easyflow.ai.service.ResourceCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; + +/** + * 素材分类 + */ +@RestController +@RequestMapping("/api/v1/resourceCategory") +@UsePermission(moduleName = "/api/v1/resource") +public class ResourceCategoryController extends BaseCurdController { + + public ResourceCategoryController(ResourceCategoryService service) { + super(service); + } + +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java new file mode 100644 index 0000000..6b367bd --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java @@ -0,0 +1,56 @@ +package tech.easyflow.admin.controller.ai; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.http.HttpUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.Resource; +import tech.easyflow.ai.service.ResourceService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.util.Date; + +/** + * 素材库 + * + * @author ArkLight + * @since 2025-06-27 + */ +@RestController +@RequestMapping("/api/v1/resource") +public class ResourceController extends BaseCurdController { + public ResourceController(ResourceService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(Resource entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + String resourceUrl = entity.getResourceUrl(); + byte[] bytes = HttpUtil.downloadBytes(resourceUrl); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + String suffix = FileTypeUtil.getType(stream, resourceUrl); + entity.setSuffix(suffix); + entity.setFileSize(BigInteger.valueOf(bytes.length)); + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + return super.queryPage(page, queryWrapper); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java new file mode 100644 index 0000000..af25cf7 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkFlowNodeController.java @@ -0,0 +1,85 @@ +package tech.easyflow.admin.controller.ai; + +import cn.hutool.core.util.IdUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.Node; +import com.easyagents.flow.core.node.ConfirmNode; +import com.easyagents.flow.core.node.EndNode; +import com.easyagents.flow.core.node.StartNode; +import com.easyagents.flow.core.parser.ChainParser; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.util.List; + +@RequestMapping("/api/v1/workflowNode") +@RestController +public class WorkFlowNodeController { + + @Resource + private WorkflowService workflowService; + @Resource + private ChainParser chainParser; + + @GetMapping("/getChainParams") + public Result getChainParams(String currentId, String workflowId) { + if (workflowId.equals(currentId)) { + throw new BusinessException("工作流不能作为自身子节点"); + } + JSONObject nodeData = new JSONObject(); + Workflow workflow = workflowService.getById(workflowId); + if (workflow == null) { + throw new BusinessException("工作流不存在: " + workflowId); + } + nodeData.put("workflowId", workflow.getId()); + nodeData.put("workflowName", workflow.getTitle()); + + ChainDefinition definition = chainParser.parse(workflow.getContent()); + List nodes = definition.getNodes(); + JSONArray inputs = new JSONArray(); + JSONArray outputs = new JSONArray(); + for (Node node : nodes) { + if (node instanceof StartNode) { + inputs = JSON.parseArray(JSON.toJSONString(node.getParameters())); + handleArray(inputs); + } + if (node instanceof EndNode) { + outputs = JSON.parseArray(JSON.toJSONString(((EndNode) node).getOutputDefs())); + handleArray(outputs); + } + if (node instanceof ConfirmNode) { + throw new BusinessException("工作流存在【确认节点】,暂不支持作为子节点"); + } + } + nodeData.put("parameters", inputs); + nodeData.put("outputDefs", outputs); + return Result.ok(nodeData); + } + + private void handleArray(JSONArray array) { + if (array != null) { + for (Object o : array) { + JSONObject obj = (JSONObject) o; + obj.put("id", IdUtil.simpleUUID()); + obj.put("nameDisabled", true); + obj.put("dataTypeDisabled", true); + obj.put("deleteDisabled", true); + obj.put("addChildDisabled", true); + obj.put("refType", "ref"); + JSONArray children = obj.getJSONArray("children"); + if (children != null) { + handleArray(children); + } + } + } + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java new file mode 100644 index 0000000..f0cb49d --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java @@ -0,0 +1,25 @@ +package tech.easyflow.admin.controller.ai; + +import tech.easyflow.ai.entity.WorkflowCategory; +import tech.easyflow.ai.service.WorkflowCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 控制层。 + * + * @author ArkLight + * @since 2025-12-11 + */ +@RestController +@RequestMapping("/api/v1/workflowCategory") +@UsePermission(moduleName = "/api/v1/workflow") +public class WorkflowCategoryController extends BaseCurdController { + + public WorkflowCategoryController(WorkflowCategoryService service) { + super(service); + } + +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowController.java new file mode 100644 index 0000000..46fcd59 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowController.java @@ -0,0 +1,213 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import com.mybatisflex.core.query.QueryWrapper; +import com.easyagents.flow.core.chain.*; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import com.easyagents.flow.core.parser.ChainParser; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.BotWorkflowService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.ai.easyagentsflow.entity.ChainInfo; +import tech.easyflow.ai.easyagentsflow.entity.NodeInfo; +import tech.easyflow.ai.easyagentsflow.service.TinyFlowService; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.service.SysApiKeyService; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/api/v1/workflow") +public class WorkflowController extends BaseCurdController { + private final ModelService modelService; + + @Resource + private SysApiKeyService apiKeyService; + @Resource + private BotWorkflowService botWorkflowService; + @Resource + private ChainExecutor chainExecutor; + @Resource + private ChainParser chainParser; + @Resource + private TinyFlowService tinyFlowService; + + public WorkflowController(WorkflowService service, ModelService modelService) { + super(service); + this.modelService = modelService; + } + + /** + * 节点单独运行 + */ + @PostMapping("/singleRun") + @SaCheckPermission("/api/v1/workflow/save") + public Result singleRun( + @JsonBody(value = "workflowId", required = true) BigInteger workflowId, + @JsonBody(value = "nodeId", required = true) String nodeId, + @JsonBody("variables") Map variables) { + + Workflow workflow = service.getById(workflowId); + if (workflow == null) { + return Result.fail(1, "工作流不存在"); + } + Map res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables); + return Result.ok(res); + } + + /** + * 运行工作流 - v2 + */ + @PostMapping("/runAsync") + @SaCheckPermission("/api/v1/workflow/save") + public Result runAsync(@JsonBody(value = "id", required = true) BigInteger id, + @JsonBody("variables") Map variables) { + if (variables == null) { + variables = new HashMap<>(); + } + Workflow workflow = service.getById(id); + if (workflow == null) { + throw new RuntimeException("工作流不存在"); + } + if (StpUtil.isLogin()) { + variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount()); + } + String executeId = chainExecutor.executeAsync(id.toString(), variables); + return Result.ok(executeId); + } + + /** + * 获取工作流运行状态 - v2 + */ + @PostMapping("/getChainStatus") + public Result getChainStatus(@JsonBody(value = "executeId") String executeId, + @JsonBody("nodes") List nodes) { + ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes); + return Result.ok(res); + } + + /** + * 恢复工作流运行 - v2 + */ + @PostMapping("/resume") + @SaCheckPermission("/api/v1/workflow/save") + public Result resume(@JsonBody(value = "executeId", required = true) String executeId, + @JsonBody("confirmParams") Map confirmParams) { + chainExecutor.resumeAsync(executeId, confirmParams); + return Result.ok(); + } + + @PostMapping("/importWorkFlow") + @SaCheckPermission("/api/v1/workflow/save") + public Result importWorkFlow(Workflow workflow, MultipartFile jsonFile) throws Exception { + InputStream is = jsonFile.getInputStream(); + String content = IoUtil.read(is, StandardCharsets.UTF_8); + workflow.setContent(content); + save(workflow); + return Result.ok(); + } + + @GetMapping("/exportWorkFlow") + @SaCheckPermission("/api/v1/workflow/save") + public Result exportWorkFlow(BigInteger id) { + Workflow workflow = service.getById(id); + return Result.ok("", workflow.getContent()); + } + + @GetMapping("getRunningParameters") + @SaCheckPermission("/api/v1/workflow/query") + public Result getRunningParameters(@RequestParam BigInteger id) { + Workflow workflow = service.getById(id); + + if (workflow == null) { + return Result.fail(1, "can not find the workflow by id: " + id); + } + + ChainDefinition definition = chainParser.parse(workflow.getContent()); + if (definition == null) { + return Result.fail(2, "节点配置错误,请检查! "); + } + List chainParameters = definition.getStartParameters(); + Map res = new HashMap<>(); + res.put("parameters", chainParameters); + res.put("title", workflow.getTitle()); + res.put("description", workflow.getDescription()); + res.put("icon", workflow.getIcon()); + return Result.ok(res); + } + + @Override + public Result detail(String id) { + Workflow workflow = service.getDetail(id); + return Result.ok(workflow); + } + + @GetMapping("/copy") + @SaCheckPermission("/api/v1/workflow/save") + public Result copy(BigInteger id) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + Workflow workflow = service.getById(id); + workflow.setId(null); + workflow.setAlias(IdUtil.fastSimpleUUID()); + commonFiled(workflow, account.getId(), account.getTenantId(), account.getDeptId()); + service.save(workflow); + return Result.ok(); + } + + @Override + protected Result onSaveOrUpdateBefore(Workflow entity, boolean isSave) { + + String alias = entity.getAlias(); + if (StringUtils.hasLength(alias)) { + Workflow workflow = service.getByAlias(alias); + if (workflow == null) { + return null; + } + if (isSave) { + throw new BusinessException("别名已存在!"); + } + BigInteger id = entity.getId(); + if (id.compareTo(workflow.getId()) != 0) { + throw new BusinessException("别名已存在!"); + } + } else { + entity.setAlias(null); + } + return null; + } + + @Override + protected Result onRemoveBefore(Collection ids) { + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.in("workflow_id", ids); + boolean exists = botWorkflowService.exists(queryWrapper); + if (exists) { + return Result.fail(1, "此工作流还关联有bot,请先取消关联后再删除!"); + } + return null; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecResultController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecResultController.java new file mode 100644 index 0000000..7193390 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecResultController.java @@ -0,0 +1,64 @@ +package tech.easyflow.admin.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.service.WorkflowExecResultService; +import tech.easyflow.ai.service.WorkflowExecStepService; +import tech.easyflow.ai.utils.WorkFlowUtil; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import javax.annotation.Resource; +import java.math.BigInteger; + +/** + * 工作流执行记录 + */ +@RestController +@RequestMapping("/api/v1/workflowExecResult") +@UsePermission(moduleName = "/api/v1/workflow") +public class WorkflowExecResultController extends BaseCurdController { + + @Resource + private WorkflowExecStepService recordStepService; + + public WorkflowExecResultController(WorkflowExecResultService service) { + super(service); + } + + @GetMapping("/del") + @Transactional(rollbackFor = Exception.class) + @SaCheckPermission("/api/v1/workflow/remove") + public Result del(BigInteger id) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + WorkflowExecResult record = service.getById(id); + if (!account.getId().toString().equals(record.getCreatedBy())) { + return Result.fail(1, "非法请求"); + } + service.removeById(id); + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecStep::getRecordId, id); + recordStepService.remove(w); + return Result.ok(); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + queryWrapper.eq(WorkflowExecResult::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + Page res = super.queryPage(page, queryWrapper); + for (WorkflowExecResult record : res.getRecords()) { + record.setWorkflowJson(WorkFlowUtil.removeSensitiveInfo(record.getWorkflowJson())); + } + return res; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecStepController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecStepController.java new file mode 100644 index 0000000..ae5614d --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowExecStepController.java @@ -0,0 +1,63 @@ +package tech.easyflow.admin.controller.ai; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.service.WorkflowExecResultService; +import tech.easyflow.ai.service.WorkflowExecStepService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 执行记录步骤 + */ +@RestController +@RequestMapping("/api/v1/workflowExecStep") +@UsePermission(moduleName = "/api/v1/workflow") +public class WorkflowExecStepController extends BaseCurdController { + + @Resource + private WorkflowExecResultService execRecordService; + + public WorkflowExecStepController(WorkflowExecStepService service) { + super(service); + } + + @GetMapping("/getListByRecordId") + public Result> getListByRecordId(BigInteger recordId) { + if (recordId == null) { + throw new BusinessException("recordId不能为空!"); + } + WorkflowExecResult record = execRecordService.getById(recordId); + String workflowJson = record.getWorkflowJson(); + JSONObject workflow = JSON.parseObject(workflowJson); + Map idTypeMap = new HashMap<>(); + JSONArray nodes = workflow.getJSONArray("nodes"); + for (Object node : nodes) { + JSONObject nodeObj = (JSONObject) node; + idTypeMap.put(nodeObj.getString("id"), nodeObj.getString("type")); + } + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecStep::getRecordId, recordId); + List list = service.list(w); + for (WorkflowExecStep step : list) { + step.setNodeData(null); + step.setNodeType(idTypeMap.get(step.getNodeId())); + } + return Result.ok(list); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/auth/AuthController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/auth/AuthController.java new file mode 100644 index 0000000..0bca5e4 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/auth/AuthController.java @@ -0,0 +1,41 @@ +package tech.easyflow.admin.controller.auth; + +import org.springframework.web.bind.annotation.GetMapping; +import tech.easyflow.auth.entity.LoginDTO; +import tech.easyflow.auth.entity.LoginVO; +import tech.easyflow.auth.service.AuthService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.jsonbody.JsonBody; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/auth/") +public class AuthController { + + @Resource + private AuthService authService; + + @PostMapping("login") + public Result login(@JsonBody LoginDTO loginDTO) { + LoginVO res = authService.login(loginDTO); + return Result.ok(res); + } + + @PostMapping("logout") + public Result logout() { + StpUtil.logout(); + return Result.ok(); + } + + @GetMapping("getPermissions") + public Result> getPermissions() { + List permissionList = StpUtil.getPermissionList(); + return Result.ok(permissionList); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/DictController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/DictController.java new file mode 100644 index 0000000..71f07b7 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/DictController.java @@ -0,0 +1,40 @@ +package tech.easyflow.admin.controller.common; + +import jakarta.servlet.http.HttpServletRequest; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.dict.Dict; +import tech.easyflow.common.dict.DictItem; +import tech.easyflow.common.dict.DictLoader; +import tech.easyflow.common.dict.DictManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/dict/") +public class DictController { + + @Resource + DictManager dictManager; + + @GetMapping("/items/{code}") + public Result> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) { + DictLoader loader = dictManager.getLoader(code); + if (loader == null) { + return Result.ok(Collections.emptyList()); + } + Map parameterMap = request.getParameterMap(); + Dict dict = loader.load(keyword, parameterMap); + if (dict == null) { + return Result.ok(Collections.emptyList()); + } + return Result.ok(dict.getItems()); + } + +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/PublicController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/PublicController.java new file mode 100644 index 0000000..9200bb8 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/PublicController.java @@ -0,0 +1,49 @@ +package tech.easyflow.admin.controller.common; + +import cloud.tianai.captcha.application.ImageCaptchaApplication; +import cloud.tianai.captcha.application.vo.ImageCaptchaVO; +import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; +import cloud.tianai.captcha.common.response.ApiResponse; +import com.alibaba.fastjson2.JSON; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.captcha.tainai.CaptchaData; +import tech.easyflow.system.entity.SysOption; +import tech.easyflow.system.service.SysOptionService; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +/** + * 公共接口 + */ +@RestController +@RequestMapping("/api/v1/public") +public class PublicController { + + @Resource + private ImageCaptchaApplication application; + @Resource + private SysOptionService sysOptionService; + + /** + * 获取验证码 + */ + @RequestMapping(value = "/getCaptcha", produces = "application/json") + public ApiResponse getCaptcha() { + return application.generateCaptcha(CaptchaTypeConstant.SLIDER); + } + + /** + * 验证码校验 + */ + @PostMapping(value = "/check", produces = "application/json") + public ApiResponse checkCaptcha(@RequestBody CaptchaData data) { + ApiResponse response = application.matching(data.getId(), data.getData()); + if (!response.isSuccess()) { + return ApiResponse.ofError("验证码错误"); + } + return ApiResponse.ofSuccess(data.getId()); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/UploadController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/UploadController.java new file mode 100644 index 0000000..06d58aa --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/common/UploadController.java @@ -0,0 +1,47 @@ +package tech.easyflow.admin.controller.common; + +import cn.dev33.satoken.annotation.SaIgnore; +import tech.easyflow.common.domain.Result; + +import tech.easyflow.common.vo.UploadResVo; +import tech.easyflow.common.filestorage.FileStorageService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/api/v1/commons/") +public class UploadController { + + @Resource(name = "default") + FileStorageService storageService; + + @PostMapping(value = "/upload", produces = MediaType.APPLICATION_JSON_VALUE) + public Result upload(MultipartFile file) { + String path = storageService.save(file); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } + + @PostMapping(value = "/uploadAntd", produces = MediaType.APPLICATION_JSON_VALUE) + public Result uploadAntd(MultipartFile file) { + String path = storageService.save(file); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } + + @PostMapping(value = "/uploadPrePath",produces = MediaType.APPLICATION_JSON_VALUE) + @SaIgnore + public Result uploadPrePath(MultipartFile file, String prePath) { + String path = storageService.save(file,prePath); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableController.java new file mode 100644 index 0000000..7550bf2 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableController.java @@ -0,0 +1,195 @@ +package tech.easyflow.admin.controller.datacenter; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.collection.CollectionUtil; +import cn.idev.excel.EasyExcel; +import cn.idev.excel.FastExcel; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.row.Row; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.DatacenterQuery; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.datacenter.entity.DatacenterTable; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.entity.vo.HeaderVo; +import tech.easyflow.datacenter.excel.ReadDataListener; +import tech.easyflow.datacenter.excel.ReadResVo; +import tech.easyflow.datacenter.service.DatacenterTableFieldService; +import tech.easyflow.datacenter.service.DatacenterTableService; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.math.BigInteger; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 数据中枢表 控制层。 + * + * @author ArkLight + * @since 2025-07-10 + */ +@RestController +@RequestMapping("/api/v1/datacenterTable") +public class DatacenterTableController extends BaseCurdController { + + @Resource + private DatacenterTableFieldService fieldsService; + + public DatacenterTableController(DatacenterTableService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(DatacenterTable entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModifiedBy(loginUser.getId()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @PostMapping("/saveTable") + @SaCheckPermission("/api/v1/datacenterTable/save") + public Result saveTable(@RequestBody DatacenterTable entity) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + List fields = entity.getFields(); + if (CollectionUtil.isEmpty(fields)) { + return Result.fail(99, "字段不能为空"); + } + BigInteger id = entity.getId(); + if (id == null) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + service.saveTable(entity, loginUser); + return Result.ok(); + } + + @GetMapping("/detailInfo") + @SaCheckPermission("/api/v1/datacenterTable/query") + public Result detailInfo(BigInteger tableId) { + DatacenterTable table = service.getById(tableId); + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.eq(DatacenterTableField::getTableId, tableId); + wrapper.orderBy("id"); + List fields = fieldsService.list(wrapper); + table.setFields(fields); + return Result.ok(table); + } + + @GetMapping("/removeTable") + @SaCheckPermission("/api/v1/datacenterTable/remove") + public Result removeTable(BigInteger tableId) { + service.removeTable(tableId); + return Result.ok(); + } + + @GetMapping("/getHeaders") + @SaCheckPermission("/api/v1/datacenterTable/query") + public Result> getHeaders(BigInteger tableId) { + List res = service.getHeaders(tableId); + return Result.ok(res); + } + + @GetMapping("/getPageData") + @SaCheckPermission("/api/v1/datacenterTable/query") + public Result> getPageData(DatacenterQuery where) { + Page res = service.getPageData(where); + return Result.ok(res); + } + + @PostMapping("/saveValue") + @SaCheckPermission("/api/v1/datacenterTable/save") + public Result saveValue(@RequestParam Map map) { + JSONObject object = new JSONObject(map); + BigInteger tableId = object.getBigInteger("tableId"); + LoginAccount account = SaTokenUtil.getLoginAccount(); + if (tableId == null) { + return Result.fail(99, "参数错误"); + } + service.saveValue(tableId, object, account); + return Result.ok(); + } + + @PostMapping("/removeValue") + @SaCheckPermission("/api/v1/datacenterTable/remove") + public Result removeValue(@RequestParam Map map) { + JSONObject object = new JSONObject(map); + BigInteger tableId = object.getBigInteger("tableId"); + BigInteger id = object.getBigInteger("id"); + if (tableId == null || id == null) { + return Result.fail(99, "参数错误"); + } + LoginAccount account = SaTokenUtil.getLoginAccount(); + service.removeValue(tableId, id, account); + return Result.ok(); + } + + /** + * 导入数据 + */ + @PostMapping("/importData") + @SaCheckPermission("/api/v1/datacenterTable/save") + public Result importData(MultipartFile file, @RequestParam Map map) throws Exception { + Object tableId = map.get("tableId"); + DatacenterTable record = service.getById(tableId.toString()); + if (record == null) { + throw new RuntimeException("数据表不存在"); + } + InputStream is = file.getInputStream(); + List fields = service.getFields(record.getId()); + + LoginAccount account = SaTokenUtil.getLoginAccount(); + ReadDataListener listener = new ReadDataListener(record.getId(), fields, account); + FastExcel.read(is, listener) + .sheet() + .doRead(); + int totalCount = listener.getTotalCount(); + int errorCount = listener.getErrorCount(); + int successCount = listener.getSuccessCount(); + List errorRows = listener.getErrorRows(); + return Result.ok(new ReadResVo(successCount, errorCount, totalCount, errorRows)); + } + + @GetMapping("/getTemplate") + public void getTemplate(BigInteger tableId, HttpServletResponse response) throws Exception { + List fields = service.getFields(tableId); + // 设置响应内容类型 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + + // 设置文件名 + String fileName = URLEncoder.encode("导入模板", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + + // 动态表头数据 + List> headList = new ArrayList<>(); + + for (DatacenterTableField field : fields) { + List head = new ArrayList<>(); + head.add(field.getFieldName()); + headList.add(head); + } + + // 写入Excel + EasyExcel.write(response.getOutputStream()) + .head(headList) + .sheet("模板") + .doWrite(new ArrayList<>()); // 写入空数据,只生成模板 + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableFieldsController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableFieldsController.java new file mode 100644 index 0000000..28a23eb --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/datacenter/DatacenterTableFieldsController.java @@ -0,0 +1,41 @@ +package tech.easyflow.admin.controller.datacenter; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.service.DatacenterTableFieldService; + +import java.util.Date; + +/** + * 控制层。 + * + * @author ArkLight + * @since 2025-07-10 + */ +@RestController +@RequestMapping("/api/v1/datacenterTableFields") +@UsePermission(moduleName = "/api/v1/datacenterTable") +public class DatacenterTableFieldsController extends BaseCurdController { + + public DatacenterTableFieldsController(DatacenterTableFieldService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(DatacenterTableField entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobController.java new file mode 100644 index 0000000..17ea8a8 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobController.java @@ -0,0 +1,91 @@ +package tech.easyflow.admin.controller.job; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.date.DateUtil; +import org.quartz.CronExpression; +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.constant.enums.EnumJobStatus; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.service.SysJobService; + +import tech.easyflow.common.entity.LoginAccount; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 系统任务表 控制层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@RestController +@RequestMapping("/api/v1/sysJob") +public class SysJobController extends BaseCurdController { + public SysJobController(SysJobService service) { + super(service); + } + + @GetMapping("/start") + @SaCheckPermission("/api/v1/sysJob/save") + public Result start(BigInteger id) { + SysJob sysJob = service.getById(id); + sysJob.setStatus(EnumJobStatus.RUNNING.getCode()); + service.addJob(sysJob); + service.updateById(sysJob); + return Result.ok(); + } + + @GetMapping("/stop") + @SaCheckPermission("/api/v1/sysJob/save") + public Result stop(BigInteger id) { + SysJob sysJob = new SysJob(); + sysJob.setId(id); + sysJob.setStatus(EnumJobStatus.STOP.getCode()); + ArrayList ids = new ArrayList<>(); + ids.add(id); + service.deleteJob(ids); + service.updateById(sysJob); + return Result.ok(); + } + + @GetMapping("/getNextTimes") + public Result> getNextTimes(String cronExpression) throws Exception{ + CronExpression ex = new CronExpression(cronExpression); + List times = new ArrayList<>(); + Date date = new Date(); + for (int i = 0; i < 5; i++) { + Date next = ex.getNextValidTimeAfter(date); + times.add(DateUtil.formatDateTime(next)); + date = next; + } + return Result.ok(times); + } + + @Override + protected Result onSaveOrUpdateBefore(SysJob entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + service.deleteJob(ids); + return super.onRemoveBefore(ids); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobLogController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobLogController.java new file mode 100644 index 0000000..47880e5 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/job/SysJobLogController.java @@ -0,0 +1,35 @@ +package tech.easyflow.admin.controller.job; + +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.job.entity.SysJobLog; +import tech.easyflow.job.service.SysJobLogService; + +/** + * 系统任务日志 控制层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@RestController +@RequestMapping("/api/v1/sysJobLog") +@UsePermission(moduleName = "/api/v1/sysJob") +public class SysJobLogController extends BaseCurdController { + public SysJobLogController(SysJobLogService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(SysJobLog entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysAccountController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysAccountController.java new file mode 100644 index 0000000..e59f405 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysAccountController.java @@ -0,0 +1,159 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.constant.enums.EnumAccountType; +import tech.easyflow.common.constant.enums.EnumDataStatus; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.util.StringUtil; + +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.log.annotation.LogRecord; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.service.SysAccountService; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.crypto.digest.BCrypt; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 用户表 控制层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController("sysAccountController") +@RequestMapping("/api/v1/sysAccount") +public class SysAccountController extends BaseCurdController { + public SysAccountController(SysAccountService service) { + super(service); + } + + @Override + @LogRecord("分页查询") + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + return service.getMapper().paginateWithRelations(page, queryWrapper); + } + + @Override + protected Result onSaveOrUpdateBefore(SysAccount entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + BigInteger tenantId = loginUser.getTenantId(); + if (isSave) { + commonFiled(entity, loginUser.getId(), tenantId, loginUser.getDeptId()); + // 查询用户名是否存在 + // long count = Db.selectCount(SqlPrepare.COUNT_ACCOUNT_BY_UNI_KEY, entity.getLoginName(), tenantId); + QueryWrapper w = QueryWrapper.create(); + w.eq(SysAccount::getLoginName, entity.getLoginName()); + long count = service.count(w); + if (count > 0) { + return Result.fail(1, "用户名已存在"); + } + String password = entity.getPassword(); + if (StringUtil.hasText(password)) { + entity.setPassword(BCrypt.hashpw(password)); + } + Integer status = entity.getStatus(); + if (status == null) { + entity.setStatus(EnumDataStatus.AVAILABLE.getCode()); + } + } else { + SysAccount record = service.getById(entity.getId()); + // 如果修改了部门,就将用户踢下线,避免用户操作数据造成数据错误 + if (record.getDeptId() != null && !record.getDeptId().equals(entity.getDeptId())) { + StpUtil.kickout(record.getId()); + } + // 不让修改用户名/密码,浏览器记住密码有可能会带上来 + entity.setLoginName(null); + entity.setPassword(null); + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } + + @Override + protected void onSaveOrUpdateAfter(SysAccount entity, boolean isSave) { + service.syncRelations(entity); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + List sysAccounts = service.listByIds(ids); + for (SysAccount account : sysAccounts) { + Integer accountType = account.getAccountType(); + if (EnumAccountType.SUPER_ADMIN.getCode().equals(accountType)) { + return Result.fail(1, "不能删除超级管理员"); + } + if (EnumAccountType.TENANT_ADMIN.getCode().equals(accountType)) { + return Result.fail(1, "不能删除租户管理员"); + } + } + return super.onRemoveBefore(ids); + } + + @GetMapping("/myProfile") + public Result myProfile() { + LoginAccount account = SaTokenUtil.getLoginAccount(); + SysAccount sysAccount = service.getById(account.getId()); + return Result.ok(sysAccount); + } + + @PostMapping("/updateProfile") + public Result updateProfile(@JsonBody SysAccount account) { + BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId(); + SysAccount update = new SysAccount(); + update.setId(loginAccountId); + update.setNickname(account.getNickname()); + update.setMobile(account.getMobile()); + update.setEmail(account.getEmail()); + update.setAvatar(account.getAvatar()); + update.setModified(new Date()); + update.setModifiedBy(loginAccountId); + service.updateById(update); + return Result.ok(); + } + + /** + * 修改密码,用于修改用户自己的密码 + * + * @param password 用户的旧密码 + * @param newPassword 新密码 + * @param confirmPassword 确认密码 + */ + @PostMapping("/updatePassword") + public Result updatePassword(@JsonBody(value = "password", required = true) String password, + @JsonBody(value = "newPassword", required = true) String newPassword, + @JsonBody(value = "confirmPassword", required = true) String confirmPassword) { + BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId(); + SysAccount record = service.getById(loginAccountId); + if (record == null) { + return Result.fail("修改失败"); + } + String pwdDb = record.getPassword(); + if (!BCrypt.checkpw(password, pwdDb)) { + return Result.fail(1, "密码不正确"); + } + if (!newPassword.equals(confirmPassword)) { + return Result.fail(2, "两次密码不一致"); + } + SysAccount update = new SysAccount(); + update.setId(loginAccountId); + update.setPassword(BCrypt.hashpw(newPassword)); + update.setModified(new Date()); + update.setModifiedBy(loginAccountId); + service.updateById(update); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java new file mode 100644 index 0000000..f563df2 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java @@ -0,0 +1,101 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.table.TableInfo; +import com.mybatisflex.core.table.TableInfoFactory; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.util.IdUtil; +import tech.easyflow.common.vo.PkVo; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.SysApiKey; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; +import tech.easyflow.system.service.SysApiKeyResourceMappingService; +import tech.easyflow.system.service.SysApiKeyService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +/** + * 控制层。 + * + * @author wangGangQiang + * @since 2025-04-18 + */ +@RestController +@RequestMapping("/api/v1/sysApiKey") +public class SysApiKeyController extends BaseCurdController { + public SysApiKeyController(SysApiKeyService service) { + super(service); + } + + @Resource + private SysApiKeyResourceMappingService sysApiKeyResourceMappingService; + /** + * 添加(保存)数据 + * + * @return {@code Result.errorCode == 0} 添加成功,否则添加失败 + */ + @PostMapping("/key/save") + @SaCheckPermission("/api/v1/sysApiKey/save") + public Result save() { + String apiKey = IdUtil.generateUUID(); + SysApiKey entity = new SysApiKey(); + entity.setApiKey(apiKey); + entity.setCreated(new Date()); + entity.setStatus(1); + // 将Date转换为LocalDate + LocalDate localDate = new Date().toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate(); + + // 添加30天 + LocalDate newLocalDate = localDate.plusDays(30); + // 转换回Date + Date expireDate = Date.from(newLocalDate.atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant()); + entity.setExpiredAt(expireDate); + LoginAccount loginAccount = SaTokenUtil.getLoginAccount(); + commonFiled(entity,loginAccount.getId(),loginAccount.getTenantId(),loginAccount.getDeptId()); + service.save(entity); + onSaveOrUpdateAfter(entity, true); + TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass()); + Object[] pkArgs = tableInfo.buildPkSqlArgs(entity); + return Result.ok(new PkVo(pkArgs)); + } + + @Override + protected void onSaveOrUpdateAfter(SysApiKey entity, boolean isSave) { + if (!isSave && entity.getPermissionIds() != null && !entity.getPermissionIds().isEmpty()) { + // 修改的时候绑定授权接口 + sysApiKeyResourceMappingService.authInterface(entity); + } + } + + @Override + @GetMapping("/page") + public Result> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) { + Result> pageResult = (Result>) super.page(request, sortKey, sortType, pageNumber, pageSize); + Page data = pageResult.getData(); + List records = data.getRecords(); + records.forEach(record -> { + QueryWrapper queryWrapper = QueryWrapper.create().select(SysApiKeyResourceMapping::getApiKeyResourceId).eq(SysApiKeyResourceMapping::getApiKeyId, record.getId()); + List resourceIds = sysApiKeyResourceMappingService.listAs(queryWrapper, BigInteger.class); + record.setPermissionIds(resourceIds); + }); + return pageResult; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceController.java new file mode 100644 index 0000000..00caa48 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.system.entity.SysApiKeyResource; +import tech.easyflow.system.service.SysApiKeyResourceService; + +/** + * 请求接口表 控制层。 + * + * @author 12076 + * @since 2025-12-01 + */ +@RestController +@RequestMapping("/api/v1/sysApiKeyResourcePermission") +@UsePermission(moduleName = "/api/v1/sysApiKey") +public class SysApiKeyResourceController extends BaseCurdController { + public SysApiKeyResourceController(SysApiKeyResourceService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceMappingController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceMappingController.java new file mode 100644 index 0000000..e32813b --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyResourceMappingController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; +import tech.easyflow.system.service.SysApiKeyResourceMappingService; + +/** + * apikey-请求接口表 控制层。 + * + * @author 12076 + * @since 2025-12-01 + */ +@RestController +@RequestMapping("/api/v1/sysApiKeyResourcePermissionRelationship") +@UsePermission(moduleName = "/api/v1/sysApiKey") +public class SysApiKeyResourceMappingController extends BaseCurdController { + public SysApiKeyResourceMappingController(SysApiKeyResourceMappingService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDeptController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDeptController.java new file mode 100644 index 0000000..d008f8b --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDeptController.java @@ -0,0 +1,92 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.tree.Tree; + +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.entity.SysDept; +import tech.easyflow.system.service.SysAccountService; +import tech.easyflow.system.service.SysDeptService; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 部门表 控制层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController("sysDeptController") +@RequestMapping("/api/v1/sysDept") +public class SysDeptController extends BaseCurdController { + + @Resource + private SysAccountService sysAccountService; + + public SysDeptController(SysDeptService service) { + super(service); + } + + @Override + protected String getDefaultOrderBy() { + return "sort_no asc"; + } + + @Override + @GetMapping("list") + public Result> list(SysDept entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List sysMenus = service.list(queryWrapper); + return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId")); + } + + @Override + protected Result onSaveOrUpdateBefore(SysDept entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + BigInteger parentId = entity.getParentId(); + if (parentId.equals(BigInteger.ZERO)) { + entity.setAncestors(parentId.toString()); + } else { + SysDept parent = service.getById(parentId); + entity.setAncestors(parent.getAncestors() + "," + parentId); + } + if (isSave) { + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } + + @Override + protected Result onRemoveBefore(Collection ids) { + List records = service.listByIds(ids); + for (SysDept dept : records) { + if (Constants.ROOT_DEPT.equals(dept.getDeptCode())) { + return Result.fail(1, "无法删除根部门"); + } + } + QueryWrapper w = QueryWrapper.create(); + w.in(SysAccount::getDeptId, ids); + long count = sysAccountService.count(w); + if (count > 0) { + return Result.fail(1, "该部门下有员工,不能删除"); + } + return super.onRemoveBefore(ids); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictController.java new file mode 100644 index 0000000..35e1ae3 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictController.java @@ -0,0 +1,47 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.util.SpringContextUtil; + +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.dict.DictManager; +import tech.easyflow.system.entity.SysDict; +import tech.easyflow.system.service.SysDictService; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 系统配置表 控制层。 + * + * @author michael + * @since 2024-03-05 + */ +@RestController +@RequestMapping("/api/v1/sysDict") +public class SysDictController extends BaseCurdController { + + public SysDictController(SysDictService service) { + super(service); + } + + @Override + protected void onSaveOrUpdateAfter(SysDict entity, boolean isSave) { + DictManager dictManager = SpringContextUtil.getBean(DictManager.class); + dictManager.putLoader(entity.buildLoader()); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + List sysDicts = service.list(QueryWrapper.create().in("id", ids)); + if (sysDicts != null) { + DictManager dictManager = SpringContextUtil.getBean(DictManager.class); + sysDicts.forEach(sysDict -> dictManager.removeLoader(sysDict.getCode())); + } + return null; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictItemController.java new file mode 100644 index 0000000..0748401 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysDictItemController.java @@ -0,0 +1,23 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.SysDictItem; +import tech.easyflow.system.service.SysDictItemService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 数据字典内容 控制层。 + * + * @author michael + * @since 2024-03-06 + */ +@RestController +@RequestMapping("/api/v1/sysDictItem") +@UsePermission(moduleName = "/api/v1/sysDict") +public class SysDictItemController extends BaseCurdController { + public SysDictItemController(SysDictItemService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysGenerateTokenController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysGenerateTokenController.java new file mode 100644 index 0000000..73c630b --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysGenerateTokenController.java @@ -0,0 +1,27 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +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.util.IdUtil; + +@RestController +@RequestMapping("/api/v1/token") +public class SysGenerateTokenController { + // 手动生成 Token 并绑定账号 + @GetMapping("/generateToken") + public SaResult generateToken() { + long loginId = StpUtil.getLoginIdAsLong(); // 假设这是你要绑定的账号ID + String customToken = IdUtil.generateUUID();; // 自定义的 Token 字符串 + SaLoginModel saLoginModel = new SaLoginModel(); + saLoginModel.setToken(customToken); + saLoginModel.setTimeout(-1); + // 将 loginId 与 customToken 关联,并设置有效期(单位:秒) + StpUtil.createLoginSession(loginId, saLoginModel); // 24小时有效期 + System.out.println("生成了token: " + customToken); + return SaResult.ok("Token 已生成").setData(customToken); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysLogController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysLogController.java new file mode 100644 index 0000000..d5d1d1d --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysLogController.java @@ -0,0 +1,34 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.log.annotation.LogRecord; +import tech.easyflow.system.entity.SysLog; +import tech.easyflow.system.service.SysLogService; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.relation.RelationManager; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; + +/** + * 操作日志表 控制层。 + * + * @author michael + * @since 2024-03-06 + */ +@RestController +@RequestMapping("/api/v1/sysLog") +public class SysLogController extends BaseCurdController { + public SysLogController(SysLogService service) { + super(service); + } + + @Override + @LogRecord("分页查询") + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + RelationManager.setQueryRelations(Collections.singleton("account")); + return service.getMapper().paginateWithRelations(page, queryWrapper); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysMenuController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysMenuController.java new file mode 100644 index 0000000..316f9a7 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysMenuController.java @@ -0,0 +1,161 @@ +package tech.easyflow.admin.controller.system; + +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.tree.Tree; + +import tech.easyflow.common.vo.MenuVo; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.SysMenu; +import tech.easyflow.system.entity.SysRoleMenu; +import tech.easyflow.system.service.SysAccountRoleService; +import tech.easyflow.system.service.SysMenuService; +import tech.easyflow.system.service.SysRoleMenuService; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 菜单表 控制层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController("sysMenuController") +@RequestMapping("/api/v1/sysMenu") +public class SysMenuController extends BaseCurdController { + + @Resource + private SysRoleMenuService sysRoleMenuService; + @Resource + private SysAccountRoleService sysAccountRoleService; + + public SysMenuController(SysMenuService service) { + super(service); + } + + @Override + protected String getDefaultOrderBy() { + return "sort_no asc"; + } + + @Override + @GetMapping("list") + public Result> list(SysMenu entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + List sysMenus = service.list(queryWrapper); + return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId")); + } + + @GetMapping("tree") + public Result> tree(SysMenu entity) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + BigInteger accountId = account.getId(); + List sysMenus = service.getMenusByAccountId(entity,accountId); + return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId")); + } + + @GetMapping("treeV2") + public Result> treeV2(SysMenu entity) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + BigInteger accountId = account.getId(); + List sysMenus = service.getMenusByAccountId(entity,accountId); + List menuVos = buildMenuVos(sysMenus); + return Result.ok(Tree.tryToTree(menuVos, "id", "parentId")); + } + + /** + * 根据角色id获取菜单树 + */ + @GetMapping("getCheckedByRoleId/{roleId}") + public Result> getCheckedByRoleId(@PathVariable BigInteger roleId) { + QueryWrapper rmWrapper = QueryWrapper.create(); + rmWrapper.eq("role_id", roleId); + List list = sysRoleMenuService.list(rmWrapper); + List menuIds = list.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(menuIds)) { + return Result.ok(new ArrayList<>()); + } + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.in(SysMenu::getId,menuIds); + List sysMenus = service.list(wrapper); + return Result.ok(sysMenus.stream().map(SysMenu::getId).collect(Collectors.toList())); + } + + /** + * 保存后,给超级管理员角色添加权限 + */ + @Override + protected void onSaveOrUpdateAfter(SysMenu entity, boolean isSave) { + if (isSave) { + SysRoleMenu admin = new SysRoleMenu(); + admin.setRoleId(Constants.SUPER_ADMIN_ROLE_ID); + admin.setMenuId(entity.getId()); + sysRoleMenuService.save(admin); + } + } + + /** + * 删除后,删除角色菜单对应关系 + */ + @Override + protected void onRemoveAfter(Collection ids) { + QueryWrapper w = QueryWrapper.create(); + w.in(SysRoleMenu::getMenuId, ids); + sysRoleMenuService.remove(w); + } + + @Override + protected Result onSaveOrUpdateBefore(SysMenu entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } + + private List buildMenuVos(List sysMenus) { + List menuVos = new ArrayList<>(); + for (SysMenu sysMenu : sysMenus) { + if (sysMenu.getIsShow() != 1) { + continue; + } + if (sysMenu.getMenuType() == 1) { + continue; + } + MenuVo menuVo = new MenuVo(); + menuVo.setId(sysMenu.getId()); + menuVo.setParentId(sysMenu.getParentId()); + + MenuVo.MetaVo metaVo = new MenuVo.MetaVo(); + metaVo.setTitle(sysMenu.getMenuTitle()); + metaVo.setIcon(sysMenu.getMenuIcon()); + metaVo.setOrder(sysMenu.getSortNo()); + + menuVo.setMeta(metaVo); + menuVo.setName(sysMenu.getId().toString()); + menuVo.setPath(sysMenu.getMenuUrl()); + menuVo.setComponent(sysMenu.getComponent()); + menuVos.add(menuVo); + } + return menuVos; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysOptionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysOptionController.java new file mode 100644 index 0000000..2e93f10 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysOptionController.java @@ -0,0 +1,84 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.options.SysOptions; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.SysOption; +import tech.easyflow.system.service.SysOptionService; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 系统配置信息表。 控制层。 + * + * @author michael + * @since 2024-03-13 + */ +@RestController +@RequestMapping("/api/v1/sysOption") +public class SysOptionController extends BaseController { + + @Resource + private SysOptionService service; + + @GetMapping("/list") + public Result> list(String[] keys) { + Map data = new HashMap<>(); + if (keys == null || keys.length == 0) { + return Result.ok(data); + } + List list = service.list(QueryWrapper.create().in(SysOption::getKey, (Object[]) keys)); + for (SysOption sysOption : list) { + data.put(sysOption.getKey(), sysOption.getValue()); + } + return Result.ok(data); + } + + @PostMapping("/save") + public Result save(@JsonBody Map map) { + if (map == null || map.isEmpty()) { + return Result.ok(); + } + map.forEach(SysOptions::set); + return Result.ok(); + } + + @PostMapping("/saveOption") + @SaCheckPermission("/api/v1/sysOption/save") + public Result saveOption(@JsonBody SysOption sysOption) { + String key = sysOption.getKey(); + if (key == null || key.isEmpty()) { + throw new BusinessException("key is empty"); + } + sysOption.setTenantId(SaTokenUtil.getLoginAccount().getTenantId()); + SysOption record = service.getByOptionKey(key); + if (record == null) { + service.save(sysOption); + } else { + QueryWrapper w = QueryWrapper.create(); + w.eq(SysOption::getKey, key); + service.update(sysOption, w); + } + return Result.ok(); + } + + @GetMapping("/getByKey") + public Result getByKey(String key) { + if (key == null || key.isEmpty()) { + throw new BusinessException("key is empty"); + } + return Result.ok(service.getByOptionKey(key)); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysPositionController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysPositionController.java new file mode 100644 index 0000000..3f09f70 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysPositionController.java @@ -0,0 +1,132 @@ +package tech.easyflow.admin.controller.system; + +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.util.StringUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.SysPosition; +import tech.easyflow.system.service.SysPositionService; + +import java.util.Date; +import java.util.Map; + +import static tech.easyflow.system.entity.table.SysPositionTableDef.SYS_POSITION; + +/** + * 职位表 控制层。 + *

+ * 提供岗位的增删改查及状态管理功能。 + *

+ * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController("sysPositionController") +@RequestMapping("/api/v1/sysPosition") +public class SysPositionController extends BaseCurdController { + public SysPositionController(SysPositionService service) { + super(service); + } + + /** + * 分页查询岗位列表 + *

+ * 支持按岗位名称模糊查询,按状态、编码精确查询。 + *

+ * + * @param request 请求对象 + * @param sortKey 排序字段 + * @param sortType 排序类型 (asc/desc) + * @param pageNumber 当前页码 + * @param pageSize 每页条数 + * @return 分页结果 + */ + @Override + @GetMapping("page") + public Result> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) { + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1L; + } + if (pageSize == null || pageSize < 1) { + pageSize = 10L; + } + + // 构建自定义查询条件 + QueryWrapper queryWrapper = QueryWrapper.create() + .select(SYS_POSITION.ALL_COLUMNS) + .from(SYS_POSITION); + + // 获取查询参数 + String positionName = request.getParameter("positionName"); + String positionCode = request.getParameter("positionCode"); + String status = request.getParameter("status"); + + // 岗位名称 - 模糊查询 + if (StringUtil.hasText(positionName)) { + queryWrapper.where(SYS_POSITION.POSITION_NAME.like(positionName)); + } + // 岗位编码 - 精确查询 + if (StringUtil.hasText(positionCode)) { + queryWrapper.where(SYS_POSITION.POSITION_CODE.eq(positionCode)); + } + // 状态 - 精确查询 + if (StringUtil.hasText(status)) { + queryWrapper.where(SYS_POSITION.STATUS.eq(status)); + } + + // 处理排序 + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + + return Result.ok(service.page(new Page<>(pageNumber, pageSize), queryWrapper)); + } + + /** + * 修改岗位状态(启用/禁用) + * + * @param body 包含 id 和 status 的 JSON 对象 + * @return 操作结果 + */ + @PostMapping("changeStatus") + public Result changeStatus(@RequestBody Map body) { + String idStr = (String) body.get("id"); + Integer status = (Integer) body.get("status"); + + if (StringUtil.noText(idStr) || status == null) { + return Result.fail("参数错误:id 和 status 不能为空"); + } + + SysPosition position = new SysPosition(); + position.setId(new java.math.BigInteger(idStr)); + position.setStatus(status); + + // 设置修改信息 + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + position.setModified(new Date()); + position.setModifiedBy(loginUser.getId()); + + boolean success = service.updateById(position); + return success ? Result.ok() : Result.fail("状态修改失败"); + } + + @Override + protected Result onSaveOrUpdateBefore(SysPosition entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), entity.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java new file mode 100644 index 0000000..41553d8 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java @@ -0,0 +1,118 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.SysRole; +import tech.easyflow.system.entity.SysRoleDept; +import tech.easyflow.system.entity.SysRoleMenu; +import tech.easyflow.system.service.SysRoleDeptService; +import tech.easyflow.system.service.SysRoleMenuService; +import tech.easyflow.system.service.SysRoleService; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统角色 控制层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController("sysRoleController") +@RequestMapping("/api/v1/sysRole/") +public class SysRoleController extends BaseCurdController { + + @Resource + private SysRoleMenuService sysRoleMenuService; + @Resource + private SysRoleDeptService sysRoleDeptService; + + public SysRoleController(SysRoleService service) { + super(service); + } + + @PostMapping("saveRoleMenu/{roleId}") + @SaCheckPermission("/api/v1/sysRole/save") + @Deprecated + public Result saveRoleMenu(@PathVariable("roleId") BigInteger roleId, @JsonBody List keys) { + service.saveRoleMenu(roleId, keys); + return Result.ok(); + } + + /** + * 获取角色菜单id + */ + @GetMapping("/getRoleMenuIds") + @SaCheckPermission("/api/v1/sysRole/query") + public Result> getRoleMenuIds(BigInteger roleId) { + QueryWrapper w = QueryWrapper.create(); + w.eq("role_id", roleId); + List res = sysRoleMenuService.list(w).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList()); + return Result.ok(res); + } + + /** + * 获取角色部门id + */ + @GetMapping("/getRoleDeptIds") + @SaCheckPermission("/api/v1/sysRole/query") + public Result> getRoleDeptIds(BigInteger roleId) { + QueryWrapper w = QueryWrapper.create(); + w.eq("role_id", roleId); + List res = sysRoleDeptService.list(w).stream().map(SysRoleDept::getDeptId).collect(Collectors.toList()); + return Result.ok(res); + } + + /** + * 保存角色 + */ + @PostMapping("saveRole") + @SaCheckPermission("/api/v1/sysRole/save") + public Result saveRole(@JsonBody SysRole entity) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (entity.getId() == null) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); + } + service.saveRole(entity); + return Result.ok(); + } + + @Override + protected Result onSaveOrUpdateBefore(SysRole entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return null; + } + + @Override + protected Result onRemoveBefore(Collection ids) { + List sysRoles = service.listByIds(ids); + for (SysRole sysRole : sysRoles) { + String roleKey = sysRole.getRoleKey(); + if (Constants.SUPER_ADMIN_ROLE_CODE.equals(roleKey)) { + return Result.fail(1, "超级管理员角色不能删除"); + } + if (Constants.TENANT_ADMIN_ROLE_CODE.equals(roleKey)) { + return Result.fail(1, "租户管理员角色不能删除"); + } + } + return super.onRemoveBefore(ids); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysTempTokenController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysTempTokenController.java new file mode 100644 index 0000000..e96b1bc --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysTempTokenController.java @@ -0,0 +1,31 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.stp.StpUtil; +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.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; + +import java.math.BigInteger; + +@RestController +@RequestMapping("/api/temp-token") +public class SysTempTokenController { + + @GetMapping("/create") + @SaIgnore + public Result createTempToken() { + + StpUtil.login(0); + String tokenValue = StpUtil.getTokenValue(); + LoginAccount loginAccount = new LoginAccount(); + loginAccount.setId(BigInteger.valueOf(0)); + loginAccount.setLoginName("匿名用户"); + StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount); + + return Result.ok("", tokenValue); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysUserFeedbackController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysUserFeedbackController.java new file mode 100644 index 0000000..b371d6c --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysUserFeedbackController.java @@ -0,0 +1,36 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.SysUserFeedback; +import tech.easyflow.system.service.SysUserFeedbackService; + +import java.math.BigInteger; +import java.util.Date; + +/** + * 控制层。 + * + * @author 12076 + * @since 2025-12-30 + */ +@RestController +@RequestMapping("/api/v1/sysUserFeedback") +public class SysUserFeedbackController extends BaseCurdController { + public SysUserFeedbackController(SysUserFeedbackService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(SysUserFeedback entity, boolean isSave) { + if (!isSave) { + entity.setHandlerId(new BigInteger(StpUtil.getLoginIdAsString())); + entity.setModified(new Date()); + entity.setHandleTime(new Date()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-mcp/pom.xml b/easyflow-api/easyflow-api-mcp/pom.xml new file mode 100644 index 0000000..b7911a6 --- /dev/null +++ b/easyflow-api/easyflow-api-mcp/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + tech.easyflow + easyflow-api + ${revision} + + + easyflow-api-mcp + + \ No newline at end of file diff --git a/easyflow-api/easyflow-api-public/pom.xml b/easyflow-api/easyflow-api-public/pom.xml new file mode 100644 index 0000000..727dad5 --- /dev/null +++ b/easyflow-api/easyflow-api-public/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + tech.easyflow + easyflow-api + ${revision} + + + easyflow-api-public + + + + tech.easyflow + easyflow-module-ai + + + tech.easyflow + easyflow-module-system + + + org.springframework.boot + spring-boot-starter-validation + + + com.github.javaparser + javaparser-core + 3.25.8 + + + com.mysql + mysql-connector-j + + + \ No newline at end of file diff --git a/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/SyncApis.java b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/SyncApis.java new file mode 100644 index 0000000..ebe7c14 --- /dev/null +++ b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/SyncApis.java @@ -0,0 +1,188 @@ +package tech.easyflow.publicapi; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.expr.MemberValuePair; +import com.github.javaparser.ast.expr.NormalAnnotationExpr; +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; +import com.mybatisflex.core.MybatisFlexBootstrap; +import com.mybatisflex.core.query.QueryWrapper; +import com.zaxxer.hikari.HikariDataSource; +import tech.easyflow.system.entity.SysApiKeyResource; +import tech.easyflow.system.mapper.SysApiKeyResourceMapper; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 同步接口到数据库 + */ +public class SyncApis { + + public static void main(String[] args) throws Exception { + + try (HikariDataSource dataSource = new HikariDataSource()) { + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.10:3306/easyflow-v2?useInformationSchema=true&characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + + MybatisFlexBootstrap bootstrap = MybatisFlexBootstrap.getInstance(); + bootstrap.setDataSource(dataSource); + bootstrap.addMapper(SysApiKeyResourceMapper.class); + bootstrap.start(); + + SysApiKeyResourceMapper mapper = bootstrap.getMapper(SysApiKeyResourceMapper.class); + String dir = System.getProperty("user.dir") + "/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller"; + List filePath = getAllFilePaths(dir); + for (String path : filePath) { + extractCommentsFromFile(path, mapper); + } + } + } + + public static List getAllFilePaths(String directoryPath) throws IOException { + Path startPath = Paths.get(directoryPath); + + try (Stream stream = Files.walk(startPath)) { + return stream + .filter(Files::isRegularFile) // 只获取文件,排除目录 + .map(Path::toAbsolutePath) // 转换为绝对路径 + .map(Path::toString) // 转换为字符串 + .collect(Collectors.toList()); + } + } + + public static void extractCommentsFromFile(String filePath, SysApiKeyResourceMapper mapper) throws Exception { + System.out.println("正在解析文件: " + filePath); + FileInputStream in = new FileInputStream(filePath); + com.github.javaparser.JavaParser parser = new com.github.javaparser.JavaParser(); + parser.getParserConfiguration().setLanguageLevel(com.github.javaparser.ParserConfiguration.LanguageLevel.JAVA_17); + CompilationUnit cu = parser.parse(in).getResult().orElseThrow(); + + cu.findAll(ClassOrInterfaceDeclaration.class).forEach(c -> { + // 1. 获取类级别的 RequestMapping 路径 + String classPath = ""; + Optional classMapping = c.getAnnotationByName("RequestMapping"); + if (classMapping.isPresent()) { + classPath = getAnnotationValue(classMapping.get()); + } + + String finalClassPath = classPath; //用于lambda中使用 + String className = c.getNameAsString(); + String classComment = c.getJavadoc().map(d -> d.getDescription().toText()).orElse(""); + + System.out.println("========================================="); + System.out.println("类名: " + className); + System.out.println("类注释: " + classComment); + System.out.println("类路径: " + finalClassPath); + + // 2. 遍历方法 + c.getMethods().forEach(method -> { + // 查找常见的 Mapping 注解 + String[] mappingTypes = {"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"}; + + for (String mappingType : mappingTypes) { + Optional methodMapping = method.getAnnotationByName(mappingType); + if (methodMapping.isPresent()) { + // 获取方法上的路径 + String methodPath = getAnnotationValue(methodMapping.get()); + + // 拼接完整 URI + String fullUri = combinePaths(finalClassPath, methodPath); + + // 获取请求方式 (如果是 RequestMapping,通常默认为 All 或者需要进一步解析 method 属性,这里简单处理) + String httpMethod = mappingType.replace("Mapping", "").toUpperCase(); + if (httpMethod.equals("REQUEST")) httpMethod = "ALL"; + + // 获取方法注释 + String methodComment = method.getJavadoc().map(doc -> doc.getDescription().toText()).orElse(""); + + System.out.println("--------------------------------"); + System.out.println(" 方法名: " + method.getNameAsString()); + System.out.println(" 类型: " + httpMethod); + System.out.println(" 完整URI: " + fullUri); + System.out.println(" 方法注释: " + methodComment); + + // 可以在这里调用 mapper 存入数据库 + QueryWrapper w = QueryWrapper.create(); + w.eq(SysApiKeyResource::getRequestInterface, fullUri); + SysApiKeyResource record = mapper.selectOneByQuery(w); + if (record != null) { + record.setTitle(methodComment); + mapper.insertOrUpdate(record); + } else { + record = new SysApiKeyResource(); + record.setRequestInterface(fullUri); + record.setTitle(methodComment); + mapper.insert(record); + } + } + } + }); + }); + } + + /** + * 解析注解中的 value 或 path 值 + * 处理几种情况: + * 1. @GetMapping("/api") -> SingleMemberAnnotationExpr + * 2. @GetMapping(value = "/api") -> NormalAnnotationExpr + * 3. @GetMapping(path = "/api") -> NormalAnnotationExpr + * 4. @GetMapping -> 默认为空字符串 + */ + private static String getAnnotationValue(AnnotationExpr annotation) { + // 情况 1: @GetMapping("/path") + if (annotation instanceof SingleMemberAnnotationExpr) { + String value = ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString(); + return removeQuotes(value); + } + // 情况 2: @GetMapping(value="/path") 或 @GetMapping(path="/path") + else if (annotation instanceof NormalAnnotationExpr) { + NormalAnnotationExpr normal = (NormalAnnotationExpr) annotation; + for (MemberValuePair pair : normal.getPairs()) { + String key = pair.getNameAsString(); + if ("value".equals(key) || "path".equals(key)) { + return removeQuotes(pair.getValue().toString()); + } + } + } + // 情况 3: @GetMapping (没有参数) + return ""; + } + + /** + * 去除 JavaParser 解析出的字符串中的双引号 + */ + private static String removeQuotes(String value) { + if (value == null) return ""; + return value.replace("\"", "").trim(); + } + + /** + * 拼接类路径和方法路径,处理斜杠 + */ + private static String combinePaths(String classPath, String methodPath) { + if (classPath == null) classPath = ""; + if (methodPath == null) methodPath = ""; + + // 确保以 / 开头 + if (!classPath.startsWith("/") && !classPath.isEmpty()) classPath = "/" + classPath; + if (!methodPath.startsWith("/") && !methodPath.isEmpty()) methodPath = "/" + methodPath; + + // 如果 classPath 只有 /,去掉它,避免 //method + if (classPath.equals("/")) classPath = ""; + + String full = classPath + methodPath; + // 处理重复斜杠 (例如 class=/api/ method=/list -> /api//list) + return full.replace("//", "/"); + } +} diff --git a/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicBotController.java b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicBotController.java new file mode 100644 index 0000000..cfc82cb --- /dev/null +++ b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicBotController.java @@ -0,0 +1,69 @@ +package tech.easyflow.publicapi.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import com.easyagents.core.message.UserMessage; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotBlank; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.entity.Bot; +import tech.easyflow.ai.entity.ChatRequestParams; +import tech.easyflow.ai.service.BotService; +import tech.easyflow.ai.service.impl.BotServiceImpl; +import tech.easyflow.common.domain.Result; +import tech.easyflow.core.chat.protocol.sse.ChatSseUtil; +import tech.easyflow.system.entity.SysApiKey; +import tech.easyflow.system.service.SysApiKeyService; + +import javax.annotation.Resource; + +/** + * bot 接口 + */ +@RestController +@RequestMapping("/public-api/bot") +public class PublicBotController { + + @Resource + private BotService botService; + @Resource + private SysApiKeyService sysApiKeyService; + + /** + * 根据id或别名获取bot详情 + */ + @GetMapping("/getByIdOrAlias") + public Result getByIdOrAlias(@NotBlank(message = "key不能为空") String key) { + return Result.ok(botService.getDetail(key)); + } + + /** + * 第三方调用聊天助手 + * + * @return 返回SseEmitter对象,用于服务器向客户端推送聊天响应数据 + */ + @PostMapping("chat") + public SseEmitter chat(@RequestBody ChatRequestParams chatRequestParams, HttpServletRequest request) { + String apikey = request.getHeader(SysApiKey.KEY_Apikey); + String requestURI = request.getRequestURI(); + if (!StringUtils.hasText(apikey)) { + return ChatSseUtil.sendSystemError(null, "Apikey不能为空!"); + } + sysApiKeyService.checkApikeyPermission(apikey, requestURI); + BotServiceImpl.ChatCheckResult chatCheckResult = new BotServiceImpl.ChatCheckResult(); + int size = chatRequestParams.getMessages().size(); + String prompt = null; + if (chatRequestParams.getMessages().get(size - 1) instanceof UserMessage) { + prompt = ((UserMessage) chatRequestParams.getMessages().get(size - 1)).getContent(); + } + // 前置校验:失败则直接返回错误SseEmitter + SseEmitter errorEmitter = botService.checkChatBeforeStart(chatRequestParams.getBotId(), prompt, chatRequestParams.getConversationId(), chatCheckResult); + if (errorEmitter != null) { + return errorEmitter; + } + return botService.startPublicChat(chatRequestParams.getBotId(), prompt, chatRequestParams.getMessages(), chatCheckResult); + } + + +} diff --git a/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicWorkflowController.java b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicWorkflowController.java new file mode 100644 index 0000000..f8ebe96 --- /dev/null +++ b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/controller/PublicWorkflowController.java @@ -0,0 +1,132 @@ +package tech.easyflow.publicapi.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.Parameter; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import com.easyagents.flow.core.parser.ChainParser; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotBlank; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.ai.easyagentsflow.entity.ChainInfo; +import tech.easyflow.ai.easyagentsflow.entity.NodeInfo; +import tech.easyflow.ai.easyagentsflow.service.TinyFlowService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 工作流 + */ +@RequestMapping("/public-api/workflow") +@RestController +public class PublicWorkflowController { + + @Resource + private WorkflowService workflowService; + @Resource + private ChainExecutor chainExecutor; + @Resource + private ChainParser chainParser; + @Resource + private TinyFlowService tinyFlowService; + + /** + * 通过id或别名获取工作流详情 + * + * @param key id或者别名 + * @return 工作流详情 + */ + @GetMapping(value = "/getByIdOrAlias") + public Result getByIdOrAlias( + @RequestParam + @NotBlank(message = "key不能为空") String key) { + Workflow workflow = workflowService.getDetail(key); + return Result.ok(workflow); + } + + /** + * 节点单独运行 + */ + @PostMapping("/singleRun") + @SaCheckPermission("/api/v1/workflow/save") + public Result singleRun( + @JsonBody(value = "workflowId", required = true) BigInteger workflowId, + @JsonBody(value = "nodeId", required = true) String nodeId, + @JsonBody("variables") Map variables) { + + Workflow workflow = workflowService.getById(workflowId); + if (workflow == null) { + return Result.fail(1, "工作流不存在"); + } + Map res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables); + return Result.ok(res); + } + + /** + * 运行工作流 - v2 + */ + @PostMapping("/runAsync") + @SaCheckPermission("/api/v1/workflow/save") + public Result runAsync(@JsonBody(value = "id", required = true) BigInteger id, + @JsonBody("variables") Map variables) { + if (variables == null) { + variables = new HashMap<>(); + } + Workflow workflow = workflowService.getById(id); + if (workflow == null) { + throw new RuntimeException("工作流不存在"); + } + String executeId = chainExecutor.executeAsync(id.toString(), variables); + return Result.ok(executeId); + } + + /** + * 获取工作流运行状态 - v2 + */ + @PostMapping("/getChainStatus") + public Result getChainStatus(@JsonBody(value = "executeId") String executeId, + @JsonBody("nodes") List nodes) { + ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes); + return Result.ok(res); + } + + /** + * 恢复工作流运行 - v2 + */ + @PostMapping("/resume") + @SaCheckPermission("/api/v1/workflow/save") + public Result resume(@JsonBody(value = "executeId", required = true) String executeId, + @JsonBody("confirmParams") Map confirmParams) { + chainExecutor.resumeAsync(executeId, confirmParams); + return Result.ok(); + } + + @GetMapping("getRunningParameters") + @SaCheckPermission("/api/v1/workflow/query") + public Result getRunningParameters(@RequestParam BigInteger id) { + Workflow workflow = workflowService.getById(id); + + if (workflow == null) { + return Result.fail(1, "can not find the workflow by id: " + id); + } + + ChainDefinition definition = chainParser.parse(workflow.getContent()); + if (definition == null) { + return Result.fail(2, "节点配置错误,请检查! "); + } + List chainParameters = definition.getStartParameters(); + Map res = new HashMap<>(); + res.put("parameters", chainParameters); + res.put("title", workflow.getTitle()); + res.put("description", workflow.getDescription()); + res.put("icon", workflow.getIcon()); + return Result.ok(res); + } +} diff --git a/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiConfig.java b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiConfig.java new file mode 100644 index 0000000..0b5e2d0 --- /dev/null +++ b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiConfig.java @@ -0,0 +1,22 @@ +package tech.easyflow.publicapi.interceptor; + +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 PublicApiConfig implements WebMvcConfigurer { + + @Resource + private PublicApiInterceptor publicApiInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + registry.addInterceptor(publicApiInterceptor) + .addPathPatterns("/public-api/**") + .excludePathPatterns("/public-api/bot/chat") + ; + } +} diff --git a/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiInterceptor.java b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiInterceptor.java new file mode 100644 index 0000000..14eab88 --- /dev/null +++ b/easyflow-api/easyflow-api-public/src/main/java/tech/easyflow/publicapi/interceptor/PublicApiInterceptor.java @@ -0,0 +1,36 @@ +package tech.easyflow.publicapi.interceptor; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.util.ResponseUtil; +import tech.easyflow.system.service.SysApiKeyService; + +@Component +public class PublicApiInterceptor implements HandlerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(PublicApiInterceptor.class); + + @Resource + private SysApiKeyService sysApiKeyService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + String requestURI = request.getRequestURI(); + String apiKey = request.getHeader("ApiKey"); + + if (apiKey == null || apiKey.isEmpty()) { + Result failed = Result.fail(401, "密钥不正确"); + ResponseUtil.renderJson(response, failed); + return false; + } + sysApiKeyService.checkApikeyPermission(apiKey, requestURI); + return true; + } +} diff --git a/easyflow-api/easyflow-api-usercenter/pom.xml b/easyflow-api/easyflow-api-usercenter/pom.xml new file mode 100644 index 0000000..34eb1c9 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + tech.easyflow + easyflow-api + ${revision} + + + easyflow-api-usercenter + + + + tech.easyflow + easyflow-module-auth + + + tech.easyflow + easyflow-module-ai + + + tech.easyflow + easyflow-common-captcha + + + + \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotCategoryController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotCategoryController.java new file mode 100644 index 0000000..c34068f --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotCategoryController.java @@ -0,0 +1,23 @@ +package tech.easyflow.usercenter.controller.ai; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotCategory; +import tech.easyflow.ai.service.BotCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.web.controller.BaseCurdController; + +/** + * bot分类 控制层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@RestController +@RequestMapping("/userCenter/botCategory") +@UsePermission(moduleName = "/api/v1/bot") +public class UcBotCategoryController extends BaseCurdController { + public UcBotCategoryController(BotCategoryService service) { + super(service); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java new file mode 100644 index 0000000..16988d6 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java @@ -0,0 +1,276 @@ + +package tech.easyflow.usercenter.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaIgnore; +import com.alicp.jetcache.Cache; +import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.entity.*; +import tech.easyflow.ai.service.*; +import tech.easyflow.ai.service.impl.BotServiceImpl; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.audio.core.AudioServiceManager; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 控制层。 + * + * @author michael + * @since 2024-08-23 + */ +@RestController +@RequestMapping("/userCenter/bot") +@UsePermission(moduleName = "/api/v1/bot") +public class UcBotController extends BaseCurdController { + + private final ModelService modelService; + private final BotWorkflowService botWorkflowService; + private final BotDocumentCollectionService botDocumentCollectionService; + @Resource + private BotService botService; + @Autowired + @Qualifier("defaultCache") // 指定 Bean 名称 + private Cache cache; + @Resource + private AudioServiceManager audioServiceManager; + + public UcBotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService, + BotDocumentCollectionService botDocumentCollectionService) { + super(service); + this.modelService = modelService; + this.botWorkflowService = botWorkflowService; + this.botDocumentCollectionService = botDocumentCollectionService; + } + + @Resource + private BotPluginService botPluginService; + @Resource + private BotConversationService conversationMessageService; + + @GetMapping("/generateConversationId") + public Result generateConversationId() { + long nextId = new SnowFlakeIDKeyGenerator().nextId(); + return Result.ok(nextId); + } + + @PostMapping("updateOptions") + @SaCheckPermission("/api/v1/bot/save") + public Result updateOptions(@JsonBody("id") BigInteger id, + @JsonBody("options") Map options) { + Bot aiBot = service.getById(id); + Map existOptions = aiBot.getOptions(); + if (existOptions == null) { + existOptions = new HashMap<>(); + } + if (options != null) { + existOptions.putAll(options); + } + aiBot.setOptions(existOptions); + service.updateById(aiBot); + return Result.ok(); + } + + @PostMapping("updateLlmOptions") + @SaCheckPermission("/api/v1/bot/save") + public Result updateLlmOptions(@JsonBody("id") + BigInteger id, @JsonBody("llmOptions") + Map llmOptions) { + Bot aiBot = service.getById(id); + Map existLlmOptions = aiBot.getModelOptions(); + if (existLlmOptions == null) { + existLlmOptions = new HashMap<>(); + } + if (llmOptions != null) { + existLlmOptions.putAll(llmOptions); + } + aiBot.setModelOptions(existLlmOptions); + service.updateById(aiBot); + return Result.ok(); + } + + @PostMapping("voiceInput") + @SaIgnore + public Result voiceInput(@RequestParam("audio") + MultipartFile audioFile) { + + String recognize = null; + try { + recognize = audioServiceManager.audioToText(audioFile.getInputStream()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return Result.ok("", recognize); + } + + /** + * 处理聊天请求的接口方法 + * + * @param prompt 用户输入的聊天内容,必须提供 + * @param botId 聊天机器人的唯一标识符,必须提供 + * @param conversationId 会话ID,用于标识当前对话会话,必须提供 + * @param messages 历史消息,用于提供上下文,可选 + * @return 返回SseEmitter对象,用于服务器向客户端推送聊天响应数据 + */ + @PostMapping("chat") + @SaIgnore + public SseEmitter chat( + @JsonBody(value = "prompt", required = true) String prompt, + @JsonBody(value = "botId", required = true) BigInteger botId, + @JsonBody(value = "conversationId", required = true) BigInteger conversationId, + @JsonBody(value = "messages") List> messages, + @JsonBody(value = "attachments") List attachments + + ) { + BotServiceImpl.ChatCheckResult chatCheckResult = new BotServiceImpl.ChatCheckResult(); + + // 前置校验:失败则直接返回错误SseEmitter + SseEmitter errorEmitter = botService.checkChatBeforeStart(botId, prompt, conversationId.toString(), chatCheckResult); + if (errorEmitter != null) { + return errorEmitter; + } + BotConversation conversation = conversationMessageService.getById(conversationId); + if (conversation == null) { + conversation = new BotConversation(); + conversation.setId(conversationId); + if (prompt.length() > 200) { + conversation.setTitle(prompt.substring(0, 200)); + } else { + conversation.setTitle(prompt); + } + conversation.setBotId(botId); + conversation.setAccountId(SaTokenUtil.getLoginAccount().getId()); + commonFiled(conversation, SaTokenUtil.getLoginAccount().getId(), SaTokenUtil.getLoginAccount().getTenantId(), SaTokenUtil.getLoginAccount().getDeptId()); + conversationMessageService.save(conversation); + } + + return botService.startChat(botId, prompt, conversationId, messages, chatCheckResult, attachments); + + } + + @PostMapping("updateLlmId") + @SaCheckPermission("/api/v1/bot/save") + public Result updateBotLlmId(@RequestBody + Bot aiBot) { + service.updateBotLlmId(aiBot); + return Result.ok(); + } + + @GetMapping("getDetail") + @SaIgnore + public Result getDetail(String id) { + return Result.ok(botService.getDetail(id)); + } + + @Override + @SaIgnore + public Result detail(String id) { + Bot data = botService.getDetail(id); + if (data == null) { + return Result.ok(data); + } + + Map llmOptions = data.getModelOptions(); + if (llmOptions == null) { + llmOptions = new HashMap<>(); + } + + if (data.getModelId() == null) { + return Result.ok(data); + } + + BigInteger llmId = data.getModelId(); + Model llm = modelService.getById(llmId); + + if (llm == null) { + data.setModelId(null); + return Result.ok(data); + } + + Map options = llm.getOptions(); + + if (options != null && !options.isEmpty()) { + + // 获取是否多模态 + Boolean multimodal = (Boolean) options.get("multimodal"); + llmOptions.put("multimodal", multimodal != null && multimodal); + + } + + return Result.ok(data); + } + + @Override + protected Result onSaveOrUpdateBefore(Bot entity, boolean isSave) { + + String alias = entity.getAlias(); + + if (StringUtils.hasLength(alias)) { + Bot aiBot = service.getByAlias(alias); + + if (aiBot != null && isSave) { + throw new BusinessException("别名已存在!"); + } + + if (aiBot != null && aiBot.getId().compareTo(entity.getId()) != 0) { + throw new BusinessException("别名已存在!"); + } + + } + + + if (isSave) { + // 设置默认值 + entity.setModelOptions(getDefaultLlmOptions()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + private Map getDefaultLlmOptions() { + Map defaultLlmOptions = new HashMap<>(); + defaultLlmOptions.put("temperature", 0.7); + defaultLlmOptions.put("topK", 4); + defaultLlmOptions.put("maxReplyLength", 2048); + defaultLlmOptions.put("topP", 0.7); + defaultLlmOptions.put("maxMessageCount", 10); + return defaultLlmOptions; + } + + private Map errorRespnseMsg(int errorCode, String message) { + HashMap result = new HashMap<>(); + result.put("error", errorCode); + result.put("message", message); + return result; + } + + @Override + protected Result onRemoveBefore(Collection ids) { + QueryWrapper queryWrapperKnowledge = QueryWrapper.create().in(BotDocumentCollection::getBotId, ids); + botDocumentCollectionService.remove(queryWrapperKnowledge); + QueryWrapper queryWrapperBotWorkflow = QueryWrapper.create().in(BotWorkflow::getBotId, ids); + botWorkflowService.remove(queryWrapperBotWorkflow); + QueryWrapper queryWrapperBotPlugins = QueryWrapper.create().in(BotPlugin::getBotId, ids); + botPluginService.remove(queryWrapperBotPlugins); + return super.onRemoveBefore(ids); + } + +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotConversationController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotConversationController.java new file mode 100644 index 0000000..7e7e7fe --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotConversationController.java @@ -0,0 +1,108 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.dev33.satoken.annotation.SaIgnore; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotConversation; +import tech.easyflow.ai.service.BotConversationService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@RestController +@RequestMapping("/userCenter/botConversation") +@SaIgnore +public class UcBotConversationController extends BaseCurdController { + + @Resource + private BotConversationService conversationMessageService; + + public UcBotConversationController(BotConversationService service) { + super(service); + } + + /** + * 删除指定会话 + */ + @GetMapping("/deleteConversation") + public Result deleteConversation(String botId, String conversationId) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + conversationMessageService.deleteConversation(botId, conversationId, account.getId()); + return Result.ok(); + } + + /** + * 更新会话标题 + */ + @GetMapping("/updateConversation") + public Result updateConversation(String botId, String conversationId, String title) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + conversationMessageService.updateConversation(botId, conversationId, title, account.getId()); + return Result.ok(); + } + + @Override + public Result> list(BotConversation entity, Boolean asTree, String sortKey, String sortType) { + entity.setAccountId(SaTokenUtil.getLoginAccount().getId()); + sortKey = "created"; + sortType = "desc"; + return super.list(entity, asTree, sortKey, sortType); + } + + @Override + protected Result onSaveOrUpdateBefore(BotConversation entity, boolean isSave) { + entity.setAccountId(SaTokenUtil.getLoginAccount().getId()); + entity.setCreated(new Date()); + return super.onSaveOrUpdateBefore(entity, isSave); + } + + /** + * 分页查询会话列表 + * + * @param request 查询数据 + * @param sortKey 排序字段 + * @param sortType 排序方式 asc | desc + * @param pageNumber 当前页码 + * @param pageSize 每页的数据量 + * @return + */ + @GetMapping("pageList") + public Result> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) { + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1L; + } + if (pageSize == null || pageSize < 1) { + pageSize = 10L; + } + + QueryWrapper queryWrapper = buildQueryWrapper(request); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + Page botConversationPage = service.getMapper().paginateWithRelations(pageNumber, pageSize, queryWrapper); + return Result.ok(botConversationPage); + } + + /** + * 根据表主键查询数据详情。 + * + * @param id 主键值 + * @return 内容详情 + */ + @GetMapping("detail") + @SaIgnore + public Result detail(String id) { + if (tech.easyflow.common.util.StringUtil.noText(id)) { + throw new BusinessException("id must not be null"); + } + return Result.ok(service.getMapper().selectOneWithRelationsById(id)); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotMessageController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotMessageController.java new file mode 100644 index 0000000..1f7afcb --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotMessageController.java @@ -0,0 +1,60 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson.JSON; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.ai.service.BotMessageService; +import tech.easyflow.ai.vo.ChatMessageVO; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * Bot 消息记录表 控制层。 + * + * @author michael + * @since 2024-11-04 + */ +@RestController +@RequestMapping("/userCenter/botMessage") +@UsePermission(moduleName = "/api/v1/bot") +public class UcBotMessageController extends BaseCurdController { + private final BotMessageService botMessageService; + + public UcBotMessageController(BotMessageService service, BotMessageService botMessageService) { + super(service); + this.botMessageService = botMessageService; + } + + @GetMapping("/getMessages") + @SaIgnore + public Result> getMessages(BigInteger botId, BigInteger conversationId) { + List res = new ArrayList<>(); + QueryWrapper w = QueryWrapper.create(); + w.eq(BotMessage::getBotId, botId); + w.eq(BotMessage::getConversationId, conversationId); + List list = botMessageService.list(w); + if (CollectionUtil.isNotEmpty(list)) { + for (BotMessage message : list) { + ChatMessageVO vo = new ChatMessageVO(); + vo.setKey(message.getId().toString()); + vo.setRole(message.getRole()); + vo.setContent(JSON.parseObject(message.getContent()).getString("textContent")); + vo.setPlacement("user".equals(message.getRole()) ? "end" : "start"); + vo.setCreated(message.getCreated()); + res.add(vo); + } + } + return Result.ok(res); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotRecentlyUsedController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotRecentlyUsedController.java new file mode 100644 index 0000000..441facd --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotRecentlyUsedController.java @@ -0,0 +1,84 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.hutool.core.collection.CollectionUtil; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.Bot; +import tech.easyflow.ai.entity.BotRecentlyUsed; +import tech.easyflow.ai.service.BotRecentlyUsedService; +import tech.easyflow.ai.service.BotService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 最近使用 控制层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@RestController +@RequestMapping("/userCenter/botRecentlyUsed") +@UsePermission(moduleName = "/api/v1/bot") +public class UcBotRecentlyUsedController extends BaseCurdController { + + @Resource + private BotService botService; + + public UcBotRecentlyUsedController(BotRecentlyUsedService service) { + super(service); + } + + @GetMapping("/getRecentlyBot") + public Result> getRecentlyBot() { + LoginAccount account = SaTokenUtil.getLoginAccount(); + QueryWrapper w = QueryWrapper.create(); + w.eq(BotRecentlyUsed::getCreatedBy,account.getId()); + w.orderBy(BotRecentlyUsed::getSortNo,true); + List list = service.list(w); + if (CollectionUtil.isNotEmpty(list)) { + List botIds = list.stream().map(BotRecentlyUsed::getBotId).collect(Collectors.toList()); + QueryWrapper botQw = QueryWrapper.create(); + botQw.in(Bot::getId,botIds); + List listBot = botService.list(botQw); + listBot.sort(Comparator.comparing(bot -> botIds.indexOf(bot.getId()))); + return Result.ok(listBot); + } + return Result.ok(new ArrayList<>()); + } + + @GetMapping("/removeByBotId") + public Result removeByBotId(BigInteger botId) { + QueryWrapper w = QueryWrapper.create(); + w.eq(BotRecentlyUsed::getBotId,botId); + w.eq(BotRecentlyUsed::getCreatedBy,SaTokenUtil.getLoginAccount().getId()); + service.remove(w); + return Result.ok(); + } + + @Override + public Result> list(BotRecentlyUsed entity, Boolean asTree, String sortKey, String sortType) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + entity.setCreatedBy(account.getId()); + return super.list(entity, asTree, sortKey, sortType); + } + + @Override + protected Result onSaveOrUpdateBefore(BotRecentlyUsed entity, boolean isSave) { + entity.setCreated(new Date()); + entity.setCreatedBy(SaTokenUtil.getLoginAccount().getId()); + return super.onSaveOrUpdateBefore(entity, isSave); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java new file mode 100644 index 0000000..9a5c770 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java @@ -0,0 +1,58 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.http.HttpUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.Resource; +import tech.easyflow.ai.service.ResourceService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.util.Date; + +/** + * 素材库 + * + * @author ArkLight + * @since 2025-06-27 + */ +@RestController +@RequestMapping("/userCenter/resource") +@UsePermission(moduleName = "/api/v1/resource") +public class UcResourceController extends BaseCurdController { + public UcResourceController(ResourceService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(Resource entity, boolean isSave) { + LoginAccount loginUser = SaTokenUtil.getLoginAccount(); + if (isSave) { + String resourceUrl = entity.getResourceUrl(); + byte[] bytes = HttpUtil.downloadBytes(resourceUrl); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + String suffix = FileTypeUtil.getType(stream, resourceUrl); + entity.setSuffix(suffix); + entity.setFileSize(BigInteger.valueOf(bytes.length)); + commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId()); + } else { + entity.setModified(new Date()); + entity.setModifiedBy(loginUser.getId()); + } + return super.onSaveOrUpdateBefore(entity, isSave); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + return super.queryPage(page, queryWrapper); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java new file mode 100644 index 0000000..7c06354 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java @@ -0,0 +1,38 @@ +package tech.easyflow.usercenter.controller.ai; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.WorkflowCategory; +import tech.easyflow.ai.service.WorkflowCategoryService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 工作流分类 + * + * @author ArkLight + * @since 2025-12-11 + */ +@RestController +@RequestMapping("/userCenter/workflowCategory") +@UsePermission(moduleName = "/api/v1/workflow") +public class UcWorkflowCategoryController extends BaseCurdController { + + public UcWorkflowCategoryController(WorkflowCategoryService service) { + super(service); + } + + @Override + protected Result onSaveOrUpdateBefore(WorkflowCategory entity, boolean isSave) { + return Result.fail("-"); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + return Result.fail("-"); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java new file mode 100644 index 0000000..2c6e015 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java @@ -0,0 +1,139 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.stp.StpUtil; +import com.easyagents.flow.core.chain.*; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import com.easyagents.flow.core.parser.ChainParser; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.ai.easyagentsflow.entity.ChainInfo; +import tech.easyflow.ai.easyagentsflow.entity.NodeInfo; +import tech.easyflow.ai.easyagentsflow.service.TinyFlowService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.*; + +/** + * 工作流 + */ +@RestController +@RequestMapping("/userCenter/workflow") +@UsePermission(moduleName = "/api/v1/workflow") +public class UcWorkflowController extends BaseCurdController { + + @Resource + private ChainExecutor chainExecutor; + @Resource + private ChainParser chainParser; + @Resource + private TinyFlowService tinyFlowService; + + public UcWorkflowController(WorkflowService service) { + super(service); + } + + /** + * 节点单独运行 + */ + @PostMapping("/singleRun") + @SaCheckPermission("/api/v1/workflow/save") + public Result singleRun( + @JsonBody(value = "workflowId", required = true) BigInteger workflowId, + @JsonBody(value = "nodeId", required = true) String nodeId, + @JsonBody("variables") Map variables) { + + Workflow workflow = service.getById(workflowId); + if (workflow == null) { + return Result.fail(1, "工作流不存在"); + } + Map res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables); + return Result.ok(res); + } + + /** + * 运行工作流 - v2 + */ + @PostMapping("/runAsync") + @SaCheckPermission("/api/v1/workflow/save") + public Result runAsync(@JsonBody(value = "id", required = true) BigInteger id, + @JsonBody("variables") Map variables) { + if (variables == null) { + variables = new HashMap<>(); + } + Workflow workflow = service.getById(id); + if (workflow == null) { + throw new RuntimeException("工作流不存在"); + } + if (StpUtil.isLogin()) { + variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount()); + } + String executeId = chainExecutor.executeAsync(id.toString(), variables); + return Result.ok(executeId); + } + + /** + * 获取工作流运行状态 - v2 + */ + @PostMapping("/getChainStatus") + public Result getChainStatus(@JsonBody(value = "executeId") String executeId, + @JsonBody("nodes") List nodes) { + ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes); + return Result.ok(res); + } + + /** + * 恢复工作流运行 - v2 + */ + @PostMapping("/resume") + @SaCheckPermission("/api/v1/workflow/save") + public Result resume(@JsonBody(value = "executeId", required = true) String executeId, + @JsonBody("confirmParams") Map confirmParams) { + chainExecutor.resumeAsync(executeId, confirmParams); + return Result.ok(); + } + + /** + * 获取工作流参数 - v2 + */ + @GetMapping("getRunningParameters") + @SaCheckPermission("/api/v1/workflow/query") + public Result getRunningParameters(@RequestParam BigInteger id) { + Workflow workflow = service.getById(id); + + if (workflow == null) { + return Result.fail(1, "can not find the workflow by id: " + id); + } + + ChainDefinition definition = chainParser.parse(workflow.getContent()); + if (definition == null) { + return Result.fail(2, "节点配置错误,请检查! "); + } + List chainParameters = definition.getStartParameters(); + Map res = new HashMap<>(); + res.put("parameters", chainParameters); + res.put("title", workflow.getTitle()); + res.put("description", workflow.getDescription()); + res.put("icon", workflow.getIcon()); + return Result.ok(res); + } + + @Override + protected Result onSaveOrUpdateBefore(Workflow entity, boolean isSave) { + return Result.fail("-"); + } + + @Override + protected Result onRemoveBefore(Collection ids) { + return Result.fail("-"); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecResultController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecResultController.java new file mode 100644 index 0000000..d6b72ff --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecResultController.java @@ -0,0 +1,92 @@ +package tech.easyflow.usercenter.controller.ai; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.service.WorkflowExecResultService; +import tech.easyflow.ai.service.WorkflowExecStepService; +import tech.easyflow.ai.utils.WorkFlowUtil; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.controller.BaseCurdController; + +import javax.annotation.Resource; +import java.math.BigInteger; + +/** + * 工作流执行记录 + */ +@RestController +@RequestMapping("/userCenter/workflowExecResult") +@UsePermission(moduleName = "/api/v1/workflow") +public class UcWorkflowExecResultController extends BaseCurdController { + + @Resource + private WorkflowExecStepService recordStepService; + + public UcWorkflowExecResultController(WorkflowExecResultService service) { + super(service); + } + + /** + * 删除 + */ + @GetMapping("/del") + @Transactional(rollbackFor = Exception.class) + @SaCheckPermission("/api/v1/workflow/remove") + public Result del(BigInteger id) { + LoginAccount account = SaTokenUtil.getLoginAccount(); + WorkflowExecResult record = service.getById(id); + if (!account.getId().toString().equals(record.getCreatedBy())) { + return Result.fail(1, "非法请求"); + } + service.removeById(id); + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecStep::getRecordId, id); + recordStepService.remove(w); + return Result.ok(); + } + + @GetMapping("getPage") + public Result> getPage(HttpServletRequest request, + String sortKey, + String sortType, + Long pageNumber, + Long pageSize, + String queryBegin, + String queryEnd) { + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1L; + } + if (pageSize == null || pageSize < 1) { + pageSize = 10L; + } + + QueryWrapper queryWrapper = buildQueryWrapper(request); + if (StrUtil.isNotEmpty(queryBegin) && StrUtil.isNotEmpty(queryEnd)) { + queryWrapper.between(WorkflowExecResult::getStartTime, queryBegin, queryEnd); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper)); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + queryWrapper.eq(WorkflowExecResult::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + Page res = super.queryPage(page, queryWrapper); + for (WorkflowExecResult record : res.getRecords()) { + record.setWorkflowJson(WorkFlowUtil.removeSensitiveInfo(record.getWorkflowJson())); + } + return res; + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecStepController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecStepController.java new file mode 100644 index 0000000..dd4b643 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowExecStepController.java @@ -0,0 +1,66 @@ +package tech.easyflow.usercenter.controller.ai; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.mybatisflex.core.query.QueryWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.service.WorkflowExecResultService; +import tech.easyflow.ai.service.WorkflowExecStepService; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 执行记录步骤 + */ +@RestController +@RequestMapping("/userCenter/workflowExecStep") +@UsePermission(moduleName = "/api/v1/workflow") +public class UcWorkflowExecStepController extends BaseCurdController { + + @Resource + private WorkflowExecResultService execRecordService; + + public UcWorkflowExecStepController(WorkflowExecStepService service) { + super(service); + } + + /** + * 根据执行记录id获取执行记录步骤列表 + */ + @GetMapping("/getListByRecordId") + public Result> getListByRecordId(BigInteger recordId) { + if (recordId == null) { + throw new BusinessException("recordId不能为空!"); + } + WorkflowExecResult record = execRecordService.getById(recordId); + String workflowJson = record.getWorkflowJson(); + JSONObject workflow = JSON.parseObject(workflowJson); + Map idTypeMap = new HashMap<>(); + JSONArray nodes = workflow.getJSONArray("nodes"); + for (Object node : nodes) { + JSONObject nodeObj = (JSONObject) node; + idTypeMap.put(nodeObj.getString("id"), nodeObj.getString("type")); + } + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecStep::getRecordId, recordId); + List list = service.list(w); + for (WorkflowExecStep step : list) { + step.setNodeData(null); + step.setNodeType(idTypeMap.get(step.getNodeId())); + } + return Result.ok(list); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/auth/UcAuthController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/auth/UcAuthController.java new file mode 100644 index 0000000..ebf26eb --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/auth/UcAuthController.java @@ -0,0 +1,51 @@ +package tech.easyflow.usercenter.controller.auth; + +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.web.bind.annotation.*; +import tech.easyflow.auth.entity.LoginDTO; +import tech.easyflow.auth.entity.LoginVO; +import tech.easyflow.auth.service.AuthService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 认证 + */ +@RestController +@RequestMapping("/userCenter/auth/") +public class UcAuthController { + + @Resource + private AuthService authService; + + /** + * 登录 + * @param loginDTO 登录参数 + */ + @PostMapping("login") + public Result login(@JsonBody LoginDTO loginDTO) { + LoginVO res = authService.login(loginDTO); + return Result.ok(res); + } + + /** + * 登出 + */ + @PostMapping("logout") + public Result logout() { + StpUtil.logout(); + return Result.ok(); + } + + /** + * 获取权限 + */ + @GetMapping("getPermissions") + public Result> getPermissions() { + List permissionList = StpUtil.getPermissionList(); + return Result.ok(permissionList); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcDictController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcDictController.java new file mode 100644 index 0000000..fca3170 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcDictController.java @@ -0,0 +1,46 @@ +package tech.easyflow.usercenter.controller.common; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.dict.Dict; +import tech.easyflow.common.dict.DictItem; +import tech.easyflow.common.dict.DictLoader; +import tech.easyflow.common.dict.DictManager; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 字典 + */ +@RestController +@RequestMapping("/userCenter/dict/") +public class UcDictController { + + @Resource + DictManager dictManager; + + /** + * 获取字典项 + */ + @GetMapping("/items/{code}") + public Result> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) { + DictLoader loader = dictManager.getLoader(code); + if (loader == null) { + return Result.ok(Collections.emptyList()); + } + Map parameterMap = request.getParameterMap(); + Dict dict = loader.load(keyword, parameterMap); + if (dict == null) { + return Result.ok(Collections.emptyList()); + } + return Result.ok(dict.getItems()); + } + +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcPublicController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcPublicController.java new file mode 100644 index 0000000..2809718 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcPublicController.java @@ -0,0 +1,44 @@ +package tech.easyflow.usercenter.controller.common; + +import cloud.tianai.captcha.application.ImageCaptchaApplication; +import cloud.tianai.captcha.application.vo.ImageCaptchaVO; +import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; +import cloud.tianai.captcha.common.response.ApiResponse; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.captcha.tainai.CaptchaData; + +import javax.annotation.Resource; + +/** + * 公共接口 + */ +@RestController +@RequestMapping("/userCenter/public") +public class UcPublicController { + + @Resource + private ImageCaptchaApplication application; + + /** + * 获取验证码 + */ + @RequestMapping(value = "/getCaptcha", produces = "application/json") + public ApiResponse getCaptcha() { + return application.generateCaptcha(CaptchaTypeConstant.SLIDER); + } + + /** + * 验证码校验 + */ + @PostMapping(value = "/check", produces = "application/json") + public ApiResponse checkCaptcha(@RequestBody CaptchaData data) { + ApiResponse response = application.matching(data.getId(), data.getData()); + if (!response.isSuccess()) { + return ApiResponse.ofError("验证码错误"); + } + return ApiResponse.ofSuccess(data.getId()); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcUploadController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcUploadController.java new file mode 100644 index 0000000..2ea21db --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/common/UcUploadController.java @@ -0,0 +1,58 @@ +package tech.easyflow.usercenter.controller.common; + +import cn.dev33.satoken.annotation.SaIgnore; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.vo.UploadResVo; +import tech.easyflow.common.filestorage.FileStorageService; + +import javax.annotation.Resource; + +/** + * 文件上传 + */ +@RestController +@RequestMapping("/userCenter/commons/") +public class UcUploadController { + + @Resource(name = "default") + private FileStorageService storageService; + + /** + * 上传 + */ + @PostMapping(value = "/upload", produces = MediaType.APPLICATION_JSON_VALUE) + public Result upload(MultipartFile file) { + String path = storageService.save(file); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } + + /** + * @ignore + */ + @PostMapping(value = "/uploadAntd", produces = MediaType.APPLICATION_JSON_VALUE) + public Result uploadAntd(MultipartFile file) { + String path = storageService.save(file); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } + + /** + * @ignore + */ + @PostMapping(value = "/uploadPrePath",produces = MediaType.APPLICATION_JSON_VALUE) + @SaIgnore + public Result uploadPrePath(MultipartFile file, String prePath) { + String path = storageService.save(file,prePath); + UploadResVo resVo = new UploadResVo(); + resVo.setPath(path); + return Result.ok(resVo); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcSysAccountController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcSysAccountController.java new file mode 100644 index 0000000..bd5341f --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcSysAccountController.java @@ -0,0 +1,91 @@ +package tech.easyflow.usercenter.controller.system; + +import cn.hutool.crypto.digest.BCrypt; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.service.SysAccountService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.Date; + +/** + * 用户 + * + * @author ArkLight + * @since 2025-03-14 + */ +@RestController +@RequestMapping("/userCenter/sysAccount") +public class UcSysAccountController { + + @Resource + private SysAccountService service; + + /** + * 获取用户的信息 + */ + @GetMapping("/myProfile") + public Result myProfile() { + LoginAccount account = SaTokenUtil.getLoginAccount(); + SysAccount sysAccount = service.getById(account.getId()); + return Result.ok(sysAccount); + } + + /** + * 修改用户信息 + */ + @PostMapping("/updateProfile") + public Result updateProfile(@JsonBody SysAccount account) { + BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId(); + SysAccount update = new SysAccount(); + update.setId(loginAccountId); + update.setNickname(account.getNickname()); + update.setMobile(account.getMobile()); + update.setEmail(account.getEmail()); + update.setAvatar(account.getAvatar()); + update.setModified(new Date()); + update.setModifiedBy(loginAccountId); + service.updateById(update); + return Result.ok(); + } + + /** + * 修改密码 + * + * @param password 用户的旧密码 + * @param newPassword 新密码 + * @param confirmPassword 确认密码 + */ + @PostMapping("/updatePassword") + public Result updatePassword(@JsonBody(value = "password", required = true) String password, + @JsonBody(value = "newPassword", required = true) String newPassword, + @JsonBody(value = "confirmPassword", required = true) String confirmPassword) { + BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId(); + SysAccount record = service.getById(loginAccountId); + if (record == null) { + return Result.fail("修改失败"); + } + String pwdDb = record.getPassword(); + if (!BCrypt.checkpw(password, pwdDb)) { + return Result.fail(1, "密码不正确"); + } + if (!newPassword.equals(confirmPassword)) { + return Result.fail(2, "两次密码不一致"); + } + SysAccount update = new SysAccount(); + update.setId(loginAccountId); + update.setPassword(BCrypt.hashpw(newPassword)); + update.setModified(new Date()); + update.setModifiedBy(loginAccountId); + service.updateById(update); + return Result.ok(); + } +} \ No newline at end of file diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcUserFeedbackController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcUserFeedbackController.java new file mode 100644 index 0000000..395f0d0 --- /dev/null +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/system/UcUserFeedbackController.java @@ -0,0 +1,36 @@ +package tech.easyflow.usercenter.controller.system; + +import cn.dev33.satoken.annotation.SaIgnore; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.SysUserFeedback; +import tech.easyflow.system.service.SysUserFeedbackService; + + +/** + * 控制层。 + * + * @author 12076 + * @since 2025-12-30 + */ +@RestController +@RequestMapping("/userCenter/sysUserFeedback") +@UsePermission(moduleName = "/api/v1/sysUserFeedback") +public class UcUserFeedbackController extends BaseCurdController { + + @PostMapping("/save") + @SaIgnore + public Result save(@JsonBody SysUserFeedback entity) { + return super.save(entity); + } + + public UcUserFeedbackController(SysUserFeedbackService service) { + super(service); + } + +} \ No newline at end of file diff --git a/easyflow-api/pom.xml b/easyflow-api/pom.xml new file mode 100644 index 0000000..ce82203 --- /dev/null +++ b/easyflow-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + tech.easyflow + easyflow + ${revision} + + + easyflow-api + pom + + + easyflow-api-admin + easyflow-api-mcp + easyflow-api-public + easyflow-api-usercenter + + + \ No newline at end of file diff --git a/easyflow-chat-protocol.md b/easyflow-chat-protocol.md new file mode 100644 index 0000000..15438a4 --- /dev/null +++ b/easyflow-chat-protocol.md @@ -0,0 +1,418 @@ + + +# EasyFlow Chat Protocol Specification v1.1 + +* **Protocol Name:** `easyflow-chat` +* **Version:** `1.1` +* **Status:** Draft / Recommended +* **Transport:** Server-Sent Events (SSE) +* **Encoding:** UTF-8 + + + +## 1. 设计背景与目标 + +本协议用于描述 **EasyFlow 对话系统中的服务端事件流通信规范**,支持: + +* AI 对话的 **流式输出** +* 模型 **思考过程(Thinking)** +* **工具调用(Tool Calling)** +* **系统 / 业务错误** +* **工作流 / Agent 状态** +* **对话中的用户交互(表单、确认等)** +* **中断与恢复(Suspend / Resume)** + +设计目标: + +* 前后端解耦 +* 协议长期可扩展 +* 不绑定具体模型厂商 +* 易于与 Workflow / Agent / Chain 架构集成 + + + +## 2. 传输层规范(Transport) + +* 使用 HTTP + SSE(支持未来扩展为其他协议,比如 WebSocket 等) +* Response Header: + + ```http + Content-Type: text/event-stream + Cache-Control: no-cache + Connection: keep-alive + ``` +* 通信方向:**Server → Client** +* 所有业务数据通过 `data` 字段传输,格式为 **JSON 字符串** + + + +## 3. SSE Event 级别规范 + +### 3.1 Event Name(固定) + +| event | 含义 | +| - |-------| +| message | 正常业务事件 | +| error | 错误事件 | +| done | 流结束事件 | + +> ⚠️ **禁止在 event name 中承载业务语义** + + + +## 4. 统一 Envelope 结构(核心) + +### 4.1 基本结构 + +```json +{ + "protocol": "easyflow-chat", + "version": "1.1", + "domain": "llm | tool | system | business | workflow | interaction | debug", + "type": "string", + "conversation_id": "string", + "message_id": "string", + "index": 0, + "payload": {}, + "meta": {} +} +``` + + + +### 4.2 字段说明 + +| 字段 | 类型 | 必填 | 说明 | +| -- |---------| -- |------------------------| +| protocol | string | ✔ | 固定值 `easyflow-chat` | +| version | string | ✔ | 协议版本 | +| domain | string | ✔ | 事件所属领域 | +| type | string | ✔ | 领域内事件类型 | +| conversation_id | string | ✔ | 会话唯一标识 | +| message_id | string | ✖ | assistant 消息 ID | +| index | number | ✖ | 流式输出序号 | +| payload | object | ✔ | 事件数据 | +| meta | object | ✖ | 元信息(token、耗时等) | + + + +## 5. Domain 定义 + +| Domain | 说明 | +| -- | -- | +| llm | 模型语义输出 | +| tool | 工具调用与结果 | +| system | 系统级事件 | +| business | 业务规则 | +| workflow | 工作流 / Agent 状态 | +| interaction | 用户交互(表单等) | +| debug | 调试信息 | + + + +## 6. llm Domain + +### 6.1 thinking + +表示模型的思考过程。 + +#### 流式输出(delta) + +```json +{ + "domain": "llm", + "type": "thinking", + "payload": { + "delta": "分析用户需求" + } +} +``` + +#### 完整输出(可选) + +```json +{ + "domain": "llm", + "type": "message", + "payload": { + "content": "这是一个完整的回答" + } +} +``` + + +### 6.2 message + +#### 流式输出(delta) + +```json +{ + "domain": "llm", + "type": "message", + "index": 12, + "payload": { + "delta": "这是一个" + } +} +``` + +#### 完整输出(可选) + +```json +{ + "domain": "llm", + "type": "message", + "payload": { + "content": "这是一个完整的回答" + } +} +``` + + + +## 7. tool Domain + +### 7.1 tool_call + +```json +{ + "domain": "tool", + "type": "tool_call", + "payload": { + "tool_call_id": "call_1", + "name": "search", + "arguments": { + "query": "SSE 协议设计" + } + } +} +``` + + + +### 7.2 tool_result + +```json +{ + "domain": "tool", + "type": "tool_result", + "payload": { + "tool_call_id": "call_1", + "status": "success | error", + "result": {} + } +} +``` + + + +## 8. system Domain + +### 8.1 error + +```json +{ + "domain": "system", + "type": "error", + "payload": { + "code": "MODEL_CONFIG_INVALID", + "message": "模型配置错误", + "retryable": false, + "detail": {} + } +} +``` + + + +### 8.2 status + +```json +{ + "domain": "system", + "type": "status", + "payload": { + "state": "initializing | running | suspended | resumed" + } +} +``` + + + +## 9. business Domain + +```json +{ + "domain": "business", + "type": "error", + "payload": { + "code": "QUOTA_EXCEEDED", + "message": "配额不足" + } +} +``` + + + +## 10. workflow Domain + +```json +{ + "domain": "workflow", + "type": "status", + "payload": { + "node_id": "node_1", + "state": "start | suspend | resume | end", + "reason": "interaction" + } +} +``` + + + +## 11. interaction Domain(对话内交互) + +### 11.1 form_request + +表示请求用户填写表单,对话进入挂起状态。 + +```json +{ + "domain": "interaction", + "type": "form_request", + "payload": { + "form_id": "user_info_form", + "title": "补充信息", + "description": "请填写以下信息以继续", + "schema": { + "type": "object", + "required": ["age", "email"], + "properties": { + "age": { + "type": "number", + "title": "年龄" + }, + "email": { + "type": "string", + "title": "邮箱", + "format": "email" + } + } + }, + "ui": { + "submit_text": "继续", + "cancel_text": "取消" + } + } +} +``` + +> 表单 schema **符合 JSON Schema 标准** + + + +### 11.2 form_cancel + +```json +{ + "domain": "interaction", + "type": "form_cancel", + "payload": { + "form_id": "user_info_form" + } +} +``` + + + +## 12. 表单提交与恢复(非 SSE) + +表单提交通过 **普通 HTTP / WebSocket 请求**: + +```json +{ + "conversation_id": "conv_1", + "form_id": "user_info_form", + "values": { + "age": 30, + "email": "a@b.com" + } +} +``` + +成功后服务端恢复 SSE 流。 + + + +## 13. done 事件(流结束) + +```json +{ + "domain": "system", + "type": "done", + "meta": { + "prompt_tokens": 1234, + "completion_tokens": 456, + "latency_ms": 2300 + } +} +``` + + + +## 14. 错误处理规则 + +* 收到 `event: error` 后客户端应终止流 +* 错误语义由: + + ``` + domain + type + payload.code + ``` + + 共同决定 + + + +## 15. 状态机视角(推荐) + +```text +RUNNING + ↓ +LLM_OUTPUT + ↓ +INTERACTION_REQUESTED + ↓ +SUSPENDED + ↓ +FORM_SUBMITTED + ↓ +RESUMED + ↓ +RUNNING + ↓ +DONE +``` + + + +## 16. 扩展与兼容规则 + +1. 可新增 domain +2. 可新增 type +3. 不允许删除已有字段 +4. payload 可自由扩展 +5. 1.x 版本保持向后兼容 + + + +## 17. 设计原则 + +> * SSE 只负责事件流 +> * domain 定义责任边界 +> * type 定义语义动作 +> * payload 定义数据结构 +> * 前端不依赖 event name 判断业务,不依赖协议本身,支持其他协议的扩展 + + + + diff --git a/easyflow-commons/easyflow-common-ai/pom.xml b/easyflow-commons/easyflow-common-ai/pom.xml new file mode 100644 index 0000000..39d8ba6 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-ai + easyflow-common-ai + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.commonmark + commonmark + + + + org.jsoup + jsoup + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-autoconfigure + + + + com.github.jsqlparser + jsqlparser + 4.9 + + + + com.easyagents + easy-agents-bom + + + com.squareup.okhttp3 + okhttp + + + com.squareup.okhttp + okhttp + + + com.squareup.okio + okio + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.projectlombok + lombok + + + org.apache.poi + ooxml-schemas + + + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + com.squareup.okhttp3 + okhttp + + + + tech.easyflow + easyflow-common-base + compile + + + + tech.easyflow + easyflow-common-options + compile + + + + cn.hutool + hutool-http + compile + + + + cn.hutool + hutool-json + compile + + + + cn.idev.excel + fastexcel + + + + diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/ChatSseEmitter.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/ChatSseEmitter.java new file mode 100644 index 0000000..bc2090c --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/ChatSseEmitter.java @@ -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(); + } +} diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/inteceptor/ToolLoggingInterceptor.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/inteceptor/ToolLoggingInterceptor.java new file mode 100644 index 0000000..0832934 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/inteceptor/ToolLoggingInterceptor.java @@ -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 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"); + } + } + +} diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/NestedParamConverter.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/NestedParamConverter.java new file mode 100644 index 0000000..00ea926 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/NestedParamConverter.java @@ -0,0 +1,49 @@ +package tech.easyflow.common.ai.plugin; +import java.util.*; + +public class NestedParamConverter { + + public static Map convertToNestedParamMap(List pluginParams) { + Map 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 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> list = new ArrayList<>(); + + Map 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; + } +} diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginHttpClient.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginHttpClient.java new file mode 100644 index 0000000..cb36228 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginHttpClient.java @@ -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 headers, + List 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 defaultHeaders, + List 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 params) { + Map queryParams = new HashMap<>(); + Map 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 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 params) { + String result = url; + + // 收集路径参数 + Map pathParams = new HashMap<>(); + params.stream() + .filter(p -> "path".equalsIgnoreCase(p.getMethod()) && p.isEnabled()) + .forEach(p -> pathParams.put(p.getName(), p.getDefaultValue())); + + // 替换变量 + for (Map.Entry entry : pathParams.entrySet()) { + result = result.replaceAll("\\{" + entry.getKey() + "\\}", + entry.getValue().toString()); + } + + return result; + } + + private static void processMultipartBody(HttpRequest request, Map bodyParams) { + // 手动设置 Content-Type 为 multipart/form-data + request.header(Header.CONTENT_TYPE, "multipart/form-data"); + for (Map.Entry 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); + } + } + } + +} diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParam.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParam.java new file mode 100644 index 0000000..44edacd --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParam.java @@ -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 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 getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParamConverter.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParamConverter.java new file mode 100644 index 0000000..6637ef7 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/plugin/PluginParamConverter.java @@ -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 convertFromJson(String jsonStr) { + try { + // 将JSON字符串解析为List结构 + List> paramMaps = objectMapper.readValue( + jsonStr, + new TypeReference>>(){} + ); + + List result = new ArrayList<>(); + for (Map 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 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> childrenMaps = (List>) childrenObj; + List children = new ArrayList<>(); + for (Map childMap : childrenMaps) { + children.add(convertMapToPluginParam(childMap)); + } + param.setChildren(children); + } + } + + return param; + } + + private static String getStringValue(Map map, String key) { + Object value = map.get(key); + return value != null ? value.toString() : null; + } + + private static boolean getBooleanValue(Map map, String key) { + Object value = map.get(key); + if (value instanceof Boolean) { + return (Boolean) value; + } + return false; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/rag/ExcelDocumentSplitter.java b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/rag/ExcelDocumentSplitter.java new file mode 100644 index 0000000..2461da7 --- /dev/null +++ b/easyflow-commons/easyflow-common-ai/src/main/java/tech/easyflow/common/ai/rag/ExcelDocumentSplitter.java @@ -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 split(Document document, DocumentIdGenerator idGenerator) { + if (document == null || document.getContent() == null) { + return Collections.emptyList(); + } + + // 解析JSON数据为表格结构 + List> tableData = JSON.parseObject(document.getContent(), + new TypeReference>>() {}); + + if (tableData == null || tableData.isEmpty()) { + return Collections.emptyList(); + } + + List chunks = new ArrayList<>(); + List 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 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; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-all/pom.xml b/easyflow-commons/easyflow-common-all/pom.xml new file mode 100644 index 0000000..b460fdb --- /dev/null +++ b/easyflow-commons/easyflow-common-all/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-all + easyflow-common-all + + + + tech.easyflow + easyflow-common-ai + + + tech.easyflow + easyflow-common-base + + + tech.easyflow + easyflow-common-cache + + + tech.easyflow + easyflow-common-file-storage + + + tech.easyflow + easyflow-common-options + + + tech.easyflow + easyflow-common-captcha + + + tech.easyflow + easyflow-common-web + + + + diff --git a/easyflow-commons/easyflow-common-audio/pom.xml b/easyflow-commons/easyflow-common-audio/pom.xml new file mode 100644 index 0000000..f712647 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-audio + + + + + io.netty + netty-all + ${netty.version} + + + + com.alibaba.nls + nls-sdk-tts + + + io.netty + * + + + + + com.alibaba.nls + nls-sdk-common + + + io.netty + * + + + + + + tech.easyflow + easyflow-common-base + compile + + + tech.easyflow + easyflow-common-web + + + org.java-websocket + Java-WebSocket + + + tech.easyflow + easyflow-common-satoken + + + org.springframework.boot + spring-boot-starter-websocket + + + diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/TestAudioController.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/TestAudioController.java new file mode 100644 index 0000000..123358b --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/TestAudioController.java @@ -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"; + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AliConfig.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AliConfig.java new file mode 100644 index 0000000..056c69f --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AliConfig.java @@ -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); + } + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AudioConfig.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AudioConfig.java new file mode 100644 index 0000000..1df2ee1 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/config/AudioConfig.java @@ -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); + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioService.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioService.java new file mode 100644 index 0000000..c614d88 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioService.java @@ -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); +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioServiceManager.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioServiceManager.java new file mode 100644 index 0000000..0dbea74 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/AudioServiceManager.java @@ -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; + } + +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/BaseAudioClient.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/BaseAudioClient.java new file mode 100644 index 0000000..ede7907 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/core/BaseAudioClient.java @@ -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(); +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioClient.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioClient.java new file mode 100644 index 0000000..ec8235e --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioClient.java @@ -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 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; + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioService.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioService.java new file mode 100644 index 0000000..94168f1 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliAudioService.java @@ -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 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(); + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliListener.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliListener.java new file mode 100644 index 0000000..59e924f --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/ali/AliListener.java @@ -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 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; + } + +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/tencent/TencentAudioService.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/tencent/TencentAudioService.java new file mode 100644 index 0000000..901537a --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/tencent/TencentAudioService.java @@ -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 ""; + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/volc/VolcanoEngineAudioService.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/volc/VolcanoEngineAudioService.java new file mode 100644 index 0000000..c75283c --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/impl/volc/VolcanoEngineAudioService.java @@ -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 ""; + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/listener/AudioMessageListener.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/listener/AudioMessageListener.java new file mode 100644 index 0000000..7a7c76f --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/listener/AudioMessageListener.java @@ -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); +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketConfig.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketConfig.java new file mode 100644 index 0000000..316dd5a --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketConfig.java @@ -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("*"); + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketHandler.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketHandler.java new file mode 100644 index 0000000..4194453 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketHandler.java @@ -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 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; + } + +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketInterceptor.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketInterceptor.java new file mode 100644 index 0000000..2b37e38 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/AudioSocketInterceptor.java @@ -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 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) { + + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SchedulingConfig.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SchedulingConfig.java new file mode 100644 index 0000000..b03bd00 --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SchedulingConfig.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SocketEntity.java b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SocketEntity.java new file mode 100644 index 0000000..88a8ffc --- /dev/null +++ b/easyflow-commons/easyflow-common-audio/src/main/java/tech/easyflow/common/audio/socket/SocketEntity.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/pom.xml b/easyflow-commons/easyflow-common-base/pom.xml new file mode 100644 index 0000000..0bdec3e --- /dev/null +++ b/easyflow-commons/easyflow-common-base/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-base + easyflow-common-base + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba + fastjson + + + + org.slf4j + slf4j-api + + + + com.mybatis-flex + mybatis-flex-core + compile + + + com.squareup.okhttp3 + okhttp + + + + cn.hutool + hutool-core + + + + diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/Consts.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/Consts.java new file mode 100644 index 0000000..acbe419 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/Consts.java @@ -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; +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/DictDef.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/DictDef.java new file mode 100644 index 0000000..0129877 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/DictDef.java @@ -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(); +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/NeedApiKeyAccess.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/NeedApiKeyAccess.java new file mode 100644 index 0000000..24d6999 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/NeedApiKeyAccess.java @@ -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 {}; +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/UsePermission.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/UsePermission.java new file mode 100644 index 0000000..73125e3 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/annotation/UsePermission.java @@ -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(); +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/CacheKey.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/CacheKey.java new file mode 100644 index 0000000..538a9f6 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/CacheKey.java @@ -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:"; +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/Constants.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/Constants.java new file mode 100644 index 0000000..800a8fb --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/Constants.java @@ -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"; +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumAccountType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumAccountType.java new file mode 100644 index 0000000..f7c583f --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumAccountType.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataScope.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataScope.java new file mode 100644 index 0000000..e16bbad --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataScope.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataStatus.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataStatus.java new file mode 100644 index 0000000..b177dc9 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumDataStatus.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFeedbackType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFeedbackType.java new file mode 100644 index 0000000..f3c0311 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFeedbackType.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFieldType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFieldType.java new file mode 100644 index 0000000..aa5f6a7 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumFieldType.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobExecStatus.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobExecStatus.java new file mode 100644 index 0000000..c843dbd --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobExecStatus.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobResult.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobResult.java new file mode 100644 index 0000000..47fe6d9 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobResult.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobStatus.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobStatus.java new file mode 100644 index 0000000..51cc41a --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobStatus.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobType.java new file mode 100644 index 0000000..ca42f22 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumJobType.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMenuType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMenuType.java new file mode 100644 index 0000000..3821585 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMenuType.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMisfirePolicy.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMisfirePolicy.java new file mode 100644 index 0000000..7f852fa --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumMisfirePolicy.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumRes.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumRes.java new file mode 100644 index 0000000..27c8cd2 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumRes.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceOriginType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceOriginType.java new file mode 100644 index 0000000..21b69df --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceOriginType.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceType.java new file mode 100644 index 0000000..3174419 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumResourceType.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumShowOrNot.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumShowOrNot.java new file mode 100644 index 0000000..60ac55e --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumShowOrNot.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumTenantStatus.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumTenantStatus.java new file mode 100644 index 0000000..3375aab --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumTenantStatus.java @@ -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; + }} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumYesOrNo.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumYesOrNo.java new file mode 100644 index 0000000..d7d30cd --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/constant/enums/EnumYesOrNo.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/Dict.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/Dict.java new file mode 100644 index 0000000..71f3483 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/Dict.java @@ -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 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 getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void addItem(DictItem item){ + if (this.items == null){ + this.items = new ArrayList<>(); + } + items.add(item); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictDefAutoConfig.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictDefAutoConfig.java new file mode 100644 index 0000000..b0f61f6 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictDefAutoConfig.java @@ -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 > 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 enumClass = (Class) 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); + } + } + } + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictItem.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictItem.java new file mode 100644 index 0000000..775c1c1 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictItem.java @@ -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 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 getChildren() { + return children; + } + + public void setChildren(List 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); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictLoader.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictLoader.java new file mode 100644 index 0000000..15dbd1c --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictLoader.java @@ -0,0 +1,8 @@ +package tech.easyflow.common.dict; + +import java.util.Map; + +public interface DictLoader { + String code(); + Dict load(String keyword, Map parameters); +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictManager.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictManager.java new file mode 100644 index 0000000..9f88968 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictManager.java @@ -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 loaders = new HashMap<>(); + + public DictManager(ObjectProvider> listObjectProvider) { + List dictLoaders = listObjectProvider.getIfAvailable(); + if (dictLoaders != null) { + dictLoaders.forEach(dictLoader -> loaders.put(dictLoader.code(), dictLoader)); + } + } + + public Map getLoaders() { + return loaders; + } + + public void setLoaders(Map 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); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictType.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictType.java new file mode 100644 index 0000000..02d3345 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/DictType.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DatabaseDictLoader.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DatabaseDictLoader.java new file mode 100644 index 0000000..4397c0e --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DatabaseDictLoader.java @@ -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 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 rows = Db.selectListBySql(sql, StringUtils.hasText(keyword) ? new Object[]{keyword.trim()} : EMPTY_PARAMS); + if (rows == null || rows.isEmpty()) { + return null; + } + + List items = new ArrayList<>(rows.size()); + + Boolean asTree = RequestUtil.getParamAsBoolean(parameters,"asTree"); + + //有树形结构 + if (StringUtils.hasText(parentColumn)) { + List 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 parentRows, List parentItems, List allRows) { + for (Row parentRow : parentRows) { + DictItem parentItem = row2DictItem(0, parentRow); + parentItems.add(parentItem); + + List children = new ArrayList<>(); + for (Row maybeChild : allRows) { + if (Objects.equals(maybeChild.get(parentColumn), parentRow.get(keyColumn))) { + children.add(maybeChild); + } + } + if (!children.isEmpty()) { + List childrenItems = new ArrayList<>(children.size()); + parentItem.setChildren(childrenItems); + makeTree(children, childrenItems, allRows); + } + } + } + + private void makeLayer(int layerNo, List parentRows, List parentItems, List allRows) { + for (Row parentRow : parentRows) { + parentItems.add(row2DictItem(layerNo, parentRow)); + + List 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 findTopLayerRows(List rows) { + List 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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DbDataLoader.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DbDataLoader.java new file mode 100644 index 0000000..ae993d9 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/DbDataLoader.java @@ -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 implements DictLoader { + + private final String code; + private final BaseMapper 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 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 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 records = mapper.selectListByQuery(where); + List rows = new ArrayList<>(); + for (T record : records) { + rows.add(JSON.parseObject(JSON.toJSONString(record))); + } + Boolean asTree = RequestUtil.getParamAsBoolean(parameters, "asTree"); + + List items = new ArrayList<>(rows.size()); + + //有树形结构 + if (StringUtils.hasText(parentColumn)) { + List 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 parentRows, List parentItems, List allRows) { + for (JSONObject parentRow : parentRows) { + DictItem parentItem = row2DictItem(0, parentRow); + parentItems.add(parentItem); + + List children = new ArrayList<>(); + for (JSONObject maybeChild : allRows) { + if (Objects.equals(maybeChild.get(parentColumnCamelCase), parentRow.get(keyColumnCamelCase))) { + children.add(maybeChild); + } + } + if (!children.isEmpty()) { + List childrenItems = new ArrayList<>(children.size()); + parentItem.setChildren(childrenItems); + makeTree(children, childrenItems, allRows); + } + } + } + + private void makeLayer(int layerNo, List parentRows, List parentItems, List allRows) { + for (JSONObject parentRow : parentRows) { + parentItems.add(row2DictItem(layerNo, parentRow)); + + List 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 findTopLayerRows(List rows) { + List 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 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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/EnumDictLoader.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/EnumDictLoader.java new file mode 100644 index 0000000..051a7ae --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/dict/loader/EnumDictLoader.java @@ -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> implements DictLoader { + + private final String code; + private final Dict dict; + + public EnumDictLoader(String code, Class enumClass, String keyField, String labelField) { + this(null, code, enumClass, keyField, labelField); + } + + public EnumDictLoader(String name, String code, Class 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 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 parameters) { + return dict; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/domain/Result.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/domain/Result.java new file mode 100644 index 0000000..90d5194 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/domain/Result.java @@ -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 implements Serializable { + private static final long serialVersionUID = -8744614420977483630L; + + /** + * 返回状态码 + * + * @mock 0 + * @see EnumRes + */ + private Integer errorCode; + + /** + * 提示消息 + * + * @mock 成功 + */ + private String message; + + /** + * 返回的数据 + */ + private T data; + + public Result message(String message) { + this.message = message; + return this; + } + + public Result data(T data) { + this.data = data; + return this; + } + + public Result success() { + this.errorCode = EnumRes.SUCCESS.getCode(); + return this; + } + + public Result fail() { + this.errorCode = EnumRes.FAIL.getCode(); + return this; + } + + + public Result fail(int errorCode) { + this.errorCode = errorCode; + return this; + } + + public static Result ok() { + Result Result = new Result<>(); + Result.setErrorCode(EnumRes.SUCCESS.getCode()); + Result.setMessage(EnumRes.SUCCESS.getMsg()); + return Result; + } + +// public static Result ok(String msg) { +// Result Result = new Result<>(); +// Result.setErrorCode(EnumRes.SUCCESS.getCode()); +// Result.setMessage(msg); +// return Result; +// } + + public static Result ok(T data) { + Result Result = new Result<>(); + Result.setErrorCode(EnumRes.SUCCESS.getCode()); + Result.setMessage(EnumRes.SUCCESS.getMsg()); + Result.setData(data); + return Result; + } + + public static Result ok(String msg, T data) { + Result 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 fail(String msg) { + Result Result = new Result<>(); + Result.setErrorCode(EnumRes.FAIL.getCode()); + Result.setMessage(msg); + return Result; + } + + public static Result fail(int code, String msg) { + Result Result = new Result<>(); + Result.setErrorCode(code); + Result.setMessage(msg); + return Result; + } + + public static Result fail(String msg, T data) { + Result 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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DatacenterQuery.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DatacenterQuery.java new file mode 100644 index 0000000..e6a7d9f --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DatacenterQuery.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateEntity.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateEntity.java new file mode 100644 index 0000000..616ed0c --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateEntity.java @@ -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); +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateTreeEntity.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateTreeEntity.java new file mode 100644 index 0000000..5f4db05 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/DateTreeEntity.java @@ -0,0 +1,4 @@ +package tech.easyflow.common.entity; + +public class DateTreeEntity extends TreeEntity{ +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/LoginAccount.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/LoginAccount.java new file mode 100644 index 0000000..4ccfec0 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/LoginAccount.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/TreeEntity.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/TreeEntity.java new file mode 100644 index 0000000..a46ca18 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/entity/TreeEntity.java @@ -0,0 +1,6 @@ +package tech.easyflow.common.entity; + +import tech.easyflow.common.tree.TreeNode; + +public class TreeEntity extends TreeNode { +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/spring/BaseApp.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/spring/BaseApp.java new file mode 100644 index 0000000..e81301b --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/spring/BaseApp.java @@ -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 { + + 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 ipList = getLocalIpList(); + for (String ip : ipList) { + msg.append(" > Network: http://").append(ip).append(":").append(port).append("\n"); + } + System.out.println(msg); + } + + + private static List getLocalIpList() { + List ipList = new ArrayList<>(); + try { + for (Enumeration e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) { + NetworkInterface networkInterface = e.nextElement(); + if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) { + continue; + } + + for (Enumeration ele = networkInterface.getInetAddresses(); ele.hasMoreElements(); ) { + InetAddress ip = ele.nextElement(); + if (ip instanceof Inet4Address) { + ipList.add(ip.getHostAddress()); + } + } + } + return ipList; + } catch (Exception e) { + return ipList; + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/Tree.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/Tree.java new file mode 100644 index 0000000..cf13239 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/Tree.java @@ -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 { + + private List root; + private final Method idGetter; + private final Method pidGetter; + + public Tree(List nodes, String idFieldName, String pidFieldName) { + //noinspection unchecked + this((Class) nodes.get(0).getClass(), idFieldName, pidFieldName); + for (T node : nodes) { + this.addNode(node); + } + } + + + public Tree(Class 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 nodes) { + if (nodes != null && !nodes.isEmpty()) { + for (T node : nodes) { + System.out.println(getPrefix(layerNo) + node.toString()); + //noinspection unchecked + doPrint(layerNo + 1, (List) 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 root, T newNode) { + List children = new ArrayList<>(); + T parent = findParentAndChildren(root, newNode, children); + if (!children.isEmpty()) { + //noinspection unchecked + newNode.setChildren((List) children); + } + if (parent == null) { + root.add(newNode); + } else { + parent.addChild(newNode); + } + } + + private T findParentAndChildren(List root, T newNode, List 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) 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 getRoot() { + return root; + } + + public List getRootAsLayer() { + List all = new ArrayList<>(); + addNodesToList(this.root, all); + return all; + } + + private void addNodesToList(List nodes, List list) { + if (nodes != null && !nodes.isEmpty()) { + for (T node : nodes) { + list.add(node); + //noinspection unchecked + addNodesToList((List) node.getChildren(), list); + } + } + } + + + public static List tryToTree(List list) { + return tryToTree(list, true); + } + + @SuppressWarnings("unchecked") + public static List tryToTree(List 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 tree = new Tree<>((List) list, "id", "pid"); + list = (List) 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 List tryToTree(List list,String idFieldName,String pidFieldName) { + if (list != null && !list.isEmpty()) { + T data = list.get(0); + if (data != null && TreeNode.class.isAssignableFrom(data.getClass())) { + Tree tree = new Tree<>((List) list, idFieldName, pidFieldName); + list = (List) tree.getRoot(); + } + } + return list; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/TreeNode.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/TreeNode.java new file mode 100644 index 0000000..9e8daa9 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/tree/TreeNode.java @@ -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 children; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(TreeNode newNode) { + if (children == null) { + children = new ArrayList<>(); + } + children.add(newNode); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/DateUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/DateUtil.java new file mode 100644 index 0000000..84703c7 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/DateUtil.java @@ -0,0 +1,1089 @@ +/** + * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.easyflow.common.util; + + +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author michael yang (fuhai999@gmail.com) + */ +public class DateUtil { + + public static String datePatternWithoutDividing = "yyyyMMdd"; + public static String datePattern = "yyyy-MM-dd"; + public static final String dateMinutePattern = "yyyy-MM-dd HH:mm"; + public static final String dateMinutePattern2 = "yyyy-MM-dd'T'HH:mm"; + public static String datetimePattern = "yyyy-MM-dd HH:mm:ss"; + public static final String dateMillisecondPattern = "yyyy-MM-dd HH:mm:ss SSS"; + public static final String dateCSTPattern = "EEE MMM dd HH:mm:ss zzz yyyy"; + + public static String dateChinesePattern = "yyyy年MM月dd日"; + public static String datetimeChinesePattern = "yyyy年MM月dd日 HH时mm分ss秒"; + + private static final String[] WEEKS = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; + + private static final ThreadLocal> TL = ThreadLocal.withInitial(() -> new HashMap<>()); + + private static final Map dateTimeFormatters = new ConcurrentHashMap<>(); + + public static DateTimeFormatter getDateTimeFormatter(String pattern) { + DateTimeFormatter ret = dateTimeFormatters.get(pattern); + if (ret == null) { + ret = DateTimeFormatter.ofPattern(pattern); + dateTimeFormatters.put(pattern, ret); + } + return ret; + } + + public static SimpleDateFormat getSimpleDateFormat(String pattern) { + SimpleDateFormat ret = TL.get().get(pattern); + if (ret == null) { + if (dateCSTPattern.equals(pattern)) { + ret = new SimpleDateFormat(dateCSTPattern, Locale.US); + } else { + ret = new SimpleDateFormat(pattern); + } + TL.get().put(pattern, ret); + } + return ret; + } + + + public static String toDateString(Date date) { + return toString(date, datePattern); + } + + + public static String toDateMinuteString(Date date) { + return toString(date, dateMinutePattern); + } + + public static String toDateTimeString(Date date) { + return toString(date, datetimePattern); + } + + + public static String toDateMillisecondString(Date date) { + return toString(date, dateMillisecondPattern); + } + + + public static String toString(Date date, String pattern) { + return date == null ? null : getSimpleDateFormat(pattern).format(date); + } + + + public static String toString(LocalDateTime localDateTime, String pattern) { + return localDateTime.format(getDateTimeFormatter(pattern)); + } + + public static String toString(LocalDate localDate, String pattern) { + return localDate.format(getDateTimeFormatter(pattern)); + } + + public static String toString(LocalTime localTime, String pattern) { + return localTime.format(getDateTimeFormatter(pattern)); + } + + + public static Date parseDate(Object value) { + if (value instanceof Number) { + return new Date(((Number) value).longValue()); + } + if (value instanceof Timestamp) { + return new Date(((Timestamp) value).getTime()); + } + if (value instanceof LocalDate) { + return DateUtil.toDate((LocalDate) value); + } + if (value instanceof LocalDateTime) { + return DateUtil.toDate((LocalDateTime) value); + } + if (value instanceof LocalTime) { + return DateUtil.toDate((LocalTime) value); + } + String s = value.toString(); + if (StringUtil.isNumeric(s)) { + return new Date(Long.parseLong(s)); + } + return DateUtil.parseDate(s); + } + + + public static Date parseDate(String dateString) { + if (StringUtil.noText(dateString)) { + return null; + } + dateString = dateString.trim(); + try { + SimpleDateFormat sdf = getSimpleDateFormat(getPattern(dateString)); + try { + return sdf.parse(dateString); + } catch (ParseException ex) { + //2022-10-23 00:00:00.0 + int lastIndexOf = dateString.lastIndexOf("."); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00,0 + lastIndexOf = dateString.lastIndexOf(","); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00 000123 + lastIndexOf = dateString.lastIndexOf(" "); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + if (dateString.contains(".") || dateString.contains("/")) { + dateString = dateString.replace(".", "-").replace("/", "-"); + return sdf.parse(dateString); + } else { + throw ex; + } + } + } catch (ParseException ex) { + throw new IllegalArgumentException("The date format is not supported for the date string: " + dateString); + } + } + + + private static String getPattern(String dateString) { + int length = dateString.length(); + if (length == datetimePattern.length()) { + return datetimePattern; + } else if (length == datePattern.length()) { + return datePattern; + } else if (length == dateMinutePattern.length()) { + if (dateString.contains("T")) { + return dateMinutePattern2; + } + return dateMinutePattern; + } else if (length == dateMillisecondPattern.length()) { + return dateMillisecondPattern; + } else if (length == datePatternWithoutDividing.length()) { + return datePatternWithoutDividing; + } else if (length == dateCSTPattern.length()) { + return dateCSTPattern; + } else { + throw new IllegalArgumentException("The date format is not supported for the date string: " + dateString); + } + } + + + public static Date parseDate(String dateString, String pattern) { + if (StringUtil.noText(dateString)) { + return null; + } + try { + return getSimpleDateFormat(pattern).parse(dateString.trim()); + } catch (ParseException e) { + throw new IllegalArgumentException("The date format is not supported for the date string: " + dateString); + } + } + + + public static LocalDateTime parseLocalDateTime(String localDateTimeString, String pattern) { + return LocalDateTime.parse(localDateTimeString, getDateTimeFormatter(pattern)); + } + + public static LocalDate parseLocalDate(String localDateString, String pattern) { + return LocalDate.parse(localDateString, getDateTimeFormatter(pattern)); + } + + + public static LocalTime parseLocalTime(String localTimeString, String pattern) { + return LocalTime.parse(localTimeString, getDateTimeFormatter(pattern)); + } + + + /** + * java.util.Date --> java.time.LocalDateTime + */ + public static LocalDateTime toLocalDateTime(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + return LocalDateTime.ofInstant(instant, zone); + } + + /** + * java.util.Date --> java.time.LocalDate + */ + public static LocalDate toLocalDate(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); + return localDateTime.toLocalDate(); + } + + /** + * java.util.Date --> java.time.LocalTime + */ + public static LocalTime toLocalTime(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); + return localDateTime.toLocalTime(); + } + + /** + * java.time.LocalDateTime --> java.util.Date + */ + public static Date toDate(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDateTime.atZone(zone).toInstant(); + return Date.from(instant); + } + + /** + * java.time.LocalDate --> java.util.Date + */ + public static Date toDate(LocalDate localDate) { + if (localDate == null) { + return null; + } + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDate.atStartOfDay().atZone(zone).toInstant(); + return Date.from(instant); + } + + /** + * java.time.LocalTime --> java.util.Date + */ + public static Date toDate(LocalTime localTime) { + if (localTime == null) { + return null; + } + LocalDate localDate = LocalDate.now(); + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDateTime.atZone(zone).toInstant(); + return Date.from(instant); + } + + /** + * java.time.LocalTime --> java.util.Date + */ + public static Date toDate(LocalDate localDate, LocalTime localTime) { + if (localDate == null) { + return null; + } + + if (localTime == null) { + localTime = LocalTime.of(0, 0, 0); + } + + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDateTime.atZone(zone).toInstant(); + return Date.from(instant); + } + + + /** + * 任意一天的开始时间 + * + * @return date + */ + public static Date getStartOfDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + /** + * 任意一天的结束时间 + * + * @return date + */ + public static Date getEndOfDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 24); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + + /** + * 获取今天的开始时间 + * + * @return + */ + public static Date getStartOfToday() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + + /** + * 获取昨天的开始时间 + * + * @return + */ + public static Date getStartOfYesterday() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getStartOfToday().getTime() - 3600L * 24 * 1000); + return cal.getTime(); + } + + + /** + * 获取最近 7 天的开始时间 + * + * @return + */ + public static Date getStartOfNearest7Days() { + return getStartOfNearestDays(7); + } + + + /** + * 获取最近 N 天的开始时间 + * + * @param days + * @return + */ + public static Date getStartOfNearestDays(int days) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getStartOfToday().getTime() - 3600L * 24 * 1000 * days); + return cal.getTime(); + } + + + /** + * 获取今天的结束数据 + * + * @return + */ + public static Date getEndOfToday() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 24); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + + /** + * 获取 本周 的开始时间 + * + * @return + */ + public static Date getStartOfThisWeek() { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(Calendar.MONDAY); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + return cal.getTime(); + } + + /** + * 获取 本周 的结束时间 + * + * @return + */ + public static Date getEndOfThisWeek() { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(Calendar.MONDAY); + cal.setTime(getStartOfThisWeek()); + cal.add(Calendar.DAY_OF_WEEK, 7); + return cal.getTime(); + } + + + /** + * 获取 本月 的开始时间 + * + * @return + */ + public static Date getStartOfThisMonth() { + Calendar cal = Calendar.getInstance(); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH)); + return cal.getTime(); + } + + /** + * 获取 本月 的结束时间 + * + * @return + */ + public static Date getEndOfThisMonth() { + Calendar cal = Calendar.getInstance(); + cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.HOUR_OF_DAY, 24); + return cal.getTime(); + } + + + /** + * 获取上个月的开始时间 + * + * @return + */ + public static Date getStartOfLastMonth() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getStartOfThisMonth()); + cal.add(Calendar.MONTH, -1); + return cal.getTime(); + } + + + /** + * 获取 本季度 的开始时间 + * + * @return + */ + public static Date getStartOfThisQuarter() { + Calendar cal = Calendar.getInstance(); + int currentMonth = cal.get(Calendar.MONTH) + 1; + if (currentMonth <= 3) { + cal.set(Calendar.MONTH, 0); + } else if (currentMonth <= 6) { + cal.set(Calendar.MONTH, 3); + } else if (currentMonth <= 9) { + cal.set(Calendar.MONTH, 6); + } else if (currentMonth <= 12) { + cal.set(Calendar.MONTH, 9); + } + cal.set(Calendar.DATE, 0); + + cal.set(Calendar.HOUR_OF_DAY, 24); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + + return cal.getTime(); + } + + + /** + * 获取 本季度的 结束时间 + * + * @return + */ + public static Date getEndOfThisQuarter() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getStartOfThisQuarter()); + cal.add(Calendar.MONTH, 3); + return cal.getTime(); + } + + + /** + * 获取 季度 的开始时间 + * + * @param quarterNumber + * @return + */ + public static Date getStartOfQuarter(int quarterNumber) { + if (quarterNumber < 1 || quarterNumber > 4) { + throw new IllegalArgumentException("quarterNumber must equals 1,2,3,4"); + } + Calendar cal = Calendar.getInstance(); + if (quarterNumber == 1) { + cal.set(Calendar.MONTH, 0); + } else if (quarterNumber == 2) { + cal.set(Calendar.MONTH, 3); + } else if (quarterNumber == 3) { + cal.set(Calendar.MONTH, 6); + } else { + cal.set(Calendar.MONTH, 9); + } + + cal.set(Calendar.DATE, 0); + cal.set(Calendar.HOUR_OF_DAY, 24); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.MILLISECOND, 0); + + return cal.getTime(); + } + + + /** + * 获取 季度 的结束时间 + * + * @param quarterNumber + * @return + */ + public static Date getEndOfQuarter(int quarterNumber) { + Calendar cal = Calendar.getInstance(); + cal.setTime(getStartOfQuarter(quarterNumber)); + cal.add(Calendar.MONTH, 3); + return cal.getTime(); + } + + + /** + * 获取 今年 的开始时间 + * + * @return + */ + public static Date getStartOfThisYear() { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.set(cal.get(Calendar.YEAR), 0, 1, 0, 0, 0); + return cal.getTime(); + } + + + /** + * 获取 今年 的结束时间 + * + * @return + */ + public static Date getEndOfThisYear() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getStartOfThisYear()); + cal.add(Calendar.YEAR, 1); + return cal.getTime(); + } + + + /** + * 获取 去年的 开始时间 + * + * @return + */ + public static Date getStartOfLastYear() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getStartOfThisYear()); + cal.add(Calendar.YEAR, -1); + return cal.getTime(); + } + + /** + * 获取两个时间直接的间隔:单位 秒 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffSecond(Date date1, Date date2) { + long date1ms = date1.getTime(); + long date2ms = date2.getTime(); + return Math.abs((int) ((date1ms - date2ms) / (1000))); + } + + /** + * 获取两个时间直接的间隔:单位 分钟 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffMinute(Date date1, Date date2) { + long date1ms = date1.getTime(); + long date2ms = date2.getTime(); + return Math.abs((int) ((date1ms - date2ms) / (1000 * 60))); + } + + + /** + * 获取两个时间直接的间隔:单位 小时 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffHours(Date date1, Date date2) { + long date1ms = date1.getTime(); + long date2ms = date2.getTime(); + return Math.abs((int) ((date1ms - date2ms) / (1000 * 60 * 60))); + } + + /** + * 获取两个时间直接的间隔:单位 天 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffDays(Date date1, Date date2) { + long date1ms = date1.getTime(); + long date2ms = date2.getTime(); + return Math.abs((int) ((date1ms - date2ms) / (1000 * 60 * 60 * 24))); + } + + /** + * 获取两个时间直接的间隔:单位 星期 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffWeeks(Date date1, Date date2) { + long date1ms = date1.getTime(); + long date2ms = date2.getTime(); + return Math.abs((int) ((date1ms - date2ms) / (1000 * 60 * 60 * 24 * 7))); + } + + /** + * 获取两个时间直接的间隔:单位 月 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffMonths(Date date1, Date date2) { + int diffYears = diffYears(date1, date2) * 12; + int number1 = getMonthNumber(date1); + int number2 = getMonthNumber(date2); + return Math.abs(diffYears + number1 - number2); + } + + /** + * 获取两个时间直接的间隔:单位 年 + * + * @param date1 + * @param date2 + * @return + */ + public static int diffYears(Date date1, Date date2) { + int number1 = getYearNumber(date1); + int number2 = getYearNumber(date2); + return Math.abs(number1 - number2); + } + + + /** + * 获取日期的月份 + * + * @param date + * @return + */ + public static int getMonthNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.MONTH) + 1; + } + + + /** + * 获取日期的季度 + * + * @param date + * @return + */ + public static int getQuarterNumber(Date date) { + int monthNumber = getMonthNumber(date); + if (monthNumber >= 1 && monthNumber <= 3) { + return 1; + } else if (monthNumber >= 4 && monthNumber <= 6) { + return 2; + } else if (monthNumber >= 7 && monthNumber <= 9) { + return 3; + } else { + return 4; + } + } + + + /** + * 获取日期的年份 + * + * @param date + * @return + */ + public static int getYearNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.YEAR); + } + + + /** + * 获取日期的是当年的第几天 + * + * @param date + * @return + */ + public static int getDayOfYearNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.DAY_OF_YEAR); + } + + /** + * 获取日期的当月的第几天 + * + * @param date + * @return + */ + public static int getDayOfMonthNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.DAY_OF_MONTH); + } + + /** + * 获取日期的当星期的第几天 + * + * @param date + * @return + */ + public static int getDayOfWeekNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(Calendar.MONDAY); + cal.setTime(date); + return cal.get(Calendar.DAY_OF_WEEK); + } + + + /** + * 获取日期的是当年的第个星期 + * + * @param date + * @return + */ + public static int getWeekOfYearNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(Calendar.MONDAY); + cal.setTime(date); + return cal.get(Calendar.WEEK_OF_YEAR); + } + + /** + * 获取日期的当月的第几星期 + * + * @param date + * @return + */ + public static int getWeekOfMonthNumber(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(Calendar.MONDAY); + cal.setTime(date); + return cal.get(Calendar.WEEK_OF_MONTH); + } + + + /** + * 取得在指定时间上加减seconds天后的时间 + * + * @param date 指定的时间 + * @param seconds 秒钟,正为加,负为减 + * @return 在指定时间上加减seconds天后的时间 + */ + public static Date addSeconds(Date date, int seconds) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.SECOND, seconds); + return cal.getTime(); + } + + + /** + * 取得在指定时间上加减minutes天后的时间 + * + * @param date 指定的时间 + * @param minutes 分钟,正为加,负为减 + * @return 在指定时间上加减minutes天后的时间 + */ + public static Date addMinutes(Date date, int minutes) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.MINUTE, minutes); + return cal.getTime(); + } + + /** + * 取得在指定时间上加减hours天后的时间 + * + * @param date 指定的时间 + * @param hours 小时,正为加,负为减 + * @return 在指定时间上加减dhours天后的时间 + */ + public static Date addHours(Date date, int hours) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.HOUR, hours); + return cal.getTime(); + } + + /** + * 取得在指定时间上加减days天后的时间 + * + * @param date 指定的时间 + * @param days 天数,正为加,负为减 + * @return 在指定时间上加减days天后的时间 + */ + public static Date addDays(Date date, int days) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DAY_OF_MONTH, days); + return cal.getTime(); + } + + /** + * 取得在指定时间上加减weeks天后的时间 + * + * @param date 指定的时间 + * @param weeks 星期,正为加,负为减 + * @return 在指定时间上加减weeks天后的时间 + */ + public static Date addWeeks(Date date, int weeks) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.WEEK_OF_YEAR, weeks); + return cal.getTime(); + } + + /** + * 取得在指定时间上加减months月后的时间 + * + * @param date 指定时间 + * @param months 月数,正为加,负为减 + * @return 在指定时间上加减months月后的时间 + */ + public static Date addMonths(Date date, int months) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.MONTH, months); + return cal.getTime(); + } + + /** + * 取得在指定时间上加减years年后的时间 + * + * @param date 指定时间 + * @param years 年数,正为加,负为减 + * @return 在指定时间上加减years年后的时间 + */ + public static Date addYears(Date date, int years) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.YEAR, years); + return cal.getTime(); + } + + + /** + * 判断 A 的时间是否在 B 的时间 "之后" + */ + public static boolean isAfter(Date self, Date other) { + return self != null && other != null && self.getTime() > other.getTime(); + } + + /** + * 判断 A 的时间是否在 B 的时间 "之后" + */ + public static boolean isBefore(Date self, Date other) { + return self != null && other != null && self.getTime() < other.getTime(); + } + + /** + * 是否是相同的一天 + */ + public static boolean isSameDay(Date self, Date other) { + return self != null && other != null && getYearNumber(self) == getYearNumber(other) + && getDayOfYearNumber(self) == getDayOfYearNumber(other); + } + + /** + * 是否是相同的星期 + */ + public static boolean isSameWeek(Date self, Date other) { + return self != null && other != null && getYearNumber(self) == getYearNumber(other) + && getWeekOfYearNumber(self) == getWeekOfYearNumber(other); + } + + /** + * 是否是相同的月份 + */ + public static boolean isSameMonth(Date self, Date other) { + return self != null && other != null && getYearNumber(self) == getYearNumber(other) + && getMonthNumber(self) == getMonthNumber(other); + } + + /** + * 是否是相同的月份 + */ + public static boolean isSameQuarter(Date self, Date other) { + return self != null && other != null && getYearNumber(self) == getYearNumber(other) + && getQuarterNumber(self) == getQuarterNumber(other); + } + + /** + * 是否是相同的月份 + */ + public static boolean isSameYear(Date self, Date other) { + return self != null && other != null && getYearNumber(self) == getYearNumber(other); + } + + + /** + * 此日期是否是今天 + */ + public static boolean isToday(Date date) { + return isSameDay(new Date(), date); + } + + /** + * 此日期是否是本星期 + */ + public static boolean isThisWeek(Date date) { + return isSameWeek(new Date(), date); + } + + /** + * 此日期是否是本月份 + */ + public static boolean isThisMonth(Date date) { + return isSameMonth(new Date(), date); + } + + + /** + * 此日期是否是本月份 + */ + public static boolean isThisQuarter(Date date) { + return isSameQuarter(new Date(), date); + } + + /** + * 此日期是否是本年份 + */ + public static boolean isThisYear(Date date) { + return date != null && getYearNumber(new Date()) == getYearNumber(date); + } + + /** + * 判断是否是润年 + */ + public static boolean isLeapYear(Date date) { + return date != null && new GregorianCalendar().isLeapYear(getYearNumber(date)); + } + + /** + * 求出指定的时间那天是星期几 + */ + public static String getWeekDay(Date date) { + return date == null ? null : DateUtil.WEEKS[getDayOfWeekNumber(date) - 1]; + } + + + public static void main(String[] args) { + System.out.println("两天后的开始时间:" + toDateTimeString(getStartOfDay(addDays(new Date(), 2)))); + System.out.println("两天后的结束时间:" + toDateTimeString(getEndOfDay(addDays(new Date(), 2)))); + + System.out.println("CST时间解析:" + toDateTimeString(parseDate("Mon Sep 02 11:23:45 CST 2019"))); + + + System.out.println("当天24点时间:" + toDateTimeString(getEndOfToday())); + System.out.println("当前时间:" + toDateTimeString(new Date())); + System.out.println("当天0点时间:" + toDateTimeString(getStartOfToday())); + System.out.println("昨天0点时间:" + toDateTimeString(getStartOfYesterday())); + System.out.println("近7天时间:" + toDateTimeString(getStartOfNearest7Days())); + System.out.println("本周周一0点时间:" + toDateTimeString(getStartOfThisWeek())); + System.out.println("本周周日24点时间:" + toDateTimeString(getEndOfThisWeek())); + System.out.println("本月初0点时间:" + toDateTimeString(getStartOfThisMonth())); + System.out.println("本月未24点时间:" + toDateTimeString(getEndOfThisMonth())); + System.out.println("上月初0点时间:" + toDateTimeString(getStartOfLastMonth())); + System.out.println("本季度开始点时间:" + toDateTimeString(getStartOfThisQuarter())); + System.out.println("本季度结束点时间:" + toDateTimeString(getEndOfThisQuarter())); + System.out.println("本年开始点时间:" + toDateTimeString(getStartOfThisYear())); + System.out.println("本年结束点时间:" + toDateTimeString(getEndOfThisYear())); + System.out.println("上年开始点时间:" + toDateTimeString(getStartOfLastYear())); + System.out.println("============="); + System.out.println("秒间隔:" + diffSecond(parseDate("2020-02-11 12:21:55"), parseDate("2020-02-11 12:22:58"))); + System.out.println("分钟间隔:" + diffMinute(parseDate("2020-02-11 12:21:55"), parseDate("2020-02-11 12:22:01"))); + System.out.println("小时间隔:" + diffHours(parseDate("2020-02-11 12:21:55"), parseDate("2020-02-12 12:22:01"))); + System.out.println("天间隔:" + diffDays(parseDate("2020-02-11 12:21:55"), parseDate("2020-02-12 12:22:01"))); + System.out.println("星期间隔:" + diffWeeks(parseDate("2020-01-11 12:21:55"), parseDate("2020-02-12 12:22:01"))); + System.out.println("月间隔:" + diffMonths(parseDate("2019-10-11 12:21:55"), parseDate("2020-09-11 12:21:55"))); + System.out.println("年间隔:" + diffYears(parseDate("1990-01-11 12:21:55"), parseDate("2020-02-12 12:22:01"))); + System.out.println("当前年份:" + getYearNumber(new Date())); + System.out.println("当前月份:" + getMonthNumber(new Date())); + System.out.println("============="); + + System.out.println("新增秒:" + toDateTimeString(addSeconds(parseDate("2020-02-11 12:21:55"), 20))); + System.out.println("新增分钟:" + toDateTimeString(addMinutes(parseDate("2020-02-11 12:21:55"), 20))); + System.out.println("新增小时:" + toDateTimeString(addHours(parseDate("2020-02-11 12:21:55"), 20))); + System.out.println("新增天:" + toDateTimeString(addDays(parseDate("2020-02-11 12:21:55"), 20))); + System.out.println("新增星期:" + toDateTimeString(addWeeks(parseDate("2020-02-11 12:21:55"), 10))); + System.out.println("新增月份:" + toDateTimeString(addMonths(parseDate("2020-02-11 12:21:55"), 20))); + System.out.println("新增年份:" + toDateTimeString(addYears(parseDate("2020-02-11 12:21:55"), 20))); + + System.out.println("============="); + System.out.println("今天星期:" + getWeekDay(parseDate("2020-11-24"))); + System.out.println("isToday:" + isToday(parseDate("2020-12-01"))); + System.out.println("isThisWeek:" + isThisWeek(parseDate("2020-11-24"))); + System.out.println("isThisMonth:" + isThisMonth(parseDate("2020-10-02"))); + System.out.println("isThisQuarter:" + isThisQuarter(parseDate("2020-10-02"))); + System.out.println("isThisYear:" + isThisYear(parseDate("2020-02-02"))); + System.out.println("第1季度:" + toDateTimeString(getEndOfQuarter(1))); + System.out.println("第2季度:" + toDateTimeString(getEndOfQuarter(2))); + System.out.println("第3季度:" + toDateTimeString(getEndOfQuarter(3))); + System.out.println("第4季度:" + toDateTimeString(getEndOfQuarter(4))); + System.out.println("本季度:" + toDateTimeString(getStartOfThisQuarter())); + System.out.println("本季度:" + toDateTimeString(getEndOfThisQuarter())); + + + System.out.println("datetime-local解析:" + parseDate("2022-12-03T16:00")); + + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/FileUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/FileUtil.java new file mode 100644 index 0000000..faf114c --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/FileUtil.java @@ -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); + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/HashUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/HashUtil.java new file mode 100644 index 0000000..ab13858 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/HashUtil.java @@ -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(); + } + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IOUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IOUtil.java new file mode 100644 index 0000000..b1f3d6d --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IOUtil.java @@ -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); + } + + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IdUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IdUtil.java new file mode 100644 index 0000000..9bd1d63 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/IdUtil.java @@ -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(); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapUtil.java new file mode 100644 index 0000000..34a2da8 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapUtil.java @@ -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 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 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 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 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 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 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 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 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 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 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()); + } + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapperUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapperUtil.java new file mode 100644 index 0000000..e1361f5 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/MapperUtil.java @@ -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 Entity 类 + */ + public static void syncList(List newModels, BaseMapper mapper, QueryWrapper existQueryWrapper, + Function getter) { + syncList(newModels, mapper, existQueryWrapper, getter, null); + } + + + /** + * 同步 List 到数据库 + * + * @param newModels 新的 Models + * @param mapper Mapper 查询 + * @param existQueryWrapper 查询旧的 Wrapper + * @param getter 根据什么字段来对比进行同步 + * @param onSyncBefore 在同步到数据库之前,可能需要做的前置操作 + * @param Entity 类 + */ + public static void syncList(List newModels, BaseMapper mapper, QueryWrapper existQueryWrapper, + Function getter, + Consumer onSyncBefore) { + + List existModels = mapper.selectListByQuery(existQueryWrapper); + + List needDeletes = new ArrayList<>(); + List 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 primaryKeyList = tableInfo.getPrimaryKeyList(); + List 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; + }); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/Maps.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/Maps.java new file mode 100644 index 0000000..367dfd7 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/Maps.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022-2023, Agents-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package 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 { + + 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 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) 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 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(); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpClientUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpClientUtil.java new file mode 100644 index 0000000..905bfe8 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpClientUtil.java @@ -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); + } + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpUtil.java new file mode 100644 index 0000000..f78a2aa --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/OkHttpUtil.java @@ -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 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 headers, String payload) { + return executeString(url, "POST", headers, payload); + } + + public static byte[] postBytes(String url, Map headers, String payload) { + return executeBytes(url, "POST", headers, payload); + } + + public static String put(String url, Map headers, String payload) { + return executeString(url, "PUT", headers, payload); + } + + public static String delete(String url, Map headers, String payload) { + return executeString(url, "DELETE", headers, payload); + } + + public static String multipartString(String url, Map headers, Map 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 headers, Map 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 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 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 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 headers, Map 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); + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/PropertiesUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/PropertiesUtil.java new file mode 100644 index 0000000..a941b85 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/PropertiesUtil.java @@ -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 目标实体类的泛型类型 + * @return 转换后的实体对象 + */ + public static T propertiesToEntity(Properties properties, Class 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 propertiesTextToEntity(String propertiesText, Class entityClass) { + Properties properties = textToProperties(propertiesText); + return propertiesToEntity(properties, entityClass); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/RequestUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/RequestUtil.java new file mode 100644 index 0000000..76d5f2a --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/RequestUtil.java @@ -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 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 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 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(); + } + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/ResponseUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/ResponseUtil.java new file mode 100644 index 0000000..ddcfe65 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/ResponseUtil.java @@ -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 + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SSEUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SSEUtil.java new file mode 100644 index 0000000..4b5207f --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SSEUtil.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SpringContextUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SpringContextUtil.java new file mode 100644 index 0000000..67ed8b5 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SpringContextUtil.java @@ -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 getBean(String name) { + return (T) getBeanFactory().getBean(name); + } + + public static T getBean(Class clazz) { + return getBeanFactory().getBean(clazz); + } + + public static T getBean(String name, Class clazz) { + return getBeanFactory().getBean(name, clazz); + } + + public static Map getBeansWithAnnotation(Class 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 getProperty(String key, Class targetType, T defaultValue) { + if (null == applicationContext) { + return null; + } + return applicationContext.getEnvironment().getProperty(key, targetType, defaultValue); + } + + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlOperatorsUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlOperatorsUtil.java new file mode 100644 index 0000000..e957055 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlOperatorsUtil.java @@ -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, SqlOperators> sqlOperatorsMap = new ConcurrentHashMap<>(); + + public static SqlOperators build(Class entityClass) { + return new SqlOperators(MapUtil.computeIfAbsent(sqlOperatorsMap, entityClass, aClass -> { + SqlOperators sqlOperators = new SqlOperators(); + List 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; + })); + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlUtil.java new file mode 100644 index 0000000..3f846cf --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/SqlUtil.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/StringUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/StringUtil.java new file mode 100644 index 0000000..eba9408 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/StringUtil.java @@ -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 splitToSet(String src, String regex) { + if (src == null) { + return Collections.emptySet(); + } + + String[] strings = src.split(regex); + Set set = new LinkedHashSet<>(); + for (String s : strings) { + if (hasText(s)) { + set.add(s.trim()); + } + } + return set; + } + + public static Set 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); + } + +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/UrlEncoderUtil.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/UrlEncoderUtil.java new file mode 100644 index 0000000..14dd0fa --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/util/UrlEncoderUtil.java @@ -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("/"); + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/MenuVo.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/MenuVo.java new file mode 100644 index 0000000..3128a64 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/MenuVo.java @@ -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; + } + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/PkVo.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/PkVo.java new file mode 100644 index 0000000..a15d426 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/PkVo.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/UploadResVo.java b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/UploadResVo.java new file mode 100644 index 0000000..0a01381 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/main/java/tech/easyflow/common/vo/UploadResVo.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeObj.java b/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeObj.java new file mode 100644 index 0000000..7d7f7d3 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeObj.java @@ -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 + '\'' + + '}'; + } +} diff --git a/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeTest.java b/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeTest.java new file mode 100644 index 0000000..0ce48a5 --- /dev/null +++ b/easyflow-commons/easyflow-common-base/src/test/java/tech/easyflow/common/tree/test/TreeTest.java @@ -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 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(); + + } +} diff --git a/easyflow-commons/easyflow-common-cache/pom.xml b/easyflow-commons/easyflow-common-cache/pom.xml new file mode 100644 index 0000000..a72d713 --- /dev/null +++ b/easyflow-commons/easyflow-common-cache/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-cache + easyflow-common-cache + + + + + redis.clients + jedis + 5.2.0 + + + + com.alicp.jetcache + jetcache-starter-redis + 2.7.7 + + + redis.clients + jedis + + + + + + com.alibaba + fastjson + + + + + + + diff --git a/easyflow-commons/easyflow-common-cache/src/main/java/tech/easyflow/common/cache/CacheConfig.java b/easyflow-commons/easyflow-common-cache/src/main/java/tech/easyflow/common/cache/CacheConfig.java new file mode 100644 index 0000000..085e962 --- /dev/null +++ b/easyflow-commons/easyflow-common-cache/src/main/java/tech/easyflow/common/cache/CacheConfig.java @@ -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 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 getDefaultCache() { + return defaultCache; + } +} diff --git a/easyflow-commons/easyflow-common-captcha/pom.xml b/easyflow-commons/easyflow-common-captcha/pom.xml new file mode 100644 index 0000000..a331f05 --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-captcha + easyflow-common-captcha + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-web + + + com.alibaba + fastjson + + + com.tencentcloudapi + tencentcloud-sdk-java + 3.1.933 + + + + org.testng + testng + RELEASE + compile + + + + tech.easyflow + easyflow-common-base + + + cloud.tianai.captcha + tianai-captcha-springboot-starter + + + + + diff --git a/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaCacheAutoConfig.java b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaCacheAutoConfig.java new file mode 100644 index 0000000..7d9dd07 --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaCacheAutoConfig.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaConfig.java b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaConfig.java new file mode 100644 index 0000000..7646a63 --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaConfig.java @@ -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")); + } +} diff --git a/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaData.java b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaData.java new file mode 100644 index 0000000..b5dbac4 --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaData.java @@ -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; + } +} diff --git a/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaMvcConfig.java b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaMvcConfig.java new file mode 100644 index 0000000..74b220f --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaMvcConfig.java @@ -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"); + } +} diff --git a/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaValidInterceptor.java b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaValidInterceptor.java new file mode 100644 index 0000000..532d839 --- /dev/null +++ b/easyflow-commons/easyflow-common-captcha/src/main/java/tech/easyflow/common/captcha/tainai/CaptchaValidInterceptor.java @@ -0,0 +1,41 @@ +package tech.easyflow.common.captcha.tainai; + +import cloud.tianai.captcha.application.ImageCaptchaApplication; +import cloud.tianai.captcha.spring.plugins.secondary.SecondaryVerificationApplication; +import com.alibaba.fastjson.JSONObject; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.util.RequestUtil; +import tech.easyflow.common.util.ResponseUtil; + +@Component +public class CaptchaValidInterceptor implements HandlerInterceptor { + + @Resource + private ImageCaptchaApplication application; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + JSONObject jsonObject = (JSONObject) RequestUtil.readJsonObjectOrArray(request); + String validToken = jsonObject.getString("validToken"); + if (validToken == null || validToken.isEmpty()) { + renderNotLogin(response); + return false; + } + boolean valid = ((SecondaryVerificationApplication) application).secondaryVerification(validToken); + if (!valid) { + renderNotLogin(response); + return false; + } + return true; + } + + private static void renderNotLogin(HttpServletResponse response) { + Result result = Result.fail(99, "验证失败,请重试!"); + ResponseUtil.renderJson(response, result); + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/pom.xml b/easyflow-commons/easyflow-common-chat-protocol/pom.xml new file mode 100644 index 0000000..0bb86db --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-chat-protocol + easyflow-common-chat-protocol + + + + com.alibaba + fastjson + + + tech.easyflow + easyflow-common-web + + + org.springframework.boot + spring-boot-starter-websocket + + + + \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatDomain.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatDomain.java new file mode 100644 index 0000000..c71a39c --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatDomain.java @@ -0,0 +1,12 @@ +package tech.easyflow.core.chat.protocol; + +public enum ChatDomain { + LLM, + TOOL, + SYSTEM, + BUSINESS, + WORKFLOW, + INTERACTION, + DEBUG + +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEnvelope.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEnvelope.java new file mode 100644 index 0000000..62eb916 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEnvelope.java @@ -0,0 +1,89 @@ +package tech.easyflow.core.chat.protocol; + +public class ChatEnvelope { + + private String protocol = "easyflow-chat"; + private String version = "1.1"; + + private ChatDomain domain; + private ChatType type; + + private String conversationId; + private String messageId; + private Integer index; + + private T payload; + private Object meta; + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public ChatDomain getDomain() { + return domain; + } + + public void setDomain(ChatDomain domain) { + this.domain = domain; + } + + public ChatType getType() { + return type; + } + + public void setType(ChatType type) { + this.type = type; + } + + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.payload = payload; + } + + public Object getMeta() { + return meta; + } + + public void setMeta(Object meta) { + this.meta = meta; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEventFactory.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEventFactory.java new file mode 100644 index 0000000..5c771e2 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatEventFactory.java @@ -0,0 +1,53 @@ +package tech.easyflow.core.chat.protocol; + +import tech.easyflow.core.chat.protocol.payload.MessageDeltaPayload; +import tech.easyflow.core.chat.protocol.payload.ThinkingPayload; + +public final class ChatEventFactory { + + private ChatEventFactory() { + } + + public static ChatEnvelope thinking(String conversationId, String content) { + ChatEnvelope e = base(conversationId); + e.setDomain(ChatDomain.LLM); + e.setType(ChatType.THINKING); + + ThinkingPayload p = new ThinkingPayload(); + p.setContent(content); +// p.setVisibility("hidden"); + + e.setPayload(p); + return e; + } + + public static ChatEnvelope messageDelta( + String conversationId, + String delta + ) { + ChatEnvelope e = base(conversationId); + e.setDomain(ChatDomain.LLM); + e.setType(ChatType.MESSAGE); + + MessageDeltaPayload p = new MessageDeltaPayload(); + p.setDelta(delta); + + e.setPayload(p); + return e; + } + + public static ChatEnvelope done(String conversationId) { + ChatEnvelope e = base(conversationId); + e.setDomain(ChatDomain.SYSTEM); + e.setType(ChatType.DONE); + e.setPayload(null); + return e; + } + + private static ChatEnvelope base(String conversationId) { + ChatEnvelope e = new ChatEnvelope<>(); + e.setConversationId(conversationId); + return e; + } +} + diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatType.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatType.java new file mode 100644 index 0000000..79807a9 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/ChatType.java @@ -0,0 +1,13 @@ +package tech.easyflow.core.chat.protocol; + +public enum ChatType { + THINKING, + MESSAGE, + TOOL_CALL, + TOOL_RESULT, + STATUS, + ERROR, + FORM_REQUEST, + FORM_CANCEL, + DONE +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/MessageRole.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/MessageRole.java new file mode 100644 index 0000000..2d6bfb4 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/MessageRole.java @@ -0,0 +1,53 @@ +package tech.easyflow.core.chat.protocol; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * 对话消息角色枚举 + */ +public enum MessageRole { + /** + * 用户角色:用户发送的消息 + */ + USER("user"), + + /** + * 助手角色:AI/机器人返回的消息 + */ + ASSISTANT("assistant"), + + /** + * 系统角色:系统级提示、日志、状态消息 + */ + SYSTEM("system"), + + /** + * 工具角色:工具自动发送/返回的消息 + */ + TOOL("tool"); + + private final String value; + + /** + * 私有构造方法,绑定枚举常量与对应值 + * @param value 数据库存储/协议传输的字符串值 + */ + MessageRole(String value) { + this.value = value; + } + + /** + * 获取枚举对应的字符串值(用于数据库持久化、协议传输) + * @JsonValue 注解:Jackson 序列化时,自动返回该值(避免序列化出枚举名称) + * @return 小写字符串(如 "user"、"assistant") + */ + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ErrorPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ErrorPayload.java new file mode 100644 index 0000000..be20f61 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ErrorPayload.java @@ -0,0 +1,40 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class ErrorPayload { + private String code; + private String message; + private Boolean retryable; + private Object detail; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Boolean getRetryable() { + return retryable; + } + + public void setRetryable(Boolean retryable) { + this.retryable = retryable; + } + + public Object getDetail() { + return detail; + } + + public void setDetail(Object detail) { + this.detail = detail; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormCancelPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormCancelPayload.java new file mode 100644 index 0000000..11c9881 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormCancelPayload.java @@ -0,0 +1,13 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class FormCancelPayload { + private String formId; + + public String getFormId() { + return formId; + } + + public void setFormId(String formId) { + this.formId = formId; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormRequestPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormRequestPayload.java new file mode 100644 index 0000000..bbc5705 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/FormRequestPayload.java @@ -0,0 +1,51 @@ +package tech.easyflow.core.chat.protocol.payload; + +import java.util.Map; + +public class FormRequestPayload { + private String formId; + private String title; + private String description; + private Map schema; // JSON Schema + private Map ui; + + public String getFormId() { + return formId; + } + + public void setFormId(String formId) { + this.formId = formId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Map getSchema() { + return schema; + } + + public void setSchema(Map schema) { + this.schema = schema; + } + + public Map getUi() { + return ui; + } + + public void setUi(Map ui) { + this.ui = ui; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageDeltaPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageDeltaPayload.java new file mode 100644 index 0000000..f14dcd1 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageDeltaPayload.java @@ -0,0 +1,14 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class MessageDeltaPayload { + + private String delta; + + public String getDelta() { + return delta; + } + + public void setDelta(String delta) { + this.delta = delta; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageFullPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageFullPayload.java new file mode 100644 index 0000000..e74bcee --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/MessageFullPayload.java @@ -0,0 +1,13 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class MessageFullPayload { + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/StatusPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/StatusPayload.java new file mode 100644 index 0000000..f50c077 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/StatusPayload.java @@ -0,0 +1,22 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class StatusPayload { + private String state; + private String reason; + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ThinkingPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ThinkingPayload.java new file mode 100644 index 0000000..49bad93 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ThinkingPayload.java @@ -0,0 +1,15 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class ThinkingPayload { + private String content; +// private String visibility; // hidden | visible + + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolCallPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolCallPayload.java new file mode 100644 index 0000000..0ab6ffc --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolCallPayload.java @@ -0,0 +1,34 @@ +package tech.easyflow.core.chat.protocol.payload; + +import java.util.Map; + +public class ToolCallPayload { + + private String toolCallId; + private String name; + private Map arguments; + + public String getToolCallId() { + return toolCallId; + } + + public void setToolCallId(String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getArguments() { + return arguments; + } + + public void setArguments(Map arguments) { + this.arguments = arguments; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolResultPayload.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolResultPayload.java new file mode 100644 index 0000000..375ca2f --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/payload/ToolResultPayload.java @@ -0,0 +1,31 @@ +package tech.easyflow.core.chat.protocol.payload; + +public class ToolResultPayload { + private String toolCallId; + private String status; // success | error + private Object result; + + public String getToolCallId() { + return toolCallId; + } + + public void setToolCallId(String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseEmitter.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseEmitter.java new file mode 100644 index 0000000..32625f0 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseEmitter.java @@ -0,0 +1,86 @@ +package tech.easyflow.core.chat.protocol.sse; + +import com.alibaba.fastjson.JSON; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.core.chat.protocol.ChatEnvelope; + +import java.io.IOException; +import java.time.Duration; + +public class ChatSseEmitter { + + private static final long DEFAULT_TIMEOUT = Duration.ofMinutes(5).toMillis(); + + private final SseEmitter emitter; + + public ChatSseEmitter() { + this(DEFAULT_TIMEOUT); + } + + public ChatSseEmitter(long timeoutMillis) { + this.emitter = new SseEmitter(timeoutMillis); + } + + public SseEmitter getEmitter() { + return emitter; + } + + /** 发送普通 ChatEnvelope(event: message) */ + public void send(ChatEnvelope envelope) { + send("message", envelope); + } + + /** 发送 error 事件 */ + public void sendError(ChatEnvelope envelope) { + send("error", envelope); + } + + /** 发送 done 事件并关闭 */ + public void sendDone(ChatEnvelope envelope) { + send("done", envelope); + complete(); + } + + /** 🔥 新增:发送并立即关闭 */ + public void sendAndClose(ChatEnvelope envelope) { + send("message", envelope); + ThreadPoolTaskExecutor threadPoolTaskExecutor = SpringContextUtil.getBean("sseThreadPool"); + threadPoolTaskExecutor.execute(() -> { + try { + Thread.sleep(500); + complete(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + } + + /** 通知前端保存该消息 */ + public void sendMessageNeedSave(ChatEnvelope envelope) { + send("needSaveMessage", envelope); + } + + /** SSE 底层发送 */ + private void send(String event, ChatEnvelope envelope) { + try { + String json = JSON.toJSONString(envelope); + emitter.send( + SseEmitter.event() + .name(event) + .data(json) + ); + } catch (IOException e) { + emitter.completeWithError(e); + } + } + + public void complete() { + emitter.complete(); + } + + public void completeWithError(Throwable ex) { + emitter.completeWithError(ex); + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseUtil.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseUtil.java new file mode 100644 index 0000000..428ad3e --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/sse/ChatSseUtil.java @@ -0,0 +1,97 @@ +package tech.easyflow.core.chat.protocol.sse; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.core.chat.protocol.ChatDomain; +import tech.easyflow.core.chat.protocol.ChatEnvelope; +import tech.easyflow.core.chat.protocol.ChatType; +import tech.easyflow.core.chat.protocol.payload.ErrorPayload; + +public class ChatSseUtil { + + private static final long DEFAULT_TIMEOUT = 1000 * 60 * 2; // 2分钟 + + /** + * 快速发送一次系统错误消息,并立即关闭 SSE + * + * @param conversationId 会话ID + * @param message 错误提示信息 + * @param code 错误码,可选 + * @return SseEmitter + */ + public static SseEmitter sendSystemError(String conversationId, String message, String code) { + ChatSseEmitter emitter = new ChatSseEmitter(DEFAULT_TIMEOUT); + + ChatEnvelope envelope = new ChatEnvelope<>(); +// envelope.setProtocol("easyflow-ai-chat"); +// envelope.setVersion("1.1"); + envelope.setDomain(ChatDomain.SYSTEM); + envelope.setType(ChatType.ERROR); + envelope.setConversationId(conversationId); + + ErrorPayload payload = new ErrorPayload(); + payload.setMessage(message); + payload.setCode(code != null ? code : "SYSTEM_ERROR"); + payload.setRetryable(false); + + envelope.setPayload(payload); + + // 发送并立即关闭 + emitter.sendAndClose(envelope); + + return emitter.getEmitter(); + } + + /** + * 快速发送一次系统错误消息(默认错误码) + */ + public static SseEmitter sendSystemError(String conversationId, String message) { + return sendSystemError(conversationId, message, null); + } + + + /** + * 快速发送任意 ChatEnvelope 消息并立即关闭 SSE + * + * @param conversationId 会话ID + * @param domain 消息 domain + * @param type 消息 type + * @param payload 消息 payload + * @param meta 可选 meta 信息 + * @return SseEmitter 已经发送并关闭 + */ + public static SseEmitter sendAndClose( + String conversationId, + ChatDomain domain, + ChatType type, + T payload, + Object meta + ) { + ChatSseEmitter emitter = new ChatSseEmitter(DEFAULT_TIMEOUT); + + ChatEnvelope envelope = new ChatEnvelope<>(); +// envelope.setProtocol("easyflow-ai-chat"); +// envelope.setVersion("1.1"); + envelope.setConversationId(conversationId); + envelope.setDomain(domain); + envelope.setType(type); + envelope.setPayload(payload); + envelope.setMeta(meta); + + // 发送并立即关闭 + emitter.sendAndClose(envelope); + + return emitter.getEmitter(); + } + + /** + * 快速发送任意 ChatEnvelope 消息,无 meta + */ + public static SseEmitter sendAndClose( + String conversationId, + ChatDomain domain, + ChatType type, + T payload + ) { + return sendAndClose(conversationId, domain, type, payload, null); + } +} diff --git a/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/websocket/ChatWebSocketSender.java b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/websocket/ChatWebSocketSender.java new file mode 100644 index 0000000..a165801 --- /dev/null +++ b/easyflow-commons/easyflow-common-chat-protocol/src/main/java/tech/easyflow/core/chat/protocol/websocket/ChatWebSocketSender.java @@ -0,0 +1,60 @@ +package tech.easyflow.core.chat.protocol.websocket; + +import com.alibaba.fastjson.JSON; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import tech.easyflow.core.chat.protocol.ChatEnvelope; + +import java.io.IOException; + +public class ChatWebSocketSender { + + private final WebSocketSession session; + + public ChatWebSocketSender(WebSocketSession session) { + this.session = session; + } + + /** + * 发送 ChatEnvelope 消息 + */ + public void send(ChatEnvelope envelope) throws IOException { + checkOpen(); + String json = JSON.toJSONString(envelope); + session.sendMessage(new TextMessage(json)); + } + + /** + * 发送 error 消息 + */ + public void sendError(ChatEnvelope envelope) throws IOException { + checkOpen(); + send(envelope); // 前端可根据 envelope.type 判断 error + } + + /** + * 发送 done 并关闭 WebSocket + */ + public void sendDone(ChatEnvelope envelope) throws IOException { + send(envelope); + close(); + } + + /** + * 关闭 WebSocket + */ + public void close() throws IOException { + if (session.isOpen()) { + session.close(); + } + } + + /** + * 检查 session 是否仍然打开 + */ + private void checkOpen() throws IOException { + if (!session.isOpen()) { + throw new IOException("WebSocket session is closed"); + } + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/pom.xml b/easyflow-commons/easyflow-common-file-storage/pom.xml new file mode 100644 index 0000000..7be294d --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-file-storage + easyflow-common-file-storage + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + tech.easyflow + easyflow-common-base + compile + + + + org.dromara.x-file-storage + x-file-storage-spring + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + com.squareup.okhttp3 + okhttp + + + + com.aliyun.oss + aliyun-sdk-oss + + + org.apache.httpcomponents + httpclient + + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageManager.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageManager.java new file mode 100644 index 0000000..1c32b0c --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageManager.java @@ -0,0 +1,55 @@ +package tech.easyflow.common.filestorage; + +import tech.easyflow.common.filestorage.xFileStorage.StorageConfig; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.filestorage.impl.LocalFileStorageServiceImpl; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +@Component("default") +public class FileStorageManager implements FileStorageService { + + @Override + public String save(MultipartFile file) { + return getService().save(file); + } + + @Override + public String save(MultipartFile file,String prePath) { + return getService().save(file,prePath); + } + + @Override + public void delete(String path) { + getService().delete(path); + } + + @Override + public String save(File file, String prePath) { + return getService().save(file, prePath); + } + + @Override + public InputStream readStream(String path) throws IOException { + return getService().readStream(path); + } + + @Override + public long getFileSize(String path) { + return getService().getFileSize(path); + } + + private FileStorageService getService() { + String type = StorageConfig.getInstance().getType(); + if (!StringUtils.hasText(type)) { + return SpringContextUtil.getBean(LocalFileStorageServiceImpl.class); + } else { + return SpringContextUtil.getBean(type); + } + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageService.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageService.java new file mode 100644 index 0000000..962c834 --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/FileStorageService.java @@ -0,0 +1,39 @@ +package tech.easyflow.common.filestorage; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public interface FileStorageService { + + + String save(MultipartFile file); + + + void delete(String path); + + /** + * 上传文件 + * @param file 文件 + * @param prePath 存储桶和文件名中间的路径(不用加斜杠) + * @return 文件url + */ + default String save(MultipartFile file, String prePath){ + return ""; + } + + default String save(File file, String prePath){ + return ""; + } + + InputStream readStream(String path) throws IOException; + + /** + * 获取文件大小 + * @param path + * @return 文件大小 单位字节 + */ + public long getFileSize(String path); +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/FileRecoderImpl.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/FileRecoderImpl.java new file mode 100644 index 0000000..7821dc5 --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/FileRecoderImpl.java @@ -0,0 +1,42 @@ +package tech.easyflow.common.filestorage.impl; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.springframework.stereotype.Service; + +/** + * 文件记录器 + */ +@Service +public class FileRecoderImpl implements FileRecorder { + @Override + public boolean save(FileInfo fileInfo) { + return true; + } + + @Override + public void update(FileInfo fileInfo) { + + } + + @Override + public FileInfo getByUrl(String url) { + return null; + } + + @Override + public boolean delete(String url) { + return true; + } + + @Override + public void saveFilePart(FilePartInfo filePartInfo) { + + } + + @Override + public void deleteFilePartByUploadId(String uploadId) { + + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/LocalFileStorageServiceImpl.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/LocalFileStorageServiceImpl.java new file mode 100644 index 0000000..205d9eb --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/LocalFileStorageServiceImpl.java @@ -0,0 +1,121 @@ +package tech.easyflow.common.filestorage.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.common.filestorage.FileStorageService; +import tech.easyflow.common.filestorage.utils.PathGeneratorUtil; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + + +@Component("local") +public class LocalFileStorageServiceImpl implements FileStorageService { + private static final Logger LOG = LoggerFactory.getLogger(LocalFileStorageServiceImpl.class); + + + @Value("${easyflow.storage.local.root:}") + private String root; + @Value("${easyflow.storage.local.prefix}") + private String prefix; + + @EventListener(ApplicationReadyEvent.class) + public void init() { + } + + + @Override + public String save(MultipartFile file) { + try { + String path = PathGeneratorUtil.generateUserPath(file.getOriginalFilename()); + File target = getLocalFile(path); + if (!target.getParentFile().exists() && !target.getParentFile().mkdirs()) { + LOG.error("创建文件失败: {} ", target.getParentFile()); + } + file.transferTo(target); + return prefix + path; + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } + + @Override + public InputStream readStream(String path) throws IOException { + File target = getLocalFile(path); + return Files.newInputStream(target.toPath()); + } + + @Override + public long getFileSize(String path) { + File target = null; + try { + target = getLocalFile(path); + } catch (IOException e) { + throw new RuntimeException("获取文件大小出错", e); + } + if (target.exists()) { + return target.length(); + } + return 0; + } + + @Override + public void delete(String path) { + try { + File file = getLocalFile(path); + Files.delete(file.toPath()); + } catch (IOException e) { + LOG.error("删除本地文件出错: {}", path, e); + throw new RuntimeException("删除本地文件出错:",e); + } + } + + /** + * 递归删除文件或目录(支持删除非空目录) + * @param file 要删除的文件或目录 + */ + private void deleteRecursively(File file) throws Exception { + if (file == null || !file.exists()) { + LOG.warn("文件/目录不存在: {}", file); + return; + } + + // 如果是目录,先递归删除子文件和子目录 + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { // 防止null(目录可能被其他进程修改) + for (File child : children) { + deleteRecursively(child); // 递归删除子内容 + } + } + } + + // 删除当前文件或空目录 + boolean deleted = file.delete(); + if (!deleted) { + throw new Exception("无法删除文件/目录: " + file.getAbsolutePath()); + } + } + + + private File getLocalFile(String path) throws IOException { + if (this.root == null || this.root.isEmpty()) { + throw new RuntimeException("请指定存储根目录"); + } + return new File(this.root, path.replace(prefix, "")); + } + + @Override + public String save(MultipartFile file, String prePath) { + return save(file); + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/XFIleStorageServiceImpl.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/XFIleStorageServiceImpl.java new file mode 100644 index 0000000..3ebe277 --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/impl/XFIleStorageServiceImpl.java @@ -0,0 +1,72 @@ +package tech.easyflow.common.filestorage.impl; + +import org.dromara.x.file.storage.core.FileInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.common.filestorage.FileStorageService; +import tech.easyflow.common.filestorage.utils.PathGeneratorUtil; +import tech.easyflow.common.util.OkHttpUtil; + +import java.io.*; + +@Component("xFileStorage") +public class XFIleStorageServiceImpl implements FileStorageService { + + private static final Logger LOG = LoggerFactory.getLogger(XFIleStorageServiceImpl.class); + + @Autowired + private org.dromara.x.file.storage.core.FileStorageService fileStorageService; + + @Override + public String save(MultipartFile file) { + FileInfo fileInfo = fileStorageService.of(file) + .setPath(PathGeneratorUtil.generateUserPath("")) + .setSaveFilename(file.getOriginalFilename()) + .setContentType(getFileContentType(file)) + .upload(); + return fileInfo == null ? "上传失败!" : fileInfo.getUrl(); + } + + @Override + public void delete(String path) { + fileStorageService.delete(path); + } + + @Override + public InputStream readStream(String fileUrl) { + return OkHttpUtil.getInputStream(fileUrl); + } + + /** + * 获取S3中文件的大小(单位:字节) + * @param path 文件路径 + * @return 文件大小(字节) + */ + @Override + public long getFileSize(String path) { + try { + return OkHttpUtil.getFileSize(path); + } catch (Exception e) { + LOG.error("获取文件大小失败", e); + throw new RuntimeException(e); + } + } + + /** + * 获取文件的 Content-Type + */ + public static String getFileContentType(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + String contentType = null; + if (originalFilename != null && originalFilename.toLowerCase().endsWith(".txt")) { + contentType = "text/plain; charset=utf-8"; + } else { + // 其他类型文件可以按需设置 + contentType = file.getContentType(); + } + return contentType; + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/utils/PathGeneratorUtil.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/utils/PathGeneratorUtil.java new file mode 100644 index 0000000..970831f --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/utils/PathGeneratorUtil.java @@ -0,0 +1,80 @@ +package tech.easyflow.common.filestorage.utils; + +import cn.dev33.satoken.stp.StpUtil; +import tech.easyflow.common.util.StringUtil; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +/** + * 路径生成工具类:年/月/日分别作为独立目录,格式为 "/年/月/日/{uuid}/文件名" + */ +public class PathGeneratorUtil { + + public static String generateUserPath(String fileName) { + return "/" + getAccountLoginIdOrCommons() + PathGeneratorUtil.generatePath(fileName); + } + + private static String getAccountLoginIdOrCommons() { + try { + String loginIdAsString = StpUtil.getLoginIdAsString(); + return StringUtil.hasText(loginIdAsString) ? loginIdAsString : "commons"; + } catch (Exception e) { + return "commons"; + } + } + + + /** + * 使用当前日期 + 自动生成UUID + 自定义文件名,生成路径 + * + * @param fileName 文件名(含后缀,如 "video.mp4") + * @return 示例:"/2024/10/15/1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed/video.mp4" + */ + public static String generatePath(String fileName) { + LocalDate currentDate = LocalDate.now(); + String uuid = UUID.randomUUID().toString(); + return buildPath(currentDate, uuid, fileName); + } + + /** + * 使用当前日期 - 1天 + * + * @return 示例:"yyyy/MM/dd"(如 2024/10/19) + */ + public static String generatePrePath() { + LocalDate yesterday = LocalDate.now().minusDays(2); + // 自定义格式为 "yyyy/MM/dd"(如 2024/10/19) + return yesterday.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + } + + /** + * 内部拼接路径核心方法(年/月/日独立目录) + */ + private static String buildPath(LocalDate date, String uuid, String fileName) { + int year = date.getYear(); // 年(如 2024) + int month = date.getMonthValue(); // 月(如 10,1-12) + int day = date.getDayOfMonth(); // 日(如 15) + + // 处理文件名(剔除可能包含的路径,只保留纯文件名) + String pureFileName = getPureFileName(fileName); + + // 拼接格式:/年/月/日/uuid/文件名 + return String.format("/%d/%d/%d/%s/%s", year, month, day, uuid, pureFileName); + } + + /** + * 提取纯文件名(避免文件名包含路径分隔符) + */ + public static String getPureFileName(String fileName) { + if (fileName == null) { + return ""; + } + // 处理 Windows(\)和 Linux(/)的路径分隔符 + int lastBackslash = fileName.lastIndexOf("\\"); + int lastSlash = fileName.lastIndexOf("/"); + int lastSeparator = Math.max(lastBackslash, lastSlash); + return lastSeparator == -1 ? fileName : fileName.substring(lastSeparator + 1); + } +} diff --git a/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/xFileStorage/StorageConfig.java b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/xFileStorage/StorageConfig.java new file mode 100644 index 0000000..1c1852a --- /dev/null +++ b/easyflow-commons/easyflow-common-file-storage/src/main/java/tech/easyflow/common/filestorage/xFileStorage/StorageConfig.java @@ -0,0 +1,26 @@ +package tech.easyflow.common.filestorage.xFileStorage; + +import tech.easyflow.common.util.SpringContextUtil; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "easyflow.storage") +public class StorageConfig { + + //支持 local、s3、xfile... + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public static StorageConfig getInstance() { + return SpringContextUtil.getBean(StorageConfig.class); + } + +} diff --git a/easyflow-commons/easyflow-common-options/pom.xml b/easyflow-commons/easyflow-common-options/pom.xml new file mode 100644 index 0000000..42dd933 --- /dev/null +++ b/easyflow-commons/easyflow-common-options/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-options + easyflow-common-options + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-autoconfigure + + + tech.easyflow + easyflow-common-base + compile + + + + + diff --git a/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptionStore.java b/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptionStore.java new file mode 100644 index 0000000..f2792d7 --- /dev/null +++ b/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptionStore.java @@ -0,0 +1,6 @@ +package tech.easyflow.common.options; + +public interface SysOptionStore { + void save(String key,Object value); + String get(String key); +} diff --git a/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptions.java b/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptions.java new file mode 100644 index 0000000..892de3a --- /dev/null +++ b/easyflow-commons/easyflow-common-options/src/main/java/tech/easyflow/common/options/SysOptions.java @@ -0,0 +1,54 @@ +package tech.easyflow.common.options; + + +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.util.StringUtil; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Component; + +@Component +public class SysOptions { + + private final SysOptionStore store; + + public SysOptions(ObjectProvider storeObjectProvider) { + this.store = storeObjectProvider.getIfAvailable(); + } + + public static void set(String key, String value) { + getInstance().store.save(key, value); + } + + public static String get(String key) { + return getInstance().store.get(key); + } + + public static String get(String key, String defaultValue) { + String value = get(key); + return StringUtil.hasText(value) ? value : defaultValue; + } + + public static Boolean getAsBoolean(String key) { + String value = get(key); + return StringUtil.hasText(value) ? Boolean.parseBoolean(value) : null; + } + + + public static Boolean getAsBoolean(String key, Boolean defaultValue) { + Boolean value = getAsBoolean(key); + return value != null ? value : defaultValue; + } + + + + private static SysOptions instance = null; + + public static SysOptions getInstance() { + if (instance == null) { + instance = SpringContextUtil.getBean(SysOptions.class); + } + return instance; + } + + +} diff --git a/easyflow-commons/easyflow-common-satoken/pom.xml b/easyflow-commons/easyflow-common-satoken/pom.xml new file mode 100644 index 0000000..5699768 --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-satoken + easyflow-common-satoken + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.netty + * + + + + + + + org.apache.commons + commons-pool2 + 2.11.1 + + + + io.netty + netty-codec + ${netty.version} + + + + tech.easyflow + easyflow-common-base + + + diff --git a/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/AdminSaTokenConfig.java b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/AdminSaTokenConfig.java new file mode 100644 index 0000000..2ca927c --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/AdminSaTokenConfig.java @@ -0,0 +1,29 @@ +package tech.easyflow.common.satoken.config; + +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * 复制此类并自行修改 + */ +@Configuration +public class AdminSaTokenConfig { + + public static final String LOGIN_TYPE = "easyflow"; + + @Bean + @Primary + public SaTokenConfig setSaTokenConfig() { + SaTokenConfig config = SaConfigs.defaultConfig(); + config.setTokenName(LOGIN_TYPE + "-token"); + config.setIsConcurrent(true); + config.setMaxLoginCount(3); + config.setTimeout(24 * 60 * 60L); + config.setTokenStyle("simple-uuid"); + StpUtil.stpLogic.setConfig(config); + return config; + } +} diff --git a/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaConfigs.java b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaConfigs.java new file mode 100644 index 0000000..19c6a64 --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaConfigs.java @@ -0,0 +1,23 @@ +package tech.easyflow.common.satoken.config; + +import cn.dev33.satoken.config.SaTokenConfig; + +public class SaConfigs { + + public static SaTokenConfig defaultConfig() { + SaTokenConfig config = new SaTokenConfig(); + config.setTokenName("easyflow-token"); + config.setTimeout(2592000L); + // 是否允许同一账号并发登录 (为 true 时允许一起登录,为 false 时新登录挤掉旧登录) + config.setIsConcurrent(false); + // 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token,为 false 时每次登录新建一个 token) + config.setIsShare(false); + // 是否尝试从 请求体 里读取 Token + config.setIsReadBody(false); + // 是否尝试从 cookie 里读取 Token,此值为 false 后,StpUtil.login(id) 登录时也不会再往前端注入Cookie + config.setIsReadCookie(false); + config.setIsPrint(false); + config.setIsLog(false); + return config; + } +} diff --git a/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaSessionForJacksonCustomized.java b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaSessionForJacksonCustomized.java new file mode 100644 index 0000000..967beba --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaSessionForJacksonCustomized.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.easyflow.common.satoken.config; + +import cn.dev33.satoken.session.SaSession; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties({"timeout"}) +public class SaSessionForJacksonCustomized extends SaSession { + + /** + * + */ + private static final long serialVersionUID = -7600983549653130681L; + + public SaSessionForJacksonCustomized() { + super(); + } + + /** + * 构建一个Session对象 + * @param id Session的id + */ + public SaSessionForJacksonCustomized(String id) { + super(id); + } + +} diff --git a/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaTokenDaoRedisJackson.java b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaTokenDaoRedisJackson.java new file mode 100644 index 0000000..dd29bd1 --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/config/SaTokenDaoRedisJackson.java @@ -0,0 +1,308 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.easyflow.common.satoken.config; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.strategy.SaStrategy; +import cn.dev33.satoken.util.SaFoxUtil; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@Configuration +@ConditionalOnProperty(prefix = "spring.data.redis", name = "host") +public class SaTokenDaoRedisJackson implements SaTokenDao { + + public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String TIME_PATTERN = "HH:mm:ss"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); + public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN); + + /** + * ObjectMapper 对象 (以 public 作用域暴露出此对象,方便开发者二次更改配置) + * + *

例如: + *

+     *      SaTokenDaoRedisJackson redisJackson = (SaTokenDaoRedisJackson) SaManager.getSaTokenDao();
+     *      redisJackson.objectMapper.xxx = xxx;
+     * 	
+ *

+ */ + public ObjectMapper objectMapper; + + /** + * String 读写专用 + */ + public StringRedisTemplate stringRedisTemplate; + + /** + * Object 读写专用 + */ + public RedisTemplate objectRedisTemplate; + + /** + * 标记:是否已初始化成功 + */ + public boolean isInit; + + @Autowired + public void init(RedisConnectionFactory connectionFactory) { + // 如果已经初始化成功了,就立刻退出,不重复初始化 + if(this.isInit) { + return; + } + + // 指定相应的序列化方案 + StringRedisSerializer keySerializer = new StringRedisSerializer(); + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); + + // 通过反射获取Mapper对象, 增加一些配置, 增强兼容性 + try { + Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper"); + field.setAccessible(true); + this.objectMapper = (ObjectMapper) field.get(valueSerializer); + + // 配置[忽略未知字段] + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // 配置[时间类型转换] + JavaTimeModule timeModule = new JavaTimeModule(); + + // LocalDateTime序列化与反序列化 + timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); + timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); + + // LocalDate序列化与反序列化 + timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER)); + timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)); + + // LocalTime序列化与反序列化 + timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER)); + timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER)); + + this.objectMapper.registerModule(timeModule); + + // 重写 SaSession 生成策略 + SaStrategy.instance.createSession = (sessionId) -> new SaSessionForJacksonCustomized(sessionId); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + // 构建StringRedisTemplate + StringRedisTemplate stringTemplate = new StringRedisTemplate(); + stringTemplate.setConnectionFactory(connectionFactory); + stringTemplate.afterPropertiesSet(); + + // 构建RedisTemplate + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(keySerializer); + template.setHashKeySerializer(keySerializer); + template.setValueSerializer(valueSerializer); + template.setHashValueSerializer(valueSerializer); + template.afterPropertiesSet(); + + // 开始初始化相关组件 + this.stringRedisTemplate = stringTemplate; + this.objectRedisTemplate = template; + + // 打上标记,表示已经初始化成功,后续无需再重新初始化 + this.isInit = true; + } + + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + stringRedisTemplate.opsForValue().set(key, value); + } else { + stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + return stringRedisTemplate.getExpire(key); + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return objectRedisTemplate.opsForValue().get(key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + objectRedisTemplate.opsForValue().set(key, object); + } else { + objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS); + } + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + objectRedisTemplate.delete(key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + return objectRedisTemplate.getExpire(key); + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Set keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*"); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + } +} diff --git a/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/util/SaTokenUtil.java b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/util/SaTokenUtil.java new file mode 100644 index 0000000..9f64c94 --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/java/tech/easyflow/common/satoken/util/SaTokenUtil.java @@ -0,0 +1,12 @@ +package tech.easyflow.common.satoken.util; + +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.entity.LoginAccount; +import cn.dev33.satoken.stp.StpUtil; + +public class SaTokenUtil { + + public static LoginAccount getLoginAccount() { + return StpUtil.getSession().getModel(Constants.LOGIN_USER_KEY, LoginAccount.class); + } +} diff --git a/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring.factories b/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..3a4a551 --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Depends On Database Initialization Detectors +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + tech.easyflow.common.satoken.config.SaTokenDaoRedisJackson diff --git a/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8b357cb --- /dev/null +++ b/easyflow-commons/easyflow-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +tech.easyflow.common.satoken.config.SaTokenDaoRedisJackson \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-web/pom.xml b/easyflow-commons/easyflow-common-web/pom.xml new file mode 100644 index 0000000..cc51927 --- /dev/null +++ b/easyflow-commons/easyflow-common-web/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + tech.easyflow + easyflow-commons + ${revision} + + + easyflow-common-web + + + + tech.easyflow + easyflow-common-satoken + + + cn.dev33 + sa-token-spring-boot3-starter + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + com.alibaba + fastjson + + + + org.slf4j + slf4j-api + + + + tech.easyflow + easyflow-common-base + + + + tech.easyflow + easyflow-common-ai + + + + jakarta.validation + jakarta.validation-api + + + + + diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseController.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseController.java new file mode 100644 index 0000000..1d94101 --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseController.java @@ -0,0 +1,6 @@ +package tech.easyflow.common.web.controller; + +public class BaseController { + + +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseCurdController.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseCurdController.java new file mode 100644 index 0000000..b355ab1 --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/BaseCurdController.java @@ -0,0 +1,318 @@ +package tech.easyflow.common.web.controller; + +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.query.SqlOperators; +import com.mybatisflex.core.service.IService; +import com.mybatisflex.core.table.TableInfo; +import com.mybatisflex.core.table.TableInfoFactory; +import com.mybatisflex.core.util.StringUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.tree.Tree; +import tech.easyflow.common.util.SqlOperatorsUtil; +import tech.easyflow.common.util.SqlUtil; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.exceptions.ProgramException; +import tech.easyflow.common.web.jsonbody.JsonBody; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.math.BigInteger; +import java.util.*; + +public class BaseCurdController, M> extends BaseController { + + + protected final S service; + + public BaseCurdController(S service) { + this.service = service; + } + + /** + * 添加(保存)数据 + * + * @param entity 表生成内容配置 + * @return {@code Result.errorCode == 0} 添加成功,否则添加失败 + */ + @PostMapping("save") + public Result save(@JsonBody M entity) { + Result result = onSaveOrUpdateBefore(entity, true); + if (result != null) return result; + + if (entity == null) { + throw new NullPointerException("entity is null"); + } + LoginAccount loginAccount = SaTokenUtil.getLoginAccount(); + commonFiled(entity, loginAccount.getId(), loginAccount.getTenantId(), loginAccount.getDeptId()); + service.save(entity); + onSaveOrUpdateAfter(entity, true); + TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass()); + Object[] pkArgs = tableInfo.buildPkSqlArgs(entity); + Map resultMap = new HashMap<>(); + resultMap.put("id", pkArgs); + return Result.ok(resultMap); + } + + /** + * 根据主键删除数据,id 需通过 json 传入,例如: + *
+     * {
+     *   "id":123
+     * }
+     * 
+     *
+     *
+     * @param id 主键
+     * @return {@code Result.errorCode == 0} 删除成功,否则删除失败
+     */
+    @PostMapping("remove")
+    @Transactional
+    public Result remove(@JsonBody(value = "id", required = true) Serializable id) {
+        List ids = Collections.singletonList(id);
+        Result result = onRemoveBefore(ids);
+        if (result != null) return result;
+        boolean success = service.removeById(id);
+        onRemoveAfter(ids);
+        return Result.ok(success);
+    }
+
+
+    /**
+     * 根据多个主键删数据内容,id 需通过 json 传入,例如:
+     * 
+     * {
+     *   "ids":[123, 234, 222]
+     * }
+     * 
+     *
+     * @param ids 主键
+     * @return {@code Result.errorCode == 0} 删除成功,否则删除失败
+     */
+    @PostMapping("removeBatch")
+    @Transactional
+    public Result removeBatch(@JsonBody(value = "ids", required = true) Collection ids) {
+        if (ids == null || ids.isEmpty()) {
+            return Result.fail("id不能为空");
+        }
+        Result result = onRemoveBefore(ids);
+        if (result != null) return result;
+        boolean success = service.removeByIds(ids);
+        onRemoveAfter(ids);
+        return Result.ok(success);
+    }
+
+    /**
+     * 根据主键更新内容
+     *
+     * @param entity 实体类数据
+     * @return {@code Result.errorCode == 0} 更新成功,否则更新失败
+     */
+    @PostMapping("update")
+    public Result update(@JsonBody M entity) {
+        Result result = onSaveOrUpdateBefore(entity, false);
+        if (result != null) return result;
+        service.updateById(entity);
+        onSaveOrUpdateAfter(entity, false);
+        return Result.ok();
+    }
+
+    /**
+     * 查询所有所有数据
+     *
+     * @return 所有数据
+     */
+    @GetMapping("list")
+    public Result> list(M entity, Boolean asTree, String sortKey, String sortType) {
+        QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
+        queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
+        List list = Tree.tryToTree(service.list(queryWrapper), asTree);
+        return Result.ok(list);
+    }
+
+
+    /**
+     * 根据表主键查询数据详情。
+     *
+     * @param id 主键值
+     * @return 内容详情
+     */
+    @GetMapping("detail")
+    public Result detail(String id) {
+        if (tech.easyflow.common.util.StringUtil.noText(id)) {
+            throw new BusinessException("id must not be null");
+        }
+        return Result.ok(service.getById(id));
+    }
+
+
+    /**
+     * 分页查询数据列表
+     *
+     * @param request    查询数据
+     * @param sortKey    排序字段
+     * @param sortType   排序方式 asc | desc
+     * @param pageNumber 当前页码
+     * @param pageSize   每页的数据量
+     * @return 查询的结果集
+     */
+    @GetMapping("page")
+    public Result> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
+        if (pageNumber == null || pageNumber < 1) {
+            pageNumber = 1L;
+        }
+        if (pageSize == null || pageSize < 1) {
+            pageSize = 10L;
+        }
+
+        QueryWrapper queryWrapper = buildQueryWrapper(request);
+        queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
+        return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
+    }
+
+
+    protected QueryWrapper buildQueryWrapper(HttpServletRequest request) {
+
+        QueryWrapper queryWrapper = new QueryWrapper();
+
+        Map parameterMap = request.getParameterMap();
+        if (parameterMap == null || parameterMap.isEmpty()) {
+            return queryWrapper;
+        }
+
+        String[] isQueryOrs = parameterMap.get("isQueryOr");
+        boolean isQueryOrBool = false;
+
+        if (isQueryOrs != null && isQueryOrs.length > 0) {
+            String isQueryOr = isQueryOrs[0];
+            isQueryOrBool = "true".equals(isQueryOr);
+        }
+
+        Map propertyColumnMapping = TableInfoFactory.ofEntityClass(getEntityClass())
+                .getPropertyColumnMapping();
+        for (Map.Entry entry : parameterMap.entrySet()) {
+            String paramKey = entry.getKey();
+            if (StringUtil.hasText(paramKey) && !paramKey.endsWith(OperatorBuilder.operatorSuffix) && propertyColumnMapping.containsKey(paramKey)) {
+                String columnName = propertyColumnMapping.get(paramKey);
+                String[] values = entry.getValue();
+                if (values != null && values.length > 0 && StringUtil.hasText(values[0])) {
+                    String op = request.getParameter(paramKey + OperatorBuilder.operatorSuffix);
+                    if (StringUtil.hasText(op)) {
+                        OperatorBuilder.buildOperator(queryWrapper, columnName, op.trim(), values);
+                    } else {
+                        if (values.length == 2) {
+                            queryWrapper.between(columnName, values[0], values[1]);
+                        } else {
+                            String value = values[0];
+                            if (StringUtil.isNumeric(value)) {
+                                queryWrapper.eq(columnName, value);
+                            } else {
+                                if (isQueryOrBool) {
+                                    queryWrapper.or(columnName + " like " + "'%" + value + "%' ");
+                                } else {
+                                    queryWrapper.like(columnName, value);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return queryWrapper;
+    }
+
+    protected Class getEntityClass() {
+        Type type = getClass().getGenericSuperclass();
+        if (type instanceof ParameterizedType) {
+            return (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
+        }
+
+        return null;
+    }
+
+
+    /**
+     * 方便子类复写,构建自己的 SqlOperators
+     *
+     * @param entity 实体类
+     * @return SqlOperators
+     */
+    protected SqlOperators buildOperators(M entity) {
+        return entity == null ? SqlOperators.empty() : SqlOperatorsUtil.build(entity.getClass());
+    }
+
+    protected String getDefaultOrderBy() {
+        return "id desc";
+    }
+
+    /**
+     * 方便子类复写,构建自己的 orderBy
+     *
+     * @param sortKey        排序字段
+     * @param sortType       排序类型
+     * @param defaultOrderBy 默认方式内容
+     * @return orderBy 的内容,返回 null 或者 空字符串,表示不参与排序
+     */
+    protected String buildOrderBy(String sortKey, String sortType, String defaultOrderBy) {
+        sortKey = StringUtil.camelToUnderline(sortKey);
+        return SqlUtil.buildOrderBy(sortKey, sortType, defaultOrderBy);
+    }
+
+    protected Page queryPage(Page page, QueryWrapper queryWrapper) {
+        return service.page(page, queryWrapper);
+    }
+
+    protected Result onSaveOrUpdateBefore(M entity, boolean isSave) {
+        return null;
+    }
+
+    protected void onSaveOrUpdateAfter(M entity, boolean isSave) {
+        //void
+    }
+
+    protected Result onRemoveBefore(Collection ids) {
+        return null;
+    }
+
+    protected void onRemoveAfter(Collection ids) {
+        //void
+    }
+
+    protected void commonFiled(Object t, BigInteger userId, BigInteger tenantId, BigInteger deptId) {
+        Method[] methods = t.getClass().getMethods();
+        try {
+            for (Method m : methods) {
+                String name = m.getName();
+                if ("setDeptId".equals(name)) {
+                    m.invoke(t, deptId);
+                }
+                if ("setTenantId".equals(name)) {
+                    m.invoke(t, tenantId);
+                }
+                if ("setCreatedBy".equals(name)) {
+                    m.invoke(t, userId);
+                }
+                if ("setModifiedBy".equals(name)) {
+                    m.invoke(t, userId);
+                }
+                if ("setCreated".equals(name)) {
+                    m.invoke(t, new Date());
+                }
+                if ("setModified".equals(name)) {
+                    m.invoke(t, new Date());
+                }
+            }
+        } catch (Exception e) {
+            throw new ProgramException("commonFiled反射出错:" + e.getMessage());
+        }
+    }
+}
diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/OperatorBuilder.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/OperatorBuilder.java
new file mode 100644
index 0000000..b9fbc54
--- /dev/null
+++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/OperatorBuilder.java
@@ -0,0 +1,81 @@
+package tech.easyflow.common.web.controller;
+
+import com.mybatisflex.core.query.QueryWrapper;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OperatorBuilder {
+
+    public static final String operatorSuffix = "__op";
+    public static final Set supportedOperators = new HashSet<>(Arrays.asList(
+            "eq", "ne", "gt", "ge", "lt", "le",
+            "like", "likeLeft", "likeRight", "notLike", "notLikeLeft", "notLikeRight",
+            "between", "notBetween", "in", "notIn"
+            , "isNull", "isNotNull"));
+
+
+    public static void buildOperator(QueryWrapper queryWrapper, String columnName, String op, String[] values) {
+        if (!supportedOperators.contains(op)) {
+            return;
+        }
+        switch (op) {
+            case "eq":
+                queryWrapper.eq(columnName, values[0]);
+                break;
+            case "ne":
+                queryWrapper.ne(columnName, values[0]);
+                break;
+            case "gt":
+                queryWrapper.gt(columnName, values[0]);
+                break;
+            case "ge":
+                queryWrapper.ge(columnName, values[0]);
+                break;
+            case "lt":
+                queryWrapper.lt(columnName, values[0]);
+                break;
+            case "le":
+                queryWrapper.le(columnName, values[0]);
+                break;
+            case "like":
+                queryWrapper.like(columnName, values[0]);
+                break;
+            case "likeLeft":
+                queryWrapper.likeLeft(columnName, values[0]);
+                break;
+            case "likeRight":
+                queryWrapper.likeRight(columnName, values[0]);
+            case "notLike":
+                queryWrapper.notLike(columnName, values[0]);
+                break;
+            case "notLikeLeft":
+                queryWrapper.notLikeLeft(columnName, values[0]);
+                break;
+            case "notLikeRight":
+                queryWrapper.notLikeRight(columnName, values[0]);
+                break;
+            case "between":
+                queryWrapper.between(columnName, values[0], values[1]);
+                break;
+            case "notBetween":
+                queryWrapper.notBetween(columnName, values[0], values[1]);
+                break;
+            case "in":
+                queryWrapper.in(columnName, values);
+                break;
+            case "notIn":
+                queryWrapper.notIn(columnName, values);
+                break;
+            case "isNull":
+                queryWrapper.isNull(columnName);
+                break;
+            case "isNotNull":
+                queryWrapper.isNotNull(columnName);
+                break;
+        }
+    }
+
+
+}
diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/WhiteLabelPageController.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/WhiteLabelPageController.java
new file mode 100644
index 0000000..5d71960
--- /dev/null
+++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/controller/WhiteLabelPageController.java
@@ -0,0 +1,26 @@
+package tech.easyflow.common.web.controller;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import tech.easyflow.common.domain.Result;
+
+/**
+ * @ignore
+ */
+@RestController
+@RequestMapping
+public class WhiteLabelPageController implements ErrorController {
+
+    /**
+     * 解决 Whitelabel Error Page
+     */
+    @RequestMapping("/error")
+    public Result error(HttpServletRequest request) {
+        Integer statusCode = (Integer) request.getAttribute("jakarta.servlet.error.status_code");
+        HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
+        return Result.fail(httpStatus.value(), httpStatus.getReasonPhrase());
+    }
+}
diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogAspect.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogAspect.java
new file mode 100644
index 0000000..347551f
--- /dev/null
+++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogAspect.java
@@ -0,0 +1,163 @@
+//package tech.easyflow.common.web.devlog;
+//
+//
+//import com.alibaba.fastjson2.JSON;
+//import com.alibaba.fastjson2.JSONWriter;
+//import com.alibaba.fastjson2.filter.PropertyFilter;
+//import com.mybatisflex.core.util.ClassUtil;
+//import com.mybatisflex.core.util.StringUtil;
+//import jakarta.servlet.http.HttpServletRequest;
+//import org.apache.ibatis.javassist.*;
+//import org.aspectj.lang.ProceedingJoinPoint;
+//import org.aspectj.lang.annotation.Around;
+//import org.aspectj.lang.annotation.Aspect;
+//import org.aspectj.lang.annotation.Pointcut;
+//import org.aspectj.lang.reflect.MethodSignature;
+//import org.springframework.context.annotation.Profile;
+//import org.springframework.stereotype.Component;
+//import org.springframework.web.context.request.RequestContextHolder;
+//import org.springframework.web.context.request.ServletRequestAttributes;
+//import org.springframework.web.servlet.ModelAndView;
+//
+//import java.lang.reflect.Method;
+//import java.util.Enumeration;
+//import java.util.StringJoiner;
+//
+//@Aspect
+//@Component
+//@Profile({"dev", "test"})
+//public class DevLogAspect {
+//
+//    private static final int maxOutputLengthOfParaValue = 512;
+//    private static ClassPool classPool = ClassPool.getDefault();
+//
+//    static {
+//        classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
+//    }
+//
+//
+//    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) || execution(* tech.easyflow.common.web.controller.BaseCurdController.*(..))")
+//    public void webLog() {
+//    }
+//
+//    @Around("webLog()")
+//    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+//        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+//        HttpServletRequest request = attributes.getRequest();
+//
+//        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
+//        Class controllerClass = signature.getDeclaringType();
+//        Method method = signature.getMethod();
+//
+//        String url = request.getRequestURL().toString();
+//        String param = getRequestParamsString(request);
+//
+//        int lineNumber = getLineNumber(controllerClass, method);
+//
+//
+//        long startTime = System.currentTimeMillis();
+//        Object result = null;
+//        try {
+//            result = proceedingJoinPoint.proceed();
+//        } finally {
+//            String logInfo = "\n" +
+//                    "+========================================= Start ==========================================\n" +
+//                    "| Request        : " + request.getMethod() + " " + url + "\n" +
+//                    "| Request Params : " + param + "\n" +
+//                    "| Request IP     : " + request.getRemoteAddr() + "\n" +
+//                    "| Controller     : " + signature.getDeclaringTypeName() + "." + "(" + controllerClass.getSimpleName() + ".java:" + lineNumber + ")" + "\n" +
+//                    "| Method         : " + method.getName() + buildParamsString(method) + "\n" +
+//                    "| Response       : " + getResponseText(result) + "\n" +
+//                    "| Elapsed Time   : " + (System.currentTimeMillis() - startTime) + " ms" + "\n" +
+//                    "+========================================== End ===========================================\n";
+//            System.out.println(logInfo);
+//        }
+//        return result;
+//    }
+//
+//    private static String getResponseText(Object result) {
+//        if (result instanceof ModelAndView && ((ModelAndView) result).isReference()) {
+//            return ((ModelAndView) result).getViewName();
+//        }
+//
+//        String originalText;
+//        if (result instanceof String) {
+//            originalText = (String) result;
+//        } else {
+//            originalText = JSON.toJSONString(result);
+//        }
+//
+//        if (StringUtil.noText(originalText)) {
+//            return "";
+//        }
+//
+//        originalText = originalText.replace("\n", "");
+//
+//        if (originalText.length() > 100) {
+//            return originalText.substring(0, 100) + "...";
+//        }
+//
+//        try {
+//            // 使用PropertyFilter过滤掉timeout字段
+//            PropertyFilter filter = (object, name, value) -> !"timeout".equals(name);
+//
+//            String resultStr = JSON.toJSONString(result,
+//                    filter,
+//                    JSONWriter.Feature.WriteMapNullValue,
+//                    JSONWriter.Feature.IgnoreNonFieldGetter);
+//            return resultStr;
+//        } catch (Exception e) {
+//            return "[Serialization Error: " + e.getMessage() + "]";
+//        }
+//    }
+//
+//
+//    private String buildParamsString(Method method) {
+//        StringJoiner joiner = new StringJoiner(", ", "(", ")");
+//        for (Class parameterType : method.getParameterTypes()) {
+//            joiner.add(parameterType.getSimpleName());
+//        }
+//        return joiner.toString();
+//    }
+//
+//
+//    private int getLineNumber(Class controllerClass, Method method) throws NotFoundException {
+//        CtClass ctClass = classPool.get(ClassUtil.getUsefulClass(controllerClass).getName());
+//        classPool.get(ClassUtil.getUsefulClass(controllerClass).getName());
+//        String desc = DevLogUtil.getMethodDescWithoutName(method);
+//        CtMethod ctMethod = ctClass.getMethod(method.getName(), desc);
+//        return ctMethod.getMethodInfo().getLineNumber(0);
+//    }
+//
+//
+//    private String getRequestParamsString(HttpServletRequest request) {
+//        StringBuilder sb = new StringBuilder();
+//        Enumeration e = request.getParameterNames();
+//        if (e.hasMoreElements()) {
+//            while (e.hasMoreElements()) {
+//                String name = e.nextElement();
+//                String[] values = request.getParameterValues(name);
+//                if (values.length == 1) {
+//                    sb.append(name).append("=");
+//                    if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) {
+//                        sb.append(values[0], 0, maxOutputLengthOfParaValue).append("...");
+//                    } else {
+//                        sb.append(values[0]);
+//                    }
+//                } else {
+//                    sb.append(name).append("[]={");
+//                    for (int i = 0; i < values.length; i++) {
+//                        if (i > 0) {
+//                            sb.append(",");
+//                        }
+//                        sb.append(values[i]);
+//                    }
+//                    sb.append("}");
+//                }
+//                sb.append("  ");
+//            }
+//        }
+//        return sb.toString();
+//    }
+//
+//}
diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogUtil.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogUtil.java
new file mode 100644
index 0000000..339d3f2
--- /dev/null
+++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/devlog/DevLogUtil.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tech.easyflow.common.web.devlog; + + +import java.lang.reflect.Method; + + +/** + * 参考 https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java + */ +public final class DevLogUtil { + + /** + * void(V). + */ + public static final char JVM_VOID = 'V'; + + /** + * boolean(Z). + */ + public static final char JVM_BOOLEAN = 'Z'; + + /** + * byte(B). + */ + public static final char JVM_BYTE = 'B'; + + /** + * char(C). + */ + public static final char JVM_CHAR = 'C'; + + /** + * double(D). + */ + public static final char JVM_DOUBLE = 'D'; + + /** + * float(F). + */ + public static final char JVM_FLOAT = 'F'; + + /** + * int(I). + */ + public static final char JVM_INT = 'I'; + + /** + * long(J). + */ + public static final char JVM_LONG = 'J'; + + /** + * short(S). + */ + public static final char JVM_SHORT = 'S'; + + + /** + * get class desc. + * boolean[].class => "[Z" + * Object.class => "Ljava/lang/Object;" + * + * @param c class. + * @return desc. + */ + public static String getDesc(Class c) { + StringBuilder ret = new StringBuilder(); + + while (c.isArray()) { + ret.append('['); + c = c.getComponentType(); + } + + if (c.isPrimitive()) { + String t = c.getName(); + if ("void".equals(t)) { + ret.append(JVM_VOID); + } else if ("boolean".equals(t)) { + ret.append(JVM_BOOLEAN); + } else if ("byte".equals(t)) { + ret.append(JVM_BYTE); + } else if ("char".equals(t)) { + ret.append(JVM_CHAR); + } else if ("double".equals(t)) { + ret.append(JVM_DOUBLE); + } else if ("float".equals(t)) { + ret.append(JVM_FLOAT); + } else if ("int".equals(t)) { + ret.append(JVM_INT); + } else if ("long".equals(t)) { + ret.append(JVM_LONG); + } else if ("short".equals(t)) { + ret.append(JVM_SHORT); + } + } else { + ret.append('L'); + ret.append(c.getName().replace('.', '/')); + ret.append(';'); + } + return ret.toString(); + } + + + /** + * get method desc. + * "(I)I", "()V", "(Ljava/lang/String;Z)V" + * + * @param m method. + * @return desc. + */ + public static String getMethodDescWithoutName(Method m) { + StringBuilder ret = new StringBuilder(); + ret.append('('); + Class[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append(getDesc(m.getReturnType())); + return ret.toString(); + } + +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/GlobalErrorResolver.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/GlobalErrorResolver.java new file mode 100644 index 0000000..e520a4b --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/GlobalErrorResolver.java @@ -0,0 +1,46 @@ +package tech.easyflow.common.web.error; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import tech.easyflow.common.domain.Result; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import jakarta.validation.ConstraintViolationException; +import tech.easyflow.common.web.exceptions.BusinessException; + +public class GlobalErrorResolver implements HandlerExceptionResolver { + + private static final Logger LOG = LoggerFactory.getLogger(GlobalErrorResolver.class); + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + ex.printStackTrace(); + Result error; + if (ex instanceof MissingServletRequestParameterException) { + error = Result.fail(1, ((MissingServletRequestParameterException) ex).getParameterName() + " 不能为空."); + } else if (ex instanceof NotLoginException) { + response.setStatus(401); + error = Result.fail(401, "请登录"); + } else if (ex instanceof NotPermissionException || ex instanceof NotRoleException) { + error = Result.fail(4010, "无权操作"); + } else if (ex instanceof ConstraintViolationException) { + error = Result.fail(400, ex.getMessage()); + } else if (ex instanceof BusinessException) { + error = Result.fail(1, ex.getMessage()); + } else { + LOG.error(ex.toString(), ex); + error = Result.fail(1, "错误信息:" + ex.getMessage()); + } + JSONObject object = JSON.parseObject(JSON.toJSONString(error)); + return new ModelAndView(new JakartaJsonView()) + .addAllObjects(object); + } +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/JakartaJsonView.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/JakartaJsonView.java new file mode 100644 index 0000000..aecea1e --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/error/JakartaJsonView.java @@ -0,0 +1,25 @@ +package tech.easyflow.common.web.error; + +import com.alibaba.fastjson2.JSON; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.View; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class JakartaJsonView implements View { + @Override + public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + if (model != null) { + String jsonString = JSON.toJSONString(model); + try (ServletOutputStream out = response.getOutputStream()) { + out.write(jsonString.getBytes(StandardCharsets.UTF_8)); + out.flush(); + } + } + } +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/BusinessException.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/BusinessException.java new file mode 100644 index 0000000..6fbd61c --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/BusinessException.java @@ -0,0 +1,14 @@ +package tech.easyflow.common.web.exceptions; + +/** + * 业务报错 + */ +public class BusinessException extends RuntimeException { + + public BusinessException() { + } + + public BusinessException(String msg) { + super(msg); + } +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ParamException.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ParamException.java new file mode 100644 index 0000000..eb61012 --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ParamException.java @@ -0,0 +1,14 @@ +package tech.easyflow.common.web.exceptions; + +/** + * 参数报错 + */ +public class ParamException extends RuntimeException { + + public ParamException() { + } + + public ParamException(String msg) { + super(msg); + } +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ProgramException.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ProgramException.java new file mode 100644 index 0000000..d54542e --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/exceptions/ProgramException.java @@ -0,0 +1,14 @@ +package tech.easyflow.common.web.exceptions; + +/** + * 程序报错 + */ +public class ProgramException extends RuntimeException { + + public ProgramException() { + } + + public ProgramException(String msg) { + super(msg); + } +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBody.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBody.java new file mode 100644 index 0000000..c49055c --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBody.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.easyflow.common.web.jsonbody; + +import java.lang.annotation.*; + +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface JsonBody { + + /** + * 自定义前缀 + */ + String value() default ""; + + /** + * 是否跳过转换异常 + */ + boolean skipConvertError() default true; + + + boolean required() default false; + + + String defaultValue() default ""; + +} \ No newline at end of file diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyArgumentResolver.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyArgumentResolver.java new file mode 100644 index 0000000..ebadf6a --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyArgumentResolver.java @@ -0,0 +1,93 @@ +package tech.easyflow.common.web.jsonbody; + +import jakarta.servlet.http.HttpServletRequest; +import tech.easyflow.common.util.RequestUtil; +import com.mybatisflex.core.util.ConvertUtil; +import com.mybatisflex.core.util.StringUtil; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Component +public class JsonBodyArgumentResolver implements HandlerMethodArgumentResolver, SmartInitializingSingleton { + + private RequestMappingHandlerAdapter requestMappingHandlerAdapter; + + + public JsonBodyArgumentResolver(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { + this.requestMappingHandlerAdapter = requestMappingHandlerAdapter; + } + + @Override + public void afterSingletonsInstantiated() { + List argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); + ArrayList resolvers = new ArrayList<>(Objects.requireNonNull(argumentResolvers)); + resolvers.add(0, this); + requestMappingHandlerAdapter.setArgumentResolvers(resolvers); + } + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(JsonBody.class); + } + + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer + , NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + + JsonBody jsonBody = parameter.getParameterAnnotation(JsonBody.class); + Class paraClass = parameter.getParameterType(); + + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + if (request == null) { + return null; + } + + Object jsonObjectOrArray = RequestUtil.readJsonObjectOrArray(request); + + Object result = null; + Type paraType = parameter.getGenericParameterType(); + if (paraType instanceof TypeVariable) { + Type variableRawType = JsonBodyParser.getTypeVariableRawType( + parameter.getContainingClass(), ((TypeVariable) paraType)); + if (variableRawType != null) { + paraClass = (Class) variableRawType; + paraType = variableRawType; + } + } + try { + result = JsonBodyParser.parseJsonBody(jsonObjectOrArray, paraClass, paraType, jsonBody.value()); + } catch (Exception e) { + if (jsonBody.skipConvertError()) { + //ignore + } else { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + if (result == null && StringUtil.hasText(jsonBody.defaultValue())) { + result = ConvertUtil.convert(jsonBody.defaultValue(), paraClass); + } + + if ((result == null) && jsonBody.required()) { + throw new IllegalArgumentException(jsonBody.value() + " must not be null or blank"); + } + + return result; + } + + +} diff --git a/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyParser.java b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyParser.java new file mode 100644 index 0000000..6e17e62 --- /dev/null +++ b/easyflow-commons/easyflow-common-web/src/main/java/tech/easyflow/common/web/jsonbody/JsonBodyParser.java @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.easyflow.common.web.jsonbody; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.mybatisflex.core.util.ConvertUtil; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class JsonBodyParser { + + private static final String startOfArray = "["; + private static final String endOfArray = "]"; + + + /** + * 获取方法里的泛型参数 T 对于的真实的 Class 类 + * + * @param defClass + * @param typeVariable + * @return + */ + public static Type getTypeVariableRawType(Class defClass, TypeVariable typeVariable) { + Type type = defClass.getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); + if (typeArguments.length == 1) { + return typeArguments[0]; + } else if (typeArguments.length > 1) { + TypeVariable[] typeVariables = typeVariable.getGenericDeclaration().getTypeParameters(); + for (int i = 0; i < typeVariables.length; i++) { + if (typeVariable.getName().equals(typeVariables[i].getName())) { + return typeArguments[i]; + } + } + } + } + return null; + } + + + public static Object parseJsonBody(Object jsonObjectOrArray, Class paraClass, Type paraType, String jsonKey) throws InstantiationException, IllegalAccessException { + if (jsonObjectOrArray == null) { + return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null; + } + if (Collection.class.isAssignableFrom(paraClass) || paraClass.isArray()) { + return parseArray(jsonObjectOrArray, paraClass, paraType, jsonKey); + } else { + return parseObject((JSONObject) jsonObjectOrArray, paraClass, paraType, jsonKey); + } + } + + + public static Object getPrimitiveDefaultValue(Class paraClass) { + if (paraClass == int.class || paraClass == long.class || paraClass == float.class || paraClass == double.class) { + return 0; + } else if (paraClass == boolean.class) { + return Boolean.FALSE; + } else if (paraClass == short.class) { + return (short) 0; + } else if (paraClass == byte.class) { + return (byte) 0; + } else if (paraClass == char.class) { + return '\u0000'; + } else { + //不存在这种类型 + return null; + } + } + + + private static Object parseObject(JSONObject rawObject, Class paraClass, Type paraType, String jsonKey) throws IllegalAccessException, InstantiationException { + if (!StringUtils.hasText(jsonKey)) { + return toJavaObject(rawObject, paraClass, paraType); + } + + Object result = null; + String[] keys = jsonKey.split("\\."); + for (int i = 0; i < keys.length; i++) { + if (rawObject != null && !rawObject.isEmpty()) { + String key = keys[i].trim(); + if (StringUtils.hasText(key)) { + //the last + if (i == keys.length - 1) { + if (key.endsWith(endOfArray) && key.contains(startOfArray)) { + String realKey = key.substring(0, key.indexOf(startOfArray)); + JSONArray jarray = rawObject.getJSONArray(realKey.trim()); + if (jarray != null && jarray.size() > 0) { + String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1); + int arrayIndex = StringUtils.hasText(arrayString) ? Integer.parseInt(arrayString.trim()) : 0; + result = arrayIndex >= jarray.size() ? null : jarray.get(arrayIndex); + } + } else { + result = rawObject.get(key); + } + } + //not last + else { + rawObject = getJSONObjectByKey(rawObject, key); + } + } + } + } + + if (result == null || "".equals(result)) { + return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null; + } + + if (paraClass == String.class && paraClass == paraType) { + return result.toString(); + } + + // JSONObject 类型 + if (result instanceof JSONObject) { + return toJavaObject((JSONObject) result, paraClass, paraType); + } + + return ConvertUtil.convert(result, paraClass); + } + + + private static Object parseArray(Object rawJsonObjectOrArray, Class typeClass, Type type, String jsonKey) { + JSONArray jsonArray = null; + if (!StringUtils.hasText(jsonKey)) { + if (rawJsonObjectOrArray instanceof JSONArray) { + jsonArray = (JSONArray) rawJsonObjectOrArray; + } + } else { + if (rawJsonObjectOrArray instanceof JSONObject) { + JSONObject rawObject = (JSONObject) rawJsonObjectOrArray; + String[] keys = jsonKey.split("\\."); + for (int i = 0; i < keys.length; i++) { + if (rawObject == null || rawObject.isEmpty()) { + break; + } + String key = keys[i].trim(); + if (StringUtils.hasText(key)) { + //the last + if (i == keys.length - 1) { + if (key.endsWith(endOfArray) && key.contains(startOfArray)) { + String realKey = key.substring(0, key.indexOf(startOfArray)); + JSONArray jarray = rawObject.getJSONArray(realKey.trim()); + if (jarray == null || jarray.isEmpty()) { + return null; + } + String subKey = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1).trim(); + if (!StringUtils.hasText(subKey)) { + throw new IllegalStateException("Sub key can not empty: " + jsonKey); + } + + JSONArray newJsonArray = new JSONArray(); + for (int j = 0; j < jarray.size(); j++) { + Object value = jarray.getJSONObject(j).get(subKey); + if (value != null) { + newJsonArray.add(value); + } + } + jsonArray = newJsonArray; + } else { + jsonArray = rawObject.getJSONArray(key); + } + } + //not last + else { + rawObject = getJSONObjectByKey(rawObject, key); + } + } + } + } + } + + if (jsonArray == null || jsonArray.isEmpty()) { + return null; + } + + //非泛型 set + if ((typeClass == Set.class || typeClass == HashSet.class) && typeClass == type) { + return new HashSet<>(jsonArray); + } + + //直接获取 JsonArray + if (typeClass == type && typeClass == JSONArray.class) { + return jsonArray; + } + + return jsonArray.toJavaObject(type); + } + + + private static JSONObject getJSONObjectByKey(JSONObject jsonObject, String key) { + if (key.endsWith(endOfArray) && key.contains(startOfArray)) { + String realKey = key.substring(0, key.indexOf(startOfArray)); + JSONArray jarray = jsonObject.getJSONArray(realKey.trim()); + if (jarray == null || jarray.isEmpty()) { + return null; + } + String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1); + int arrayIndex = StringUtils.hasText(arrayString) ? Integer.parseInt(arrayString.trim()) : 0; + return arrayIndex >= jarray.size() ? null : jarray.getJSONObject(arrayIndex); + } else { + return jsonObject.getJSONObject(key); + } + } + + private static Object toJavaObject(JSONObject rawObject, Class paraClass, Type paraType) throws IllegalAccessException, InstantiationException { + if (rawObject.isEmpty()) { + return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null; + } + + //非泛型 的 map + if ((paraClass == Map.class || paraClass == JSONObject.class) && paraClass == paraType) { + return rawObject; + } + + //非泛型 的 map + if (Map.class.isAssignableFrom(paraClass) && paraClass == paraType && canNewInstance(paraClass)) { + Map map = (Map) paraClass.newInstance(); + map.putAll(rawObject); + return map; + } + + return rawObject.toJavaObject(paraType); + } + + + private static boolean canNewInstance(Class clazz) { + int modifiers = clazz.getModifiers(); + return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); + } + +} \ No newline at end of file diff --git a/easyflow-commons/pom.xml b/easyflow-commons/pom.xml new file mode 100644 index 0000000..b5bf0f1 --- /dev/null +++ b/easyflow-commons/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + tech.easyflow + easyflow + ${revision} + + + easyflow-commons + pom + + + easyflow-common-all + easyflow-common-web + easyflow-common-captcha + easyflow-common-base + easyflow-common-file-storage + easyflow-common-ai + easyflow-common-options + easyflow-common-cache + easyflow-common-satoken + easyflow-common-audio + easyflow-common-chat-protocol + + + diff --git a/easyflow-modules/easyflow-module-ai/pom.xml b/easyflow-modules/easyflow-module-ai/pom.xml new file mode 100644 index 0000000..7562465 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-ai + easyflow-module-ai + + + + tech.easyflow + easyflow-common-audio + + + tech.easyflow + easyflow-module-datacenter + + + tech.easyflow + easyflow-common-cache + + + tech.easyflow + easyflow-common-base + + + tech.easyflow + easyflow-common-ai + + + com.easyagents + easy-agents-flow + + + com.easyagents + easy-agents-support + + + + com.jfinal + enjoy + 5.1.3 + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + tech.easyflow + easyflow-common-satoken + + + tech.easyflow + easyflow-common-file-storage + + + cn.hutool + hutool-core + + + tech.easyflow + easyflow-module-system + + + + org.java-websocket + Java-WebSocket + + + + org.springframework.boot + spring-boot-starter-websocket + + + + tech.easyflow + easyflow-common-chat-protocol + + + + com.easyagents + easy-agents-mcp + + + diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiDictAutoConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiDictAutoConfig.java new file mode 100644 index 0000000..c72376d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiDictAutoConfig.java @@ -0,0 +1,38 @@ +package tech.easyflow.ai.config; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import tech.easyflow.ai.mapper.*; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.dict.DictManager; +import tech.easyflow.common.dict.loader.DbDataLoader; + +import javax.annotation.Resource; + +@Configuration +public class AiDictAutoConfig { + + @Resource + private WorkflowMapper workflowMapper; + @Resource + private WorkflowCategoryMapper workflowCategoryMapper; + @Resource + private BotCategoryMapper botCategoryMapper; + @Resource + private ResourceCategoryMapper resourceCategoryMapper; + @Resource + private DocumentCollectionCategoryMapper documentCollectionCategoryMapper; + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationStartup() { + + DictManager dictManager = SpringContextUtil.getBean(DictManager.class); + dictManager.putLoader(new DbDataLoader<>("aiWorkFlow", workflowMapper, "id", "title", null, null, false)); + dictManager.putLoader(new DbDataLoader<>("aiWorkFlowCategory", workflowCategoryMapper, "id", "category_name", null, null, false)); + dictManager.putLoader(new DbDataLoader<>("aiBotCategory", botCategoryMapper, "id", "category_name", null, null, false)); + dictManager.putLoader(new DbDataLoader<>("aiResourceCategory", resourceCategoryMapper, "id", "category_name", null, null, false)); + dictManager.putLoader(new DbDataLoader<>("aiDocumentCollectionCategory", documentCollectionCategoryMapper, "id", "category_name", null, null, false)); + + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiEsConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiEsConfig.java new file mode 100644 index 0000000..104ffcc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiEsConfig.java @@ -0,0 +1,35 @@ +package tech.easyflow.ai.config; + +import com.easyagents.engine.es.ESConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class AiEsConfig extends ESConfig { + + @Value("${rag.searcher.elastic.host}") + @Override + public void setHost(String host) { + super.setHost(host); + } + + @Value("${rag.searcher.elastic.userName}") + @Override + public void setUserName(String userName) { + super.setUserName(userName); + } + + @Value("${rag.searcher.elastic.password}") + @Override + public void setPassword(String password) { + super.setPassword(password); + } + + @Value("${rag.searcher.elastic.indexName}") + @Override + public void setIndexName(String indexName) { + super.setIndexName(indexName); + } + +} + diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiLuceneConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiLuceneConfig.java new file mode 100644 index 0000000..0cbbf59 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiLuceneConfig.java @@ -0,0 +1,17 @@ +package tech.easyflow.ai.config; + +import com.easyagents.search.engine.lucene.LuceneConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class AiLuceneConfig extends LuceneConfig { + + @Value("${rag.searcher.lucene.indexDirPath}") + @Override + public void setIndexDirPath(String indexDirPath) { + super.setIndexDirPath(indexDirPath); + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiModuleConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiModuleConfig.java new file mode 100644 index 0000000..2644e7f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/AiModuleConfig.java @@ -0,0 +1,13 @@ +package tech.easyflow.ai.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; + +@MapperScan("tech.easyflow.ai.mapper") +@AutoConfiguration +public class AiModuleConfig { + + public AiModuleConfig() { + System.out.println("启用模块 >>>>>>>>>> module-ai"); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/BochaaiProps.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/BochaaiProps.java new file mode 100644 index 0000000..88a23c5 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/BochaaiProps.java @@ -0,0 +1,19 @@ +package tech.easyflow.ai.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "node.bochaai") +public class BochaaiProps { + + private String apiKey; + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/McpClientAutoConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/McpClientAutoConfig.java new file mode 100644 index 0000000..f7d7c5b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/McpClientAutoConfig.java @@ -0,0 +1,54 @@ +package tech.easyflow.ai.config; + +import com.easyagents.mcp.client.McpClientManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import tech.easyflow.ai.entity.Mcp; +import tech.easyflow.ai.service.impl.McpServiceImpl; +import tech.easyflow.common.util.StringUtil; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; + +import static tech.easyflow.ai.service.impl.McpServiceImpl.getFirstMcpServerName; + +@Configuration +@DependsOn("mcpServiceImpl") // 确保 mcpService 先初始化 +public class McpClientAutoConfig { + + private final McpClientManager mcpClientManager = McpClientManager.getInstance(); + private static final Logger log = LoggerFactory.getLogger(McpClientAutoConfig.class); + + @Resource + private McpServiceImpl mcpService; + + @PostConstruct + public void initMcpClient() { + log.info("开始初始化 MCP 客户端..."); + List mcpList = mcpService.list(); + log.info("获取到 MCP 配置列表,数量:{}", mcpList.size()); + mcpList.forEach(mcp -> { + if (!mcp.getStatus()) { + return; + } + String configJson = mcp.getConfigJson(); + String serverName = getFirstMcpServerName(configJson); + if (StringUtil.hasText(serverName)) { + try { + mcpClientManager.registerFromJson(configJson); + } catch (Exception e) { + log.error("MCP服务名称:{} 注册失败", serverName, e); + } + log.info("MCP服务名称:{} 注册成功", serverName); + } else { + log.error("MCP服务名称为:{} 启动失败,配置 JSON 中未找到服务名称", mcp.getTitle()); + } + }); + + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/SearcherFactory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/SearcherFactory.java new file mode 100644 index 0000000..9ac6890 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/SearcherFactory.java @@ -0,0 +1,40 @@ +package tech.easyflow.ai.config; + +import com.easyagents.engine.es.ElasticSearcher; +import com.easyagents.search.engine.lucene.LuceneSearcher; +import com.easyagents.search.engine.service.DocumentSearcher; +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 SearcherFactory { + + @Autowired + private AiLuceneConfig luceneConfig; + + @Autowired + private AiEsConfig aiEsConfig; + + @Bean + public LuceneSearcher luceneSearcher() { + return new LuceneSearcher(luceneConfig); + } + + @Bean + public ElasticSearcher elasticSearcher() { + return new ElasticSearcher(aiEsConfig); + } + + + public DocumentSearcher getSearcher(String defaultSearcherType) { + switch (defaultSearcherType) { + case "elasticSearch": + return new ElasticSearcher(aiEsConfig); + case "lucene": + default: + return new LuceneSearcher(luceneConfig); + } + } +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/ThreadPoolConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/ThreadPoolConfig.java new file mode 100644 index 0000000..71ab735 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/config/ThreadPoolConfig.java @@ -0,0 +1,40 @@ +package tech.easyflow.ai.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import tech.easyflow.common.web.exceptions.BusinessException; + +@Configuration +public class ThreadPoolConfig { + private static final Logger log = LoggerFactory.getLogger(ThreadPoolConfig.class); + + /** + * SSE消息发送专用线程池 + * 核心原则:IO密集型任务(网络推送),线程数 = CPU核心数 * 2 + 1 + */ + @Bean(name = "sseThreadPool") + public ThreadPoolTaskExecutor sseThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + int cpuCoreNum = Runtime.getRuntime().availableProcessors(); // 获取CPU核心数(4核返回4) + executor.setCorePoolSize(cpuCoreNum * 2); // 核心线程数 + executor.setMaxPoolSize(cpuCoreNum * 10); // 最大线程数(峰值时扩容,避免线程过多导致上下文切换) + executor.setQueueCapacity(8000); // 任务队列容量 + executor.setKeepAliveSeconds(30); // 空闲线程存活时间:30秒(非核心线程空闲后销毁,节省资源) + executor.setThreadNamePrefix("sse-sender-"); + + // 拒绝策略 + executor.setRejectedExecutionHandler((runnable, executorService) -> { + log.error("SSE线程池过载!核心线程数:{},最大线程数:{},队列任务数:{}", + executorService.getCorePoolSize(), + executorService.getMaximumPoolSize(), + executorService.getQueue().size()); + // 抛出自定义异常,全局捕获后返回“服务繁忙” + throw new BusinessException("服务器忙,请稍后重试"); + }); + executor.initialize(); + return executor; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/CustomMultipartFile.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/CustomMultipartFile.java new file mode 100644 index 0000000..da68a07 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/CustomMultipartFile.java @@ -0,0 +1,77 @@ +package tech.easyflow.ai.easyagents; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class CustomMultipartFile implements MultipartFile { + + private final byte[] content; + private final String name; + private final String originalFilename; + private final String contentType; + + public CustomMultipartFile(byte[] content, String name, String originalFilename, String contentType) { + this.content = content; + this.name = name; + this.originalFilename = originalFilename; + this.contentType = contentType; + } + + // 从 InputStream 构建 CustomMultipartFile + public static CustomMultipartFile fromInputStream(InputStream inputStream, String name, String originalFilename, String contentType) throws IOException { + // 将 InputStream 转为字节数组 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return new CustomMultipartFile(outputStream.toByteArray(), name, originalFilename, contentType); + } + + // 实现 MultipartFile 接口的抽象方法 + @Override + public String getName() { + return name; + } + + @Override + public String getOriginalFilename() { + return originalFilename; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return content == null || content.length == 0; + } + + @Override + public long getSize() { + return content.length; + } + + @Override + public byte[] getBytes() throws IOException { + return content; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(content); + } + + @Override + public void transferTo(java.io.File dest) throws IOException, IllegalStateException { + // 如需保存到文件,可实现此方法 + throw new UnsupportedOperationException("transferTo is not implemented"); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/ChatStreamListener.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/ChatStreamListener.java new file mode 100644 index 0000000..edb61fc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/ChatStreamListener.java @@ -0,0 +1,144 @@ +package tech.easyflow.ai.easyagents.listener; + +import com.easyagents.core.message.AiMessage; +import com.easyagents.core.message.ToolMessage; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.chat.ChatOptions; +import com.easyagents.core.model.chat.StreamResponseListener; +import com.easyagents.core.model.chat.response.AiMessageResponse; +import com.easyagents.core.model.client.StreamContext; +import com.easyagents.core.prompt.MemoryPrompt; +import org.apache.catalina.connector.ClientAbortException; +import tech.easyflow.core.chat.protocol.ChatDomain; +import tech.easyflow.core.chat.protocol.ChatEnvelope; +import tech.easyflow.core.chat.protocol.ChatType; +import tech.easyflow.core.chat.protocol.MessageRole; +import tech.easyflow.core.chat.protocol.payload.ErrorPayload; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ChatStreamListener implements StreamResponseListener { + + private final String conversationId; + private final ChatModel chatModel; + private final MemoryPrompt memoryPrompt; + private final ChatSseEmitter sseEmitter; + private final ChatOptions chatOptions; + // 核心标记:是否允许执行onStop业务逻辑(仅最后一次无后续工具调用时为true) + private boolean canStop = true; + // 辅助标记:是否进入过工具调用(避免重复递归判断) + private boolean hasToolCall = false; + + public ChatStreamListener(String conversationId, ChatModel chatModel, MemoryPrompt memoryPrompt, ChatSseEmitter sseEmitter, ChatOptions chatOptions) { + this.conversationId = conversationId; + this.chatModel = chatModel; + this.memoryPrompt = memoryPrompt; + this.sseEmitter = sseEmitter; + this.chatOptions = chatOptions; + } + + @Override + public void onStart(StreamContext context) { + StreamResponseListener.super.onStart(context); + } + + @Override + public void onMessage(StreamContext context, AiMessageResponse aiMessageResponse) { + try { + AiMessage aiMessage = aiMessageResponse.getMessage(); + if (aiMessage == null) { + return; + } + if (aiMessage.isFinalDelta() && aiMessageResponse.hasToolCalls()) { + this.canStop = false; // 工具调用期间,禁止执行onStop + this.hasToolCall = true; // 标记已进入过工具调用 + aiMessage.setContent(null); + memoryPrompt.addMessage(aiMessage); + List toolMessages = aiMessageResponse.executeToolCallsAndGetToolMessages(); + for (ToolMessage toolMessage : toolMessages) { + memoryPrompt.addMessage(toolMessage); + } + chatModel.chatStream(memoryPrompt, this, chatOptions); + } else { + if (this.hasToolCall) { + this.canStop = true; + } + String reasoningContent = aiMessage.getReasoningContent(); + if (reasoningContent != null && !reasoningContent.isEmpty()) { + sendChatEnvelope(sseEmitter, reasoningContent, ChatType.THINKING); + } else { + String delta = aiMessage.getContent(); + if (delta != null && !delta.isEmpty()) { + sendChatEnvelope(sseEmitter, delta, ChatType.MESSAGE); + } + } + + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void onStop(StreamContext context) { + // 仅当canStop为true(最后一次无后续工具调用的响应)时,执行业务逻辑 + if (this.canStop) { + System.out.println("onStop"); + if (context.getThrowable() != null) { + sendSystemError(sseEmitter, context.getThrowable().getMessage()); + return; + } + memoryPrompt.addMessage(context.getFullMessage()); + ChatEnvelope> chatEnvelope = new ChatEnvelope<>(); + chatEnvelope.setDomain(ChatDomain.SYSTEM); + sseEmitter.sendDone(chatEnvelope); + StreamResponseListener.super.onStop(context); + } + + } + + @Override + public void onFailure(StreamContext context, Throwable throwable) { + if (throwable != null) { + throwable.printStackTrace(); + sendSystemError(sseEmitter, throwable.getMessage()); + } + } + + private void sendChatEnvelope(ChatSseEmitter sseEmitter, String deltaContent, ChatType chatType) throws IOException { + if (deltaContent == null || deltaContent.isEmpty()) { + return; + } + + ChatEnvelope> chatEnvelope = new ChatEnvelope<>(); + chatEnvelope.setDomain(ChatDomain.LLM); + chatEnvelope.setType(chatType); + + Map deltaMap = new LinkedHashMap<>(); + deltaMap.put("conversation_id", this.conversationId); + deltaMap.put("role", MessageRole.ASSISTANT.getValue()); + deltaMap.put("delta", deltaContent); + chatEnvelope.setPayload(deltaMap); + + sseEmitter.send(chatEnvelope); + } + + public void sendSystemError(ChatSseEmitter sseEmitter, + String message) { + ChatEnvelope envelope = new ChatEnvelope<>(); + ErrorPayload payload = new ErrorPayload(); + payload.setMessage(message); + payload.setCode("SYSTEM_ERROR"); + payload.setRetryable(false); + envelope.setPayload(payload); + envelope.setDomain(ChatDomain.SYSTEM); + envelope.setType(ChatType.ERROR); + sseEmitter.sendError(envelope); + sseEmitter.complete(); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/PromptChoreChatStreamListener.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/PromptChoreChatStreamListener.java new file mode 100644 index 0000000..7c914d3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/listener/PromptChoreChatStreamListener.java @@ -0,0 +1,63 @@ +package tech.easyflow.ai.easyagents.listener; + +import com.easyagents.core.model.chat.StreamResponseListener; +import com.easyagents.core.model.chat.response.AiMessageResponse; +import com.easyagents.core.model.client.StreamContext; +import com.alibaba.fastjson.JSON; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.core.chat.protocol.ChatDomain; +import tech.easyflow.core.chat.protocol.ChatEnvelope; +import tech.easyflow.core.chat.protocol.ChatType; +import tech.easyflow.core.chat.protocol.MessageRole; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * 系统提示词优化监听器 + */ +public class PromptChoreChatStreamListener implements StreamResponseListener { + + private final ChatSseEmitter sseEmitter; + + public PromptChoreChatStreamListener(ChatSseEmitter sseEmitter) { + this.sseEmitter = sseEmitter; + } + @Override + public void onStart(StreamContext context) { + StreamResponseListener.super.onStart(context); + } + + @Override + public void onMessage(StreamContext context, AiMessageResponse response) { + String content = response.getMessage().getContent(); + if (content != null) { + String delta = response.getMessage().getContent(); + if (StringUtil.hasText(delta)) { + ChatEnvelope> chatEnvelope = new ChatEnvelope<>(); + chatEnvelope.setDomain(ChatDomain.LLM); + chatEnvelope.setType(ChatType.MESSAGE); + Map deletaMap = new HashMap<>(); + deletaMap.put("delta", delta); + deletaMap.put("role", MessageRole.ASSISTANT.getValue()); + chatEnvelope.setPayload(deletaMap); + sseEmitter.send(chatEnvelope); + } + } + } + + @Override + public void onStop(StreamContext context) { + System.out.println("onStop"); + sseEmitter.sendDone(new ChatEnvelope<>()); + StreamResponseListener.super.onStop(context); + } + + @Override + public void onFailure(StreamContext context, Throwable throwable) { + StreamResponseListener.super.onFailure(context, throwable); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/BotMessageMemory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/BotMessageMemory.java new file mode 100644 index 0000000..44e12ce --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/BotMessageMemory.java @@ -0,0 +1,74 @@ +package tech.easyflow.ai.easyagents.memory; + +import com.easyagents.core.memory.ChatMemory; +import com.easyagents.core.message.Message; +import com.mybatisflex.core.query.QueryWrapper; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.ai.service.BotMessageService; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class BotMessageMemory implements ChatMemory { + private final BigInteger botId; + private final BigInteger accountId; + private final BigInteger conversationId; + private final BotMessageService messageService; + + public BotMessageMemory(BigInteger botId, BigInteger accountId, BigInteger conversationId, + BotMessageService messageService) { + this.botId = botId; + this.accountId = accountId; + this.conversationId = conversationId; + this.messageService = messageService; + } + + @Override + public List getMessages(int count) { + List sysAiMessages = messageService.list(QueryWrapper.create() + .eq(BotMessage::getBotId, botId, true) + .eq(BotMessage::getAccountId, accountId, true) + .eq(BotMessage::getConversationId, conversationId, true) + .orderBy(BotMessage::getCreated, true) + .limit(count) + ); + + if (sysAiMessages == null || sysAiMessages.isEmpty()) { + return null; + } + + List messages = new ArrayList<>(sysAiMessages.size()); + for (BotMessage botMessage : sysAiMessages) { + Message message = botMessage.getContentAsMessage(); + if (message != null) messages.add(message); + } + return messages; + } + + + @Override + public void addMessage(Message message) { + + BotMessage dbMessage = new BotMessage(); + dbMessage.setCreated(new Date()); + dbMessage.setBotId(botId); + dbMessage.setAccountId(accountId); + dbMessage.setConversationId(conversationId); + dbMessage.setContentAndRole(message); + dbMessage.setModified(new Date()); + messageService.save(dbMessage); + } + + @Override + public void clear() { + + } + + @Override + public Object id() { + return botId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/DefaultBotMessageMemory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/DefaultBotMessageMemory.java new file mode 100644 index 0000000..b3569e4 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/DefaultBotMessageMemory.java @@ -0,0 +1,79 @@ +package tech.easyflow.ai.easyagents.memory; + +import com.easyagents.core.memory.DefaultChatMemory; +import com.easyagents.core.message.*; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.core.chat.protocol.ChatDomain; +import tech.easyflow.core.chat.protocol.ChatEnvelope; +import tech.easyflow.core.chat.protocol.ChatType; +import tech.easyflow.core.chat.protocol.MessageRole; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +public class DefaultBotMessageMemory extends DefaultChatMemory { + + private final ChatSseEmitter sseEmitter; + + private final List> messages; + public DefaultBotMessageMemory(BigInteger conversationId, ChatSseEmitter sseEmitter, List> messages) { + super(conversationId); + this.sseEmitter = sseEmitter; + this.messages = messages; + } + + @Override + public List getMessages(int count) { + List list = new ArrayList<>(messages.size()); + for (Map msg : messages) { + BotMessage botMessage = new BotMessage(); + botMessage.setRole(msg.get("role")); + botMessage.setContent(msg.get("content")); + Message message = botMessage.getContentAsMessage(); + list.add(message); + } + List collect = list.stream() + .limit(count) + .collect(Collectors.toList()); + return collect; + } + + @Override + public void addMessage(Message message) { + BotMessage dbMessage = new BotMessage(); + ChatEnvelope> chatEnvelope = new ChatEnvelope<>(); + String jsonMessage = JSON.toJSONString(message, SerializerFeature.WriteClassName); + if (message instanceof AiMessage) { + dbMessage.setRole(MessageRole.ASSISTANT.getValue()); + + } else if (message instanceof UserMessage) { + dbMessage.setRole(MessageRole.USER.getValue()); + } else if (message instanceof SystemMessage) { + dbMessage.setRole(MessageRole.SYSTEM.getValue()); + } else if (message instanceof ToolMessage) { + dbMessage.setRole(MessageRole.TOOL.getValue()); + } + Map res = new HashMap<>(); + res.put("role", dbMessage.getRole()); + res.put("content", jsonMessage); + chatEnvelope.setType(ChatType.MESSAGE); + chatEnvelope.setPayload(res); + chatEnvelope.setDomain(ChatDomain.SYSTEM); + if (dbMessage.getRole().equals(MessageRole.USER.getValue())) { + messages.remove(messages.size() - 1); + } + sseEmitter.sendMessageNeedSave(chatEnvelope); + messages.add(res); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/PublicBotMessageMemory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/PublicBotMessageMemory.java new file mode 100644 index 0000000..778d0bf --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/memory/PublicBotMessageMemory.java @@ -0,0 +1,31 @@ +package tech.easyflow.ai.easyagents.memory; + +import com.easyagents.core.memory.DefaultChatMemory; +import com.easyagents.core.message.Message; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class PublicBotMessageMemory extends DefaultChatMemory { + private final ChatSseEmitter sseEmitter; + private List messages = new ArrayList<>(); + + public PublicBotMessageMemory(ChatSseEmitter sseEmitter, List messages ) { + this.messages = messages; + this.sseEmitter = sseEmitter; + } + + @Override + public List getMessages(int count) { + return messages.stream() + .limit(count) + .collect(Collectors.toList()); + } + + @Override + public void addMessage(Message message) { + this.messages.add(message); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/DocumentCollectionTool.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/DocumentCollectionTool.java new file mode 100644 index 0000000..868ebbc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/DocumentCollectionTool.java @@ -0,0 +1,66 @@ +package tech.easyflow.ai.easyagents.tool; + +import com.easyagents.core.document.Document; +import com.easyagents.core.model.chat.tool.BaseTool; +import com.easyagents.core.model.chat.tool.Parameter; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.common.util.SpringContextUtil; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +public class DocumentCollectionTool extends BaseTool { + + private BigInteger knowledgeId; + + public DocumentCollectionTool() { + } + + public DocumentCollectionTool(DocumentCollection documentCollection, boolean needEnglishName) { + this.knowledgeId = documentCollection.getId(); + if (needEnglishName) { + this.name = documentCollection.getEnglishName(); + } else { + this.name = documentCollection.getTitle(); + } + this.description = documentCollection.getDescription(); + this.parameters = getDefaultParameters(); + } + + + public Parameter[] getDefaultParameters() { + Parameter parameter = new Parameter(); + parameter.setName("input"); + parameter.setDescription("要查询的相关知识"); + parameter.setType("string"); + parameter.setRequired(true); + return new Parameter[]{parameter}; + } + + public BigInteger getKnowledgeId() { + return knowledgeId; + } + + public void setKnowledgeId(BigInteger knowledgeId) { + this.knowledgeId = knowledgeId; + } + + @Override + public Object invoke(Map argsMap) { + + DocumentCollectionService knowledgeService = SpringContextUtil.getBean(DocumentCollectionService.class); + List documents = knowledgeService.search(this.knowledgeId, (String) argsMap.get("input")); + + StringBuilder sb = new StringBuilder(); + if (documents != null) { + for (Document document : documents) { + sb.append(document.getContent()); + } + } + return sb.toString(); + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/McpTool.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/McpTool.java new file mode 100644 index 0000000..a97a569 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/McpTool.java @@ -0,0 +1,46 @@ +package tech.easyflow.ai.easyagents.tool; + +import com.easyagents.core.model.chat.tool.BaseTool; +import com.easyagents.core.model.chat.tool.Tool; +import com.easyagents.mcp.client.McpClientManager; +import tech.easyflow.ai.entity.Mcp; +import tech.easyflow.ai.service.McpService; +import tech.easyflow.ai.service.impl.McpServiceImpl; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.util.StringUtil; + +import java.math.BigInteger; +import java.util.Map; +import java.util.Optional; + +public class McpTool extends BaseTool { + private BigInteger mcpId; + + @Override + public Object invoke(Map argsMap) { + return runMcp(this.mcpId, argsMap); + } + + public Object runMcp(BigInteger mcpId, Map argsMap) { + + McpService mcpService = SpringContextUtil.getBean(McpService.class); + Mcp mcp = mcpService.getMapper().selectOneById(mcpId); + String serverName = McpServiceImpl.getFirstMcpServerName(mcp.getConfigJson()); + if (StringUtil.hasText(serverName)) { + McpClientManager mcpClientManager = McpClientManager.getInstance(); + Tool mcpTool = mcpClientManager.getMcpTool(serverName, this.name); + Object result = mcpTool.invoke(argsMap); + return result; + } + return null; + } + + + public void setMcpId(BigInteger mcpId) { + this.mcpId = mcpId; + } + + public BigInteger getMcpId() { + return mcpId; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/PluginTool.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/PluginTool.java new file mode 100644 index 0000000..2a07fa5 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/PluginTool.java @@ -0,0 +1,368 @@ +package tech.easyflow.ai.easyagents.tool; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import com.easyagents.core.model.chat.tool.BaseTool; +import com.easyagents.core.model.chat.tool.Parameter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mybatisflex.core.query.QueryWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.ai.easyagents.CustomMultipartFile; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.entity.PluginItem; +import tech.easyflow.ai.mapper.PluginMapper; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.common.ai.plugin.NestedParamConverter; +import tech.easyflow.common.ai.plugin.PluginHttpClient; +import tech.easyflow.common.ai.plugin.PluginParam; +import tech.easyflow.common.ai.plugin.PluginParamConverter; +import tech.easyflow.common.filestorage.FileStorageManager; +import tech.easyflow.common.filestorage.FileStorageService; +import tech.easyflow.common.util.SpringContextUtil; + +import java.io.*; +import java.lang.reflect.Array; +import java.math.BigInteger; +import java.util.*; + +public class PluginTool extends BaseTool { + + // 插件工具id + private BigInteger pluginToolId; + private String name; + private String description; + private Parameter[] parameters; + private static final Logger logger = LoggerFactory.getLogger(PluginTool.class); + + public PluginTool() { + + } + + public PluginTool(PluginItem pluginItem) { + this.name = pluginItem.getEnglishName(); + this.description = pluginItem.getDescription(); + this.pluginToolId = pluginItem.getId(); + this.parameters = getDefaultParameters(pluginItem.getInputData()); + } + + public BigInteger getPluginToolId() { + return pluginToolId; + } + + public void setPluginToolId(BigInteger pluginToolId) { + this.pluginToolId = pluginToolId; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setParameters(Parameter[] parameters) { + this.parameters = parameters; + } + + private Plugin getAiPlugin(BigInteger pluginId) { + QueryWrapper queryWrapper = QueryWrapper.create() + .select("*") + .from("tb_plugin") + .where("id = ?", pluginId); + PluginMapper pluginMapper = SpringContextUtil.getBean(PluginMapper.class); + Plugin plugin1 = pluginMapper.selectOneByQuery(queryWrapper); + return plugin1; + } + + private Parameter[] getDefaultParameters(String inputData) { + PluginItemService pluginToolService = SpringContextUtil.getBean(PluginItemService.class); + QueryWrapper queryAiPluginToolWrapper = QueryWrapper.create() + .select("*") + .from("tb_plugin_item") + .where("id = ? ", this.pluginToolId); + PluginItem pluginItem = pluginToolService.getMapper().selectOneByQuery(queryAiPluginToolWrapper); + List> dataList = null; + if (pluginItem == null || pluginItem.getInputData() == null){ + dataList = getDataList(inputData); + } else { + dataList = getDataList(pluginItem.getInputData()); + } + Parameter[] params = new Parameter[dataList.size()]; + for (int i = 0; i < dataList.size(); i++) { + Map item = dataList.get(i); + Parameter parameter = new Parameter(); + parameter.setName((String) item.get("name")); + parameter.setDescription((String) item.get("description")); + parameter.setRequired((boolean) item.get("required")); + String type = (String) item.get("type"); + if (type != null) { + parameter.setType(type.toLowerCase()); + } + params[i] = parameter; + } + return params; + } + + // 转换输入参数 + private List> getDataList(String jsonArray){ + List> dataList; + if (jsonArray == null) { + return new ArrayList<>(); + } + try { + dataList = new ObjectMapper().readValue( + jsonArray, + new TypeReference>>(){} + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return dataList; + } + + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Parameter[] getParameters() { + return parameters; + } + + @Override + public Object invoke(Map argsMap) { + return runPluginTool(argsMap, null, this.pluginToolId); + } + + public Object runPluginTool(Map argsMap, String inputData, BigInteger pluginId){ + PluginItemService pluginToolService = SpringContextUtil.getBean(PluginItemService.class); + QueryWrapper queryAiPluginToolWrapper = QueryWrapper.create() + .select("*") + .from("tb_plugin_item") + .where("id = ? ", pluginId); + PluginItem pluginItem = pluginToolService.getMapper().selectOneByQuery(queryAiPluginToolWrapper); + String method = pluginItem.getRequestMethod().toUpperCase(); + Plugin plugin = getAiPlugin(pluginItem.getPluginId()); + + String url; + if (!StrUtil.isEmpty(pluginItem.getBasePath())) { + url = plugin.getBaseUrl()+ pluginItem.getBasePath(); + } else { + url = plugin.getBaseUrl()+"/"+ pluginItem.getName(); + } + + List> headers = getDataList(plugin.getHeaders()); + Map headersMap = new HashMap<>(); + for (Map header : headers) { + headersMap.put((String) header.get("label"), header.get("value")); + } + List params = new ArrayList<>(); + + String authType = plugin.getAuthType(); + if (!StrUtil.isEmpty(authType) && "apiKey".equals(plugin.getAuthType())){ + if ("headers".equals(plugin.getPosition())){ + headersMap.put(plugin.getTokenKey(), plugin.getTokenValue()); + } else { + PluginParam pluginParam = new PluginParam(); + pluginParam.setName(plugin.getTokenKey()); + pluginParam.setDefaultValue(plugin.getTokenValue()); + pluginParam.setEnabled(true); + pluginParam.setRequired(true); + pluginParam.setMethod("query"); + params.add(pluginParam); + } + } + List pluginParams = null; + // 前端点击试运行传过来的参数 + if (inputData != null && !inputData.isEmpty()){ + pluginParams = PluginParamConverter.convertFromJson(inputData); + // 大模型命中funcation_call 调用参数 + } else { + pluginParams = PluginParamConverter.convertFromJson(pluginItem.getInputData()); + } + + // 准备存放不同位置的参数 + List queryParams = new ArrayList<>(); + List bodyParams = new ArrayList<>(); + List headerParams = new ArrayList<>(); + List pathParams = new ArrayList<>(); + Map nestedParams = NestedParamConverter.convertToNestedParamMap(pluginParams); + + // 遍历嵌套参数 + for (Map.Entry entry : nestedParams.entrySet()) { + String paramName = entry.getKey(); + + // 获取原始参数定义 + PluginParam originalParam = findOriginalParam(pluginParams, paramName); + if (originalParam == null || !originalParam.isEnabled()) { + continue; + } + + // 创建参数副本以避免修改原始定义 + PluginParam requestParam = new PluginParam(); + requestParam.setName(originalParam.getName()); + requestParam.setDescription(originalParam.getDescription()); + requestParam.setRequired(originalParam.isRequired()); + + requestParam.setEnabled(originalParam.isEnabled()); + requestParam.setMethod(originalParam.getMethod()); + requestParam.setChildren(originalParam.getChildren()); + // 优先级: argsMap值 < 参数默认值 + if (argsMap != null && argsMap.containsKey(paramName)) { + // 1. 优先检查是否有有效的默认值 + if (hasValidDefaultValue(originalParam.getDefaultValue())) { + // 使用默认值 + requestParam.setDefaultValue(originalParam.getDefaultValue()); + } else { + // 使用大模型返回的值 + requestParam.setDefaultValue(argsMap.get(paramName)); + } + } else if (hasValidDefaultValue(originalParam.getDefaultValue())) { + // 2. 没有传参但默认值有效时使用默认值 + // 如果是文件类型 + if (originalParam.getType().equals("File")){ + try { + FileStorageService fileStorageService = SpringContextUtil.getBean(FileStorageManager.class); + InputStream inputStream = fileStorageService.readStream((String)originalParam.getDefaultValue()); + requestParam.setType("MultipartFile"); + byte[] bytes = inputStreamToBytes(inputStream); + String contentType = FileTypeUtil.getType(new ByteArrayInputStream(bytes)); + String fileUrl = (String) originalParam.getDefaultValue(); + int lastSlashIndex = fileUrl.lastIndexOf("/"); + String fileName = fileUrl.substring(lastSlashIndex + 1); + requestParam.setDefaultValue(new CustomMultipartFile(bytes, originalParam.getName(), fileName, contentType)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + requestParam.setType(originalParam.getType()); + requestParam.setDefaultValue(originalParam.getDefaultValue()); + } + } + // 根据method分类参数 + switch (originalParam.getMethod().toLowerCase()) { + case "query": + queryParams.add(requestParam); + break; + case "body": + bodyParams.add(requestParam); + break; + case "header": + headerParams.add(requestParam); + break; + case "path": + pathParams.add(requestParam); + break; + } + } + + // 合并所有参数 + List allParams = new ArrayList<>(); + allParams.addAll(pathParams); + allParams.addAll(queryParams); + allParams.addAll(bodyParams); + allParams.addAll(headerParams); + allParams.addAll(params); + + // 发送请求 + JSONObject result = PluginHttpClient.sendRequest(url, method, headersMap, allParams); + if (result.get("error") != null){ + logger.error("插件调用失败"); + logger.error(result.get("error").toString()); + } + return result; + } + + // 辅助方法:根据参数名查找原始参数定义 + private PluginParam findOriginalParam(List params, String name) { + for (PluginParam param : params) { + if (name.equals(param.getName())) { + return param; + } + } + return null; + } + + // 添加辅助方法判断默认值是否有效 + private boolean hasValidDefaultValue(Object defaultValue) { + if (defaultValue == null) { + return false; + } + + // 字符串类型检查 + if (defaultValue instanceof CharSequence) { + return !((CharSequence) defaultValue).toString().trim().isEmpty(); + } + + // 集合/数组类型检查 + if (defaultValue instanceof Collection) { + return !((Collection) defaultValue).isEmpty(); + } + if (defaultValue instanceof Map) { + return !((Map) defaultValue).isEmpty(); + } + if (defaultValue.getClass().isArray()) { + return Array.getLength(defaultValue) > 0; + } + + // 其他类型直接认为有效 + return true; + } + + private void processParamWithChildren(Map paramDef, Map argsMap, List params) { + boolean enabled = (boolean) paramDef.get("enabled"); + if (!enabled){ + return; + } + String paramName = (String) paramDef.get("name"); + PluginParam pluginParam = new PluginParam(); + pluginParam.setName(paramName); + pluginParam.setDescription((String) paramDef.get("description")); + pluginParam.setRequired((boolean) paramDef.get("required")); + pluginParam.setType((String) paramDef.get("type")); + pluginParam.setEnabled((boolean) paramDef.get("enabled")); + pluginParam.setMethod((String) paramDef.get("method")); + + // 如果用户传了值,就用用户的值;否则用默认值 + if (paramDef.get("defaultValue") != null && !"".equals(paramDef.get("defaultValue"))) { + pluginParam.setDefaultValue(paramDef.get("defaultValue")); + } else if (argsMap != null && paramDef.get("name").equals(paramName) && paramDef.get("defaultValue") != null) { + pluginParam.setDefaultValue(argsMap.get(paramName)); + } + + params.add(pluginParam); + + // 处理 children + List> children = (List>) paramDef.get("children"); + if (children != null) { + for (Map child : children) { + processParamWithChildren(child, argsMap, params); + } + } + } + + public static byte[] inputStreamToBytes(InputStream inputStream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; // 1KB缓冲区 + + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + return buffer.toByteArray(); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/WorkflowTool.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/WorkflowTool.java new file mode 100644 index 0000000..777db7a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagents/tool/WorkflowTool.java @@ -0,0 +1,81 @@ +package tech.easyflow.ai.easyagents.tool; + +import com.easyagents.core.model.chat.tool.BaseTool; +import com.easyagents.core.model.chat.tool.Parameter; +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.DataType; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.common.util.SpringContextUtil; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class WorkflowTool extends BaseTool { + + private BigInteger workflowId; + + public WorkflowTool() { + } + + public WorkflowTool(Workflow workflow, boolean needEnglishName) { + this.workflowId = workflow.getId(); + if (needEnglishName) { + this.name = workflow.getEnglishName(); + } else { + this.name = workflow.getTitle(); + } + this.description = workflow.getDescription(); + this.parameters = toParameters(workflow); + } + + + static Parameter[] toParameters(Workflow workflow) { + ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class); + ChainDefinition definition = executor.getDefinitionRepository().getChainDefinitionById(workflow.getId().toString()); + List parameterDefs = definition.getStartParameters(); + if (parameterDefs == null || parameterDefs.isEmpty()) { + return new Parameter[0]; + } + + Parameter[] parameters = new Parameter[parameterDefs.size()]; + for (int i = 0; i < parameterDefs.size(); i++) { + com.easyagents.flow.core.chain.Parameter parameterDef = parameterDefs.get(i); + Parameter parameter = new Parameter(); + parameter.setName(parameterDef.getName()); + parameter.setDescription(parameterDef.getDescription()); + DataType dataType = parameterDef.getDataType(); + if (dataType == null) dataType = DataType.String; + parameter.setType(dataType.toString()); + parameter.setRequired(parameterDef.isRequired()); + parameters[i] = parameter; + } + return parameters; + } + + public BigInteger getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(BigInteger workflowId) { + this.workflowId = workflowId; + } + + @Override + public Object invoke(Map argsMap) { + ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class); + return executor.execute(workflowId.toString(), argsMap); + } + + @Override + public String toString() { + return "AiWorkflowFunction{" + + "workflowId=" + workflowId + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", parameters=" + Arrays.toString(parameters) + + '}'; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainExecutorConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainExecutorConfig.java new file mode 100644 index 0000000..e0a64b3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainExecutorConfig.java @@ -0,0 +1,47 @@ +package tech.easyflow.ai.easyagentsflow.config; + +import com.easyagents.flow.core.chain.repository.ChainDefinitionRepository; +import com.easyagents.flow.core.chain.repository.ChainStateRepository; +import com.easyagents.flow.core.chain.repository.NodeStateRepository; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.easyflow.ai.easyagentsflow.listener.ChainErrorListenerForSave; +import tech.easyflow.ai.easyagentsflow.listener.ChainEventListenerForSave; +import tech.easyflow.ai.easyagentsflow.listener.NodeErrorListenerForSave; + +import javax.annotation.Resource; + +@Configuration +public class ChainExecutorConfig { + + @Resource + private ChainDefinitionRepository chainDefinitionRepository; + @Resource + private ChainStateRepository chainStateRepository; + @Resource + private NodeStateRepository nodeStateRepository; + @Resource + private ChainEventListenerForSave chainEventListenerForSave; + + @Bean(name = "chainExecutor") + public ChainExecutor chainExecutor() { + + ChainExecutor chainExecutor = new ChainExecutor(chainDefinitionRepository, + chainStateRepository, + nodeStateRepository); + + saveStepsListeners(chainExecutor); + + return chainExecutor; + } + + /** + * 步骤保存监听器 - 自行实现 + */ + private void saveStepsListeners(ChainExecutor chainExecutor) { + chainExecutor.addEventListener(chainEventListenerForSave); + chainExecutor.addErrorListener(new ChainErrorListenerForSave()); + chainExecutor.addNodeErrorListener(new NodeErrorListenerForSave()); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainParserConfig.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainParserConfig.java new file mode 100644 index 0000000..11a535b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/config/ChainParserConfig.java @@ -0,0 +1,24 @@ +package tech.easyflow.ai.easyagentsflow.config; + +import com.easyagents.flow.core.parser.ChainParser; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.easyflow.ai.easyagentsflow.service.TinyFlowConfigService; + +import javax.annotation.Resource; + +@Configuration +public class ChainParserConfig { + + @Resource + private TinyFlowConfigService tinyFlowConfigService; + + @Bean + public ChainParser chainParser() { + ChainParser chainParser = ChainParser.builder() + .withDefaultParsers(true) + .build(); + tinyFlowConfigService.initProvidersAndNodeParsers(chainParser); + return chainParser; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/ChainInfo.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/ChainInfo.java new file mode 100644 index 0000000..6a5cb2c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/ChainInfo.java @@ -0,0 +1,85 @@ +package tech.easyflow.ai.easyagentsflow.entity; + +import com.easyagents.flow.core.chain.ChainStatus; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class ChainInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 执行ID + */ + private String executeId; + /** + * 执行状态 + * @see ChainStatus + */ + private Integer status; + /** + * 消息,错误时显示 + */ + private String message; + /** + * 执行结果 + */ + private Map result; + /** + * 节点信息 + */ + private Map nodes = new HashMap<>(); + + public String getExecuteId() { + return executeId; + } + + public void setExecuteId(String executeId) { + this.executeId = executeId; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getResult() { + return result; + } + + public void setResult(Map result) { + this.result = result; + } + + public Map getNodes() { + return nodes; + } + + public void setNodes(Map nodes) { + this.nodes = nodes; + } + + @Override + public String toString() { + return "ChainInfo{" + + "executeId='" + executeId + '\'' + + ", status=" + status + + ", message='" + message + '\'' + + ", result=" + result + + ", nodes=" + nodes + + '}'; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/NodeInfo.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/NodeInfo.java new file mode 100644 index 0000000..5a3b4a8 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/entity/NodeInfo.java @@ -0,0 +1,99 @@ +package tech.easyflow.ai.easyagentsflow.entity; + +import com.easyagents.flow.core.chain.NodeStatus; +import com.easyagents.flow.core.chain.Parameter; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +public class NodeInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 节点ID + */ + private String nodeId; + /** + * 节点名称 + */ + private String nodeName; + /** + * 执行状态 + * @see NodeStatus + */ + private Integer status; + /** + * 消息,错误时显示 + */ + private String message; + /** + * 执行结果 + */ + private Map result; + /** + * 挂起时需要填写的参数 + */ + private List suspendForParameters; + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getResult() { + return result; + } + + public void setResult(Map result) { + this.result = result; + } + + public List getSuspendForParameters() { + return suspendForParameters; + } + + public void setSuspendForParameters(List suspendForParameters) { + this.suspendForParameters = suspendForParameters; + } + + @Override + public String toString() { + return "NodeInfo{" + + "nodeId='" + nodeId + '\'' + + ", nodeName='" + nodeName + '\'' + + ", status=" + status + + ", message='" + message + '\'' + + ", result=" + result + + ", suspendForParameters=" + suspendForParameters + + '}'; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/FileStorageProviderImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/FileStorageProviderImpl.java new file mode 100644 index 0000000..56ac86d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/FileStorageProviderImpl.java @@ -0,0 +1,19 @@ +package tech.easyflow.ai.easyagentsflow.filestorage; + +import com.easyagents.flow.core.filestoreage.FileStorage; +import com.easyagents.flow.core.filestoreage.FileStorageProvider; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +public class FileStorageProviderImpl implements FileStorageProvider { + + @Resource(name = "tinyFlowFileStorage") + private FileStorage tinyFlowFileStorage; + + @Override + public FileStorage getFileStorage() { + return tinyFlowFileStorage; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/StorageImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/StorageImpl.java new file mode 100644 index 0000000..5c2ef3b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/filestorage/StorageImpl.java @@ -0,0 +1,24 @@ +package tech.easyflow.ai.easyagentsflow.filestorage; + +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.filestoreage.FileStorage; +import com.easyagents.flow.core.node.BaseNode; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.node.InputStreamFile; +import tech.easyflow.common.filestorage.FileStorageService; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.util.Map; + +@Component(value = "tinyFlowFileStorage") +public class StorageImpl implements FileStorage { + + @Resource(name = "default") + private FileStorageService fileStorageService; + + @Override + public String saveFile(InputStream stream, Map headers, BaseNode node, Chain chain) { + return fileStorageService.save(new InputStreamFile(stream, headers)); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java new file mode 100644 index 0000000..78ba97d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/knowledge/KnowledgeProviderImpl.java @@ -0,0 +1,42 @@ +package tech.easyflow.ai.easyagentsflow.knowledge; + +import com.easyagents.core.document.Document; +import com.alibaba.fastjson2.JSONObject; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.knowledge.Knowledge; +import com.easyagents.flow.core.knowledge.KnowledgeProvider; +import com.easyagents.flow.core.node.KnowledgeNode; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.service.DocumentCollectionService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Component +public class KnowledgeProviderImpl implements KnowledgeProvider { + + @Resource + private DocumentCollectionService documentCollectionService; + + /** + * 获取知识库 + * @param id 知识库id + */ + @Override + public Knowledge getKnowledge(Object id) { + return new Knowledge() { + @Override + public List> search(String keyword, int limit, KnowledgeNode knowledgeNode, Chain chain) { + List documents = documentCollectionService.search(new BigInteger(id.toString()), keyword); + List> res = new ArrayList<>(); + for (Document document : documents) { + res.add(JSONObject.from(document)); + } + return res; + } + }; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainErrorListenerForSave.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainErrorListenerForSave.java new file mode 100644 index 0000000..014888a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainErrorListenerForSave.java @@ -0,0 +1,16 @@ +package tech.easyflow.ai.easyagentsflow.listener; + +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.listener.ChainErrorListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChainErrorListenerForSave implements ChainErrorListener { + + private static final Logger log = LoggerFactory.getLogger(ChainErrorListenerForSave.class); + + @Override + public void onError(Throwable error, Chain chain) { + log.error("ChainErrorListenerForFront: {}", chain, error); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainEventListenerForSave.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainEventListenerForSave.java new file mode 100644 index 0000000..d688170 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/ChainEventListenerForSave.java @@ -0,0 +1,169 @@ +package tech.easyflow.ai.easyagentsflow.listener; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.easyagents.flow.core.chain.*; +import com.easyagents.flow.core.chain.event.*; +import com.easyagents.flow.core.chain.listener.ChainEventListener; +import com.easyagents.flow.core.chain.repository.NodeStateField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.service.WorkflowExecResultService; +import tech.easyflow.ai.service.WorkflowExecStepService; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.ai.utils.WorkFlowUtil; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.EnumSet; + +@Component +public class ChainEventListenerForSave implements ChainEventListener { + + + private static final Logger log = LoggerFactory.getLogger(ChainEventListenerForSave.class); + + @Resource + private WorkflowService workflowService; + @Resource + private WorkflowExecResultService workflowExecResultService; + @Resource + private WorkflowExecStepService workflowExecStepService; + + @Override + public void onEvent(Event event, Chain chain) { + if (event instanceof ChainStartEvent) { + handleChainStartEvent((ChainStartEvent) event, chain); + } + if (event instanceof ChainEndEvent) { + handleChainEndEvent((ChainEndEvent) event, chain); + } + if (event instanceof NodeStartEvent) { + handleNodeStartEvent((NodeStartEvent) event, chain); + } + if (event instanceof NodeEndEvent) { + handleNodeEndEvent((NodeEndEvent) event, chain); + } + if (event instanceof ChainStatusChangeEvent) { + handleChainStatusChangeEvent((ChainStatusChangeEvent) event, chain); + } + if (event instanceof ChainResumeEvent) { + handleChainResumeEvent((ChainResumeEvent) event, chain); + } + } + + private void handleChainStartEvent(ChainStartEvent event, Chain chain) { + log.info("ChainStartEvent: {}", event); + ChainDefinition definition = chain.getDefinition(); + ChainState state = chain.getState(); + Workflow workflow = workflowService.getById(definition.getId()); + String instanceId = state.getInstanceId(); + WorkflowExecResult record = new WorkflowExecResult(); + record.setExecKey(instanceId); + record.setWorkflowId(workflow.getId()); + record.setTitle(workflow.getTitle()); + record.setDescription(workflow.getDescription()); + record.setInput(JSON.toJSONString(event.getVariables())); + record.setWorkflowJson(workflow.getContent()); + record.setStartTime(new Date()); + record.setStatus(state.getStatus().getValue()); + record.setCreatedKey(WorkFlowUtil.USER_KEY); + record.setCreatedBy(WorkFlowUtil.getOperator(chain).getId().toString()); + workflowExecResultService.save(record); + } + + private void handleChainEndEvent(ChainEndEvent event, Chain chain) { + log.info("ChainEndEvent: {}", event); + ChainState state = chain.getState(); + String instanceId = state.getInstanceId(); + WorkflowExecResult record = workflowExecResultService.getByExecKey(instanceId); + if (record == null) { + log.error("ChainEndEvent: record not found: {}", instanceId); + } else { + record.setEndTime(new Date()); + record.setStatus(state.getStatus().getValue()); + record.setOutput(JSON.toJSONString(state.getExecuteResult())); + ExceptionSummary error = state.getError(); + if (error != null) { + record.setErrorInfo(error.getRootCauseClass() + " --> " + error.getRootCauseMessage()); + } + workflowExecResultService.updateById(record); + } + } + + private void handleNodeStartEvent(NodeStartEvent event, Chain chain) { + log.info("NodeStartEvent: {}", event); + Node node = event.getNode(); + ChainState ancestorState = findAncestorState(chain.getState(), chain); + + String instanceId = ancestorState.getInstanceId(); + NodeState nodeState = chain.getNodeState(node.getId()); + + String execKey = IdUtil.fastSimpleUUID(); + chain.updateNodeStateSafely(node.getId(), state -> { + state.getMemory().put("executeId", execKey); + return EnumSet.of(NodeStateField.MEMORY); + }); + + WorkflowExecResult record = workflowExecResultService.getByExecKey(instanceId); + if (record == null) { + log.error("NodeStartEvent: record not found: {}", instanceId); + } else { + WorkflowExecStep step = new WorkflowExecStep(); + step.setRecordId(record.getId()); + step.setExecKey(execKey); + step.setNodeId(node.getId()); + step.setNodeName(node.getName()); + step.setInput(JSON.toJSONString(ancestorState.resolveParameters(node))); + step.setNodeData(JSON.toJSONString(node)); + step.setStartTime(new Date()); + step.setStatus(nodeState.getStatus().getValue()); + workflowExecStepService.save(step); + } + } + + private void handleNodeEndEvent(NodeEndEvent event, Chain chain) { + log.info("NodeEndEvent: {}", event); + Node node = event.getNode(); + NodeState nodeState = chain.getNodeState(node.getId()); + String execKey = nodeState.getMemory().get("executeId").toString(); + WorkflowExecStep step = workflowExecStepService.getByExecKey(execKey); + if (step == null) { + log.error("NodeEndEvent: step not found: {}", execKey); + } else { + step.setOutput(JSON.toJSONString(event.getResult())); + step.setEndTime(new Date()); + step.setStatus(nodeState.getStatus().getValue()); + ExceptionSummary error = nodeState.getError(); + if (error != null) { + step.setErrorInfo(error.getRootCauseClass() + " --> " + error.getRootCauseMessage()); + } + workflowExecStepService.updateById(step); + } + } + + private void handleChainStatusChangeEvent(ChainStatusChangeEvent event, Chain chain) { + log.info("ChainStatusChangeEvent: {}", event); + } + + private void handleChainResumeEvent(ChainResumeEvent event, Chain chain) { + log.info("ChainResumeEvent: {}", event); + } + + /** + * 递归查找顶级状态 + */ + private ChainState findAncestorState(ChainState state, Chain chain) { + String parentInstanceId = state.getParentInstanceId(); + if (StrUtil.isEmpty(parentInstanceId)) { + return state; + } + ChainState chainState = chain.getChainStateRepository().load(parentInstanceId); + return findAncestorState(chainState, chain); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/NodeErrorListenerForSave.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/NodeErrorListenerForSave.java new file mode 100644 index 0000000..db2c9de --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/listener/NodeErrorListenerForSave.java @@ -0,0 +1,19 @@ +package tech.easyflow.ai.easyagentsflow.listener; + +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.Node; +import com.easyagents.flow.core.chain.listener.NodeErrorListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class NodeErrorListenerForSave implements NodeErrorListener { + + private static final Logger log = LoggerFactory.getLogger(NodeErrorListenerForSave.class); + + @Override + public void onError(Throwable error, Node node, Map nodeResult, Chain chain) { + log.error("NodeErrorListenerForFront: {}", node, error); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/llm/LlmProviderImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/llm/LlmProviderImpl.java new file mode 100644 index 0000000..57e5e20 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/llm/LlmProviderImpl.java @@ -0,0 +1,33 @@ +package tech.easyflow.ai.easyagentsflow.llm; + +import com.easyagents.flow.support.provider.EasyAgentsLlm; +import com.easyagents.flow.core.llm.Llm; +import com.easyagents.flow.core.llm.LlmProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.service.ModelService; + +import javax.annotation.Resource; +import java.math.BigInteger; + +@Component +public class LlmProviderImpl implements LlmProvider { + + private static final Logger log = LoggerFactory.getLogger(LlmProviderImpl.class); + @Resource + private ModelService modelService; + + @Override + public Llm getChatModel(Object modelId) { + Model model = modelService.getModelInstance(new BigInteger(modelId.toString())); + if (model == null) { + log.error("LlmProviderImpl.getChatModel: modelId not found: {}", modelId); + return null; + } + EasyAgentsLlm llm = new EasyAgentsLlm(); + llm.setChatModel(model.toChatModel()); + return llm; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/BaseRepository.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/BaseRepository.java new file mode 100644 index 0000000..9952201 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/BaseRepository.java @@ -0,0 +1,27 @@ +package tech.easyflow.ai.easyagentsflow.repository; + +import com.alicp.jetcache.Cache; + +import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; + +public class BaseRepository { + + @Resource(name = "defaultCache") + private Cache cache; + + /** + * chain 的相关状态缓存三天 + */ + protected void putCache(String key, Object value) { + cache.put(key, value, 3, TimeUnit.DAYS); + } + + protected T getCache(String key, Class clazz) { + Object value = cache.get(key); + if (value == null) { + return null; + } + return clazz.cast(value); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainDefinitionRepositoryImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainDefinitionRepositoryImpl.java new file mode 100644 index 0000000..9749a1f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainDefinitionRepositoryImpl.java @@ -0,0 +1,30 @@ +package tech.easyflow.ai.easyagentsflow.repository; + +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.repository.ChainDefinitionRepository; +import com.easyagents.flow.core.parser.ChainParser; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.WorkflowService; + +import javax.annotation.Resource; + +@Component +public class ChainDefinitionRepositoryImpl implements ChainDefinitionRepository { + + @Resource + private WorkflowService workflowService; + @Resource + private ChainParser chainParser; + + @Override + public ChainDefinition getChainDefinitionById(String id) { + Workflow workflow = workflowService.getById(id); + String json = workflow.getContent(); + ChainDefinition chainDefinition = chainParser.parse(json); + chainDefinition.setId(workflow.getId().toString()); + chainDefinition.setName(workflow.getEnglishName()); + chainDefinition.setDescription(workflow.getDescription()); + return chainDefinition; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainStateRepositoryImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainStateRepositoryImpl.java new file mode 100644 index 0000000..25595c8 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/ChainStateRepositoryImpl.java @@ -0,0 +1,32 @@ +package tech.easyflow.ai.easyagentsflow.repository; + +import com.easyagents.flow.core.chain.ChainState; +import com.easyagents.flow.core.chain.repository.ChainStateField; +import com.easyagents.flow.core.chain.repository.ChainStateRepository; +import org.springframework.stereotype.Component; +import tech.easyflow.common.constant.CacheKey; + +import java.util.EnumSet; + +@Component +public class ChainStateRepositoryImpl extends BaseRepository implements ChainStateRepository { + + @Override + public ChainState load(String instanceId) { + String key = CacheKey.CHAIN_CACHE_KEY + instanceId; + ChainState chainState = getCache(key, ChainState.class); + if (chainState == null) { + chainState = new ChainState(); + chainState.setInstanceId(instanceId); + putCache(key, chainState); + } + return chainState; + } + + @Override + public boolean tryUpdate(ChainState newState, EnumSet fields) { + String key = CacheKey.CHAIN_CACHE_KEY + newState.getInstanceId(); + putCache(key, newState); + return true; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/NodeStateRepositoryImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/NodeStateRepositoryImpl.java new file mode 100644 index 0000000..9eb783e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/repository/NodeStateRepositoryImpl.java @@ -0,0 +1,33 @@ +package tech.easyflow.ai.easyagentsflow.repository; + +import com.easyagents.flow.core.chain.NodeState; +import com.easyagents.flow.core.chain.repository.NodeStateField; +import com.easyagents.flow.core.chain.repository.NodeStateRepository; +import org.springframework.stereotype.Component; +import tech.easyflow.common.constant.CacheKey; + +import java.util.EnumSet; + +@Component +public class NodeStateRepositoryImpl extends BaseRepository implements NodeStateRepository { + + @Override + public NodeState load(String instanceId, String nodeId) { + String key = CacheKey.NODE_CACHE_KEY + instanceId + ":" + nodeId; + NodeState nodeState = getCache(key, NodeState.class); + if (nodeState == null) { + nodeState = new NodeState(); + nodeState.setChainInstanceId(instanceId); + nodeState.setNodeId(nodeId); + putCache(key, nodeState); + } + return nodeState; + } + + @Override + public boolean tryUpdate(NodeState newState, EnumSet fields, long chainStateVersion) { + String key = CacheKey.NODE_CACHE_KEY + newState.getChainInstanceId() + ":" + newState.getNodeId(); + putCache(key, newState); + return true; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java new file mode 100644 index 0000000..b5c6f5b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowConfigService.java @@ -0,0 +1,91 @@ +package tech.easyflow.ai.easyagentsflow.service; + +import com.easyagents.flow.core.filestoreage.FileStorageManager; +import com.easyagents.flow.core.filestoreage.FileStorageProvider; +import com.easyagents.flow.core.knowledge.KnowledgeManager; +import com.easyagents.flow.core.knowledge.KnowledgeProvider; +import com.easyagents.flow.core.llm.LlmManager; +import com.easyagents.flow.core.llm.LlmProvider; +import com.easyagents.flow.core.parser.ChainParser; +import com.easyagents.flow.core.searchengine.SearchEngine; +import com.easyagents.flow.core.searchengine.SearchEngineManager; +import com.easyagents.flow.core.searchengine.SearchEngineProvider; +import com.easyagents.flow.core.searchengine.impl.BochaaiSearchEngineImpl; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.config.BochaaiProps; +import tech.easyflow.ai.node.*; + +import javax.annotation.Resource; + +@Component +public class TinyFlowConfigService { + + @Resource + private LlmProvider llmProvider; + @Resource + private FileStorageProvider fileStorageProvider; + @Resource + private BochaaiProps bochaaiProps; + @Resource + private KnowledgeProvider knowledgeProvider; + + public void initProvidersAndNodeParsers(ChainParser chainParser) { + setExtraNodeParser(chainParser); + setLlmProvider(); + setFileStorage(); + setKnowledgeProvider(); + setSearchEngineProvider(); + } + + private void setFileStorage() { + FileStorageManager.getInstance().registerProvider(fileStorageProvider); + } + + public void setExtraNodeParser(ChainParser chainParser) { + + // 文档解析 + DocNodeParser docNodeParser = new DocNodeParser(); + // 文件生成 + MakeFileNodeParser makeFileNodeParser = new MakeFileNodeParser(); + // 插件 + PluginToolNodeParser pluginToolNodeParser = new PluginToolNodeParser(); + // SQL查询 + SqlNodeParser sqlNodeParser = new SqlNodeParser(); + // 下载文件节点 + DownloadNodeParser downloadNodeParser = new DownloadNodeParser(); + // 保存数据节点 + SaveToDatacenterNodeParser saveDaveParser = new SaveToDatacenterNodeParser(); + // 查询数据节点 + SearchDatacenterNodeParser searchDatacenterNodeParser = new SearchDatacenterNodeParser(); + // 工作流节点 + WorkflowNodeParser workflowNodeParser = new WorkflowNodeParser(); + + chainParser.addNodeParser(docNodeParser.getNodeName(), docNodeParser); + chainParser.addNodeParser(makeFileNodeParser.getNodeName(), makeFileNodeParser); + chainParser.addNodeParser(pluginToolNodeParser.getNodeName(), pluginToolNodeParser); + chainParser.addNodeParser(sqlNodeParser.getNodeName(), sqlNodeParser); + chainParser.addNodeParser(downloadNodeParser.getNodeName(), downloadNodeParser); + chainParser.addNodeParser(saveDaveParser.getNodeName(), saveDaveParser); + chainParser.addNodeParser(searchDatacenterNodeParser.getNodeName(), searchDatacenterNodeParser); + chainParser.addNodeParser(workflowNodeParser.getNodeName(), workflowNodeParser); + } + + public void setSearchEngineProvider() { + BochaaiSearchEngineImpl engine = new BochaaiSearchEngineImpl(); + engine.setApiKey(bochaaiProps.getApiKey()); + SearchEngineManager.getInstance().registerProvider(new SearchEngineProvider() { + @Override + public SearchEngine getSearchEngine(Object id) { + return id.equals("bocha-search") ? engine : null; + } + }); + } + + public void setLlmProvider() { + LlmManager.getInstance().registerProvider(llmProvider); + } + + public void setKnowledgeProvider() { + KnowledgeManager.getInstance().registerProvider(knowledgeProvider); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowService.java new file mode 100644 index 0000000..a719744 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/TinyFlowService.java @@ -0,0 +1,91 @@ +package tech.easyflow.ai.easyagentsflow.service; + +import com.easyagents.flow.core.chain.ChainState; +import com.easyagents.flow.core.chain.ExceptionSummary; +import com.easyagents.flow.core.chain.NodeState; +import com.easyagents.flow.core.chain.repository.ChainStateRepository; +import com.easyagents.flow.core.chain.repository.NodeStateRepository; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.easyagentsflow.entity.ChainInfo; +import tech.easyflow.ai.easyagentsflow.entity.NodeInfo; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@Component +public class TinyFlowService { + + @Resource + private ChainExecutor chainExecutor; + + /** + * 获取执行状态 + */ + public ChainInfo getChainStatus(String executeId, List nodes) { + + ChainStateRepository chainStateRepository = chainExecutor.getChainStateRepository(); + NodeStateRepository nodeStateRepository = chainExecutor.getNodeStateRepository(); + + ChainState chainState = chainStateRepository.load(executeId); + ChainInfo res = getChainInfo(executeId, chainState); + + for (NodeInfo node : nodes) { + processNodeState(executeId, node, chainStateRepository, nodeStateRepository); + res.getNodes().put(node.getNodeId(), node); + } + return res; + } + + /** + * 处理节点状态 + */ + private void processNodeState(String currentExecuteId, + NodeInfo node, + ChainStateRepository chainStateRepository, + NodeStateRepository nodeStateRepository) { + + // 加载当前层的状态 + ChainState currentChainState = chainStateRepository.load(currentExecuteId); + NodeState currentNodeState = nodeStateRepository.load(currentExecuteId, node.getNodeId()); + + setNodeStatus(node, currentNodeState, currentChainState); + } + + private static ChainInfo getChainInfo(String executeId, ChainState chainState) { + ChainInfo res = new ChainInfo(); + res.setExecuteId(executeId); + res.setStatus(chainState.getStatus().getValue()); + ExceptionSummary chainError = chainState.getError(); + if (chainError != null) { + res.setMessage(chainError.getRootCauseClass() + " --> " + chainError.getRootCauseMessage()); + } + Map executeResult = chainState.getExecuteResult(); + if (executeResult != null && !executeResult.isEmpty()) { + res.setResult(executeResult); + } + return res; + } + + private void setNodeStatus(NodeInfo node, NodeState nodeState, ChainState chainState) { + String nodeId = node.getNodeId(); + // 如果状态为空或不存在,可能不需要覆盖,这里视具体业务逻辑而定,目前保持原逻辑 + node.setStatus(nodeState.getStatus().getValue()); + + ExceptionSummary error = nodeState.getError(); + if (error != null) { + node.setMessage(error.getRootCauseClass() + " --> " + error.getRootCauseMessage()); + } + + Map nodeExecuteResult = chainState.getNodeExecuteResult(nodeId); + if (nodeExecuteResult != null && !nodeExecuteResult.isEmpty()) { + node.setResult(nodeExecuteResult); + } + + // 只有当参数不为空时才覆盖 + if (chainState.getSuspendForParameters() != null) { + node.setSuspendForParameters(chainState.getSuspendForParameters()); + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Bot.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Bot.java new file mode 100644 index 0000000..b93667c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Bot.java @@ -0,0 +1,31 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.BotBase; +import com.mybatisflex.annotation.Table; + +import java.util.Map; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_bot") +public class Bot extends BotBase { + + public static final String KEY_SYSTEM_PROMPT = "systemPrompt"; + public static final String KEY_MAX_MESSAGE_COUNT = "maxMessageCount"; + public static final String KEY_ENABLE_DEEP_THINKING = "enableDeepThinking"; + + public boolean isAnonymousEnabled() { + Map options = getOptions(); + if (options == null) { + return false; + } + Object o = options.get("anonymousEnabled"); + return o != null && (boolean) o; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotCategory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotCategory.java new file mode 100644 index 0000000..a439e7c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotCategory.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotCategoryBase; + +/** + * bot分类 实体类。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@Table(value = "tb_bot_category", comment = "bot分类") +public class BotCategory extends BotCategoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotConversation.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotConversation.java new file mode 100644 index 0000000..9822606 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotConversation.java @@ -0,0 +1,41 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.RelationOneToOne; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotConversationBase; + +import java.util.List; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-04-15 + */ +@Table("tb_bot_conversation") +public class BotConversation extends BotConversationBase { + @Column(ignore = true) + private List botMessageList; + + @RelationOneToOne(selfField = "botId", targetField = "id") + private Bot bot; + + public List getAiBotMessageList() { + return botMessageList; + } + + public void setAiBotMessageList(List botMessageList) { + this.botMessageList = botMessageList; + } + + public Bot getBot() { + return bot; + } + + public void setBot(Bot bot) { + this.bot = bot; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotDocumentCollection.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotDocumentCollection.java new file mode 100644 index 0000000..eac0b00 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotDocumentCollection.java @@ -0,0 +1,27 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.BotDocumentCollectionBase; +import com.mybatisflex.annotation.RelationOneToOne; +import com.mybatisflex.annotation.Table; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-28 + */ + +@Table("tb_bot_document_collection") +public class BotDocumentCollection extends BotDocumentCollectionBase { + + @RelationOneToOne(selfField = "documentCollectionId", targetField = "id") + private DocumentCollection knowledge; + + public DocumentCollection getKnowledge() { + return knowledge; + } + + public void setKnowledge(DocumentCollection knowledge) { + this.knowledge = knowledge; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMcp.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMcp.java new file mode 100644 index 0000000..a4b9ad4 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMcp.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotMcpBase; + + +/** + * 实体类。 + * + * @author wangGangQiang + * @since 2026-01-05 + */ +@Table("tb_bot_mcp") +public class BotMcp extends BotMcpBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMessage.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMessage.java new file mode 100644 index 0000000..a23cd60 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotMessage.java @@ -0,0 +1,73 @@ +package tech.easyflow.ai.entity; + +import com.easyagents.core.message.*; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotMessageBase; +import tech.easyflow.core.chat.protocol.ChatDomain; +import tech.easyflow.core.chat.protocol.MessageRole; + + +/** + * Bot 消息记录表 实体类。 + * + * @author michael + * @since 2024-11-04 + */ + +@Table(value = "tb_bot_message", comment = "Bot 消息记录表") +public class BotMessage extends BotMessageBase { + + @Column(ignore = true) + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public void setContentAndRole(Message msg) { + String jsonMessage = JSON.toJSONString(msg, SerializerFeature.WriteClassName); + super.setContent(jsonMessage); + + if (msg instanceof AiMessage) { + super.setRole(MessageRole.ASSISTANT.getValue()); + } else if (msg instanceof UserMessage) { + super.setRole(MessageRole.USER.getValue()); + } else if (msg instanceof SystemMessage) { + super.setRole(MessageRole.SYSTEM.getValue()); + } else if (msg instanceof ToolMessage) { + super.setRole(MessageRole.TOOL.getValue()); + } + } + + public Message getContentAsMessage() { + String role = getRole(); + if (MessageRole.ASSISTANT.getValue().equals(role)) { + return parseMessage(AiMessage.class); + } else if (MessageRole.USER.getValue().equals(role)) { + return parseMessage(UserMessage.class); + } else if (MessageRole.SYSTEM.getValue().equals(role)) { + return parseMessage(SystemMessage.class); + } else if (MessageRole.TOOL.getValue().equals(role)) { + return parseMessage(ToolMessage.class); + } + return null; + } + + private T parseMessage(Class clazz) { + return JSON.parseObject( + getContent(), + clazz, + Feature.SupportClassForName, + Feature.SupportAutoType); + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotModel.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotModel.java new file mode 100644 index 0000000..860549c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotModel.java @@ -0,0 +1,28 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotModelBase; + +import java.util.Map; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-28 + */ + +@Table("tb_bot_model") +public class BotModel extends BotModelBase { + + public Object getOption(String key) { + Map options = super.getOptions(); + return options == null ? null : options.get(key); + } + + public Object getOptionOrDefault(String key, Object defaultValue) { + Map options = super.getOptions(); + return options == null ? defaultValue : options.getOrDefault(key, defaultValue); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotPlugin.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotPlugin.java new file mode 100644 index 0000000..cdd8be1 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotPlugin.java @@ -0,0 +1,27 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.RelationOneToOne; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotPluginBase; + + +/** + * 实体类。 + * + * @author michael + * @since 2025-04-07 + */ +@Table("tb_bot_plugin") +public class BotPlugin extends BotPluginBase { + + @RelationOneToOne(selfField = "pluginId", targetField = "id") + private Plugin plugin; + + public Plugin getAiPlugin() { + return plugin; + } + + public void setAiPlugin(Plugin plugin) { + this.plugin = plugin; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotRecentlyUsed.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotRecentlyUsed.java new file mode 100644 index 0000000..871da34 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotRecentlyUsed.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.BotRecentlyUsedBase; + +/** + * 最近使用 实体类。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@Table(value = "tb_bot_recently_used", comment = "最近使用") +public class BotRecentlyUsed extends BotRecentlyUsedBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotWorkflow.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotWorkflow.java new file mode 100644 index 0000000..5c1d21d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/BotWorkflow.java @@ -0,0 +1,27 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.BotWorkflowBase; +import com.mybatisflex.annotation.RelationOneToOne; +import com.mybatisflex.annotation.Table; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-28 + */ + +@Table("tb_bot_workflow") +public class BotWorkflow extends BotWorkflowBase { + + @RelationOneToOne(selfField = "workflowId", targetField = "id") + private Workflow workflow; + + public Workflow getWorkflow() { + return workflow; + } + + public void setWorkflow(Workflow workflow) { + this.workflow = workflow; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ChatRequestParams.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ChatRequestParams.java new file mode 100644 index 0000000..f860c69 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ChatRequestParams.java @@ -0,0 +1,89 @@ +package tech.easyflow.ai.entity; + +import com.easyagents.core.message.*; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +public class ChatRequestParams implements Serializable { + private static final long serialVersionUID = 1L; + + private BigInteger botId; + private String conversationId; + private List messages; + + public ChatRequestParams() { + } + + @JsonProperty("messages") + public void setMessagesFromJson(List rawMessages) { + if (rawMessages == null) { + this.messages = null; + return; + } + + this.messages = new ArrayList<>(); + for (Object raw : rawMessages) { + Message message = convertToMessage(raw); + if (message != null) { + this.messages.add(message); + } + } + } + + private Message convertToMessage(Object raw) { + JSONObject jsonObj = JSONObject.from(raw); + if (jsonObj == null) { + return null; + } + + String role = jsonObj.getString("role"); + String content = jsonObj.getString("content"); + if (role == null || role.isBlank()) { + throw new IllegalArgumentException("Invalid role: " + role); + } + if ("user".equals(role)) { + if (content == null || content.isBlank()) { + throw new IllegalArgumentException("User message cannot be empty"); + } + } + + return switch (role) { + case "user" -> jsonObj.toJavaObject(UserMessage.class); + case "system" -> jsonObj.toJavaObject(SystemMessage.class); + case "assistant" -> jsonObj.toJavaObject(AiMessage.class); + case "tool" -> jsonObj.toJavaObject(ToolMessage.class); + default -> { + UserMessage defaultMsg = new UserMessage(); + defaultMsg.setContent(content); + yield defaultMsg; + } + }; + } + + public List getMessages() { + return messages; + } + + public void setMessages(List messages) { + this.messages = messages; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public String getConversationId() {return conversationId;} + + public void setConversationId(String conversationId) {this.conversationId = conversationId;} +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Document.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Document.java new file mode 100644 index 0000000..2ab81fe --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Document.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.DocumentBase; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; + +import java.math.BigInteger; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_document") +public class Document extends DocumentBase { + + // 每条document对应的有多少条documentChunk分段 + @Column(ignore = true) + private BigInteger chunkCount; + + /** + * 分块最大长度 + */ + @Column(ignore = true) + private int chunkSize; + + /** + * 分块之间的重叠长度 + */ + @Column(ignore = true) + private int overlapSize; + + public BigInteger getChunkCount() { + return chunkCount; + } + + public void setChunkCount(BigInteger chunkCount) { + this.chunkCount = chunkCount; + } + + public int getChunkSize() { + return chunkSize; + } + + public void setChunkSize(int chunkSize) { + this.chunkSize = chunkSize; + } + + public int getOverlapSize() { + return overlapSize; + } + + public void setOverlapSize(int overlapSize) { + this.overlapSize = overlapSize; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentChunk.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentChunk.java new file mode 100644 index 0000000..a40482f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentChunk.java @@ -0,0 +1,102 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.DocumentChunkBase; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.RelationOneToOne; +import com.mybatisflex.annotation.Table; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_document_chunk") +public class DocumentChunk extends DocumentChunkBase { + + @RelationOneToOne(selfField = "documentId", + targetTable = "tb_document", + targetField = "id", + valueField = "title") + @Column(ignore = true) + private String title; + + /** + * 相似度 + */ + @Column(ignore = true) + private Double similarityScore; + + /** + * 向量 相似度 + */ + @Column(ignore = true) + private Double vectorSimilarityScore; + + /** + * elasticSearch 相似度 + */ + @Column(ignore = true) + private Double elasticSimilarityScore; + + /** + * 元数据关键词 + */ + @Column(ignore = true) + private String[] metadataKeyWords; + + /** + * 元数据问题 + */ + @Column(ignore = true) + private String[] metadataQuestions; + + public Double getSimilarityScore() { + return similarityScore; + } + + public void setSimilarityScore(Double similarityScore) { + this.similarityScore = similarityScore; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String[] getMetadataKeyWords() { + return metadataKeyWords; + } + + public void setMetadataKeyWords(String[] metadataKeyWords) { + this.metadataKeyWords = metadataKeyWords; + } + + public String[] getMetadataQuestions() { + return metadataQuestions; + } + + public void setMetadataQuestions(String[] metadataQuestions) { + this.metadataQuestions = metadataQuestions; + } + + public Double getElasticSimilarityScore() { + return elasticSimilarityScore; + } + + public void setElasticSimilarityScore(Double elasticSimilarityScore) { + this.elasticSimilarityScore = elasticSimilarityScore; + } + + public Double getVectorSimilarityScore() { + return vectorSimilarityScore; + } + + public void setVectorSimilarityScore(Double vectorSimilarityScore) { + this.vectorSimilarityScore = vectorSimilarityScore; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollection.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollection.java new file mode 100644 index 0000000..5063b33 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollection.java @@ -0,0 +1,160 @@ +package tech.easyflow.ai.entity; + +import com.easyagents.core.model.chat.tool.Tool; +import com.easyagents.core.store.DocumentStore; +import com.easyagents.store.aliyun.AliyunVectorStore; +import com.easyagents.store.aliyun.AliyunVectorStoreConfig; +import com.easyagents.store.elasticsearch.ElasticSearchVectorStore; +import com.easyagents.store.elasticsearch.ElasticSearchVectorStoreConfig; +import com.easyagents.store.opensearch.OpenSearchVectorStore; +import com.easyagents.store.opensearch.OpenSearchVectorStoreConfig; +import com.easyagents.store.qcloud.QCloudVectorStore; +import com.easyagents.store.qcloud.QCloudVectorStoreConfig; +import com.easyagents.store.redis.RedisVectorStore; +import com.easyagents.store.redis.RedisVectorStoreConfig; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.easyagents.tool.DocumentCollectionTool; +import tech.easyflow.ai.entity.base.DocumentCollectionBase; +import tech.easyflow.common.util.PropertiesUtil; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_document_collection") +public class DocumentCollection extends DocumentCollectionBase { + + /** + * 文档块问题集合配置key + */ + public static final String KEY_CHUNK_QUESTION_VECTOR_STORE_COLLECTION = "chunkQuestionVectorStoreCollection"; + + /** + * 文档块摘要集合配置key + */ + public static final String KEY_CHUNK_SUMMARY_VECTOR_STORE_COLLECTION = "chunkSummaryVectorStoreCollection"; + + /** + * 知识召回最大条数 + */ + public static final String KEY_DOC_RECALL_MAX_NUM = "docRecallMaxNum"; + + /** + * 相似度最小值 + */ + public static final String KEY_SIMILARITY_THRESHOLD = "simThreshold"; + + /** + * 搜索引擎类型 + */ + public static final String KEY_SEARCH_ENGINE_TYPE = "searchEngineType"; + + /** + * 是否允许更新向量模型 + */ + public static final String KEY_CAN_UPDATE_EMBEDDING_MODEL = "canUpdateEmbeddingModel"; + + public DocumentStore toDocumentStore() { + String storeType = this.getVectorStoreType(); + if (StringUtil.noText(storeType)) { + throw new BusinessException("向量数据库类型未设置"); + } + if (storeType == null) { + return null; + } + switch (storeType.toLowerCase()) { + case "redis": + return redisStore(); +// case "milvus": +// return milvusStore(); + case "opensearch": + return openSearchStore(); + case "elasticsearch": + return elasticSearchStore(); + case "aliyun": + return aliyunStore(); + case "qcloud": + return qcloudStore(); + } + return null; + } + + public boolean isVectorStoreEnabled() { + return this.getVectorStoreEnable() != null && this.getVectorStoreEnable(); + } + + public boolean isSearchEngineEnabled() { + return this.getSearchEngineEnable() != null && this.getSearchEngineEnable(); + } + + + private DocumentStore redisStore() { + RedisVectorStoreConfig redisVectorStoreConfig = getStoreConfig(RedisVectorStoreConfig.class); + return new RedisVectorStore(redisVectorStoreConfig); + } + +// private DocumentStore milvusStore() { +// MilvusVectorStoreConfig milvusVectorStoreConfig = getStoreConfig(MilvusVectorStoreConfig.class); +// return new MilvusVectorStore(milvusVectorStoreConfig); +// } + + private DocumentStore openSearchStore() { + OpenSearchVectorStoreConfig openSearchVectorStoreConfig = getStoreConfig(OpenSearchVectorStoreConfig.class); + return new OpenSearchVectorStore(openSearchVectorStoreConfig); + } + + private DocumentStore elasticSearchStore() { + ElasticSearchVectorStoreConfig elasticSearchVectorStoreConfig = getStoreConfig(ElasticSearchVectorStoreConfig.class); + return new ElasticSearchVectorStore(elasticSearchVectorStoreConfig); + } + + private DocumentStore aliyunStore() { + AliyunVectorStoreConfig aliyunVectorStoreConfig = getStoreConfig(AliyunVectorStoreConfig.class); + return new AliyunVectorStore(aliyunVectorStoreConfig); + } + + private DocumentStore qcloudStore() { + QCloudVectorStoreConfig qCloudVectorStoreConfig = getStoreConfig(QCloudVectorStoreConfig.class); + return new QCloudVectorStore(qCloudVectorStoreConfig); + } + + private T getStoreConfig(Class clazz) { + return PropertiesUtil.propertiesTextToEntity(this.getVectorStoreConfig(), clazz); + } + + public Tool toFunction(boolean needEnglishName) { + return new DocumentCollectionTool(this, needEnglishName); + } + + public Object getOptionsByKey(String key) { + Map options = this.getOptions(); + if (options == null) { + return null; + } + if (KEY_DOC_RECALL_MAX_NUM.equals(key) && !options.containsKey(KEY_DOC_RECALL_MAX_NUM)) { + return 5; + } + if (KEY_SIMILARITY_THRESHOLD.equals(key)) { + if (!options.containsKey(KEY_SIMILARITY_THRESHOLD)) { + return 0.6f; + } else { + BigDecimal score = (BigDecimal) options.get(key); + return (float) score.doubleValue(); + } + } + if (KEY_SEARCH_ENGINE_TYPE.equals(key)) { + if (!options.containsKey(KEY_SEARCH_ENGINE_TYPE)) { + return "lucene"; + } + } + return options.get(key); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionCategory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionCategory.java new file mode 100644 index 0000000..448f303 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionCategory.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.DocumentCollectionCategoryBase; + + +/** + * 实体类。 + * + * @author 12076 + * @since 2026-01-23 + */ +@Table("tb_document_collection_category") +public class DocumentCollectionCategory extends DocumentCollectionCategoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionSplitParams.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionSplitParams.java new file mode 100644 index 0000000..54ba2f9 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentCollectionSplitParams.java @@ -0,0 +1,146 @@ +package tech.easyflow.ai.entity; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * 文本拆分参数 + */ +public class DocumentCollectionSplitParams implements Serializable { + private static final long serialVersionUID = 1L; + private Integer pageNumber = 1; + private Integer pageSize = 10; + /** + * 拆分操作 textSplit 拆分预览/ saveText 保存 + */ + private String operation; + /** + * 文件路径 + */ + private String filePath; + /** + * 文件原始名称 + */ + private String fileOriginName; + /** + * 知识库id + */ + private BigInteger knowledgeId; + /** + * 拆分器名称 + */ + private String splitterName; + /** + * 分段大小 + */ + private Integer chunkSize = 512; + /** + * 重叠大小 + */ + private Integer overlapSize = 128; + /** + * 正则表达式 + */ + private String regex; + private Integer rowsPerChunk = 1; + /** + * markDown 层级拆分级别 + */ + private Integer mdSplitterLevel; + + public Integer getPageNumber() { + return pageNumber; + } + + public void setPageNumber(Integer pageNumber) { + this.pageNumber = pageNumber; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getFileOriginName() { + return fileOriginName; + } + + public void setFileOriginName(String fileOriginName) { + this.fileOriginName = fileOriginName; + } + + public BigInteger getKnowledgeId() { + return knowledgeId; + } + + public void setKnowledgeId(BigInteger knowledgeId) { + this.knowledgeId = knowledgeId; + } + + public String getSplitterName() { + return splitterName; + } + + public void setSplitterName(String splitterName) { + this.splitterName = splitterName; + } + + public Integer getChunkSize() { + return chunkSize; + } + + public void setChunkSize(Integer chunkSize) { + this.chunkSize = chunkSize; + } + + public Integer getOverlapSize() { + return overlapSize; + } + + public void setOverlapSize(Integer overlapSize) { + this.overlapSize = overlapSize; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public Integer getRowsPerChunk() { + return rowsPerChunk; + } + + public void setRowsPerChunk(Integer rowsPerChunk) { + this.rowsPerChunk = rowsPerChunk; + } + + public Integer getMdSplitterLevel() { + return mdSplitterLevel; + } + + public void setMdSplitterLevel(Integer mdSplitterLevel) { + this.mdSplitterLevel = mdSplitterLevel; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentHistory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentHistory.java new file mode 100644 index 0000000..0e9eacd --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/DocumentHistory.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import tech.easyflow.ai.entity.base.DocumentHistoryBase; +import com.mybatisflex.annotation.Table; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_document_history") +public class DocumentHistory extends DocumentHistoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Mcp.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Mcp.java new file mode 100644 index 0000000..d9b4341 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Mcp.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import io.modelcontextprotocol.spec.McpSchema; +import tech.easyflow.ai.entity.base.McpBase; + +import java.util.List; + + +/** + * 实体类。 + * + * @author wangGangQiang + * @since 2026-01-04 + */ +@Table("tb_mcp") +public class Mcp extends McpBase { + + @Column(ignore = true) + private List tools; + + /** + * 该MCP服务是否存活 + */ + @Column(ignore = true) + private boolean alive; + + /** + * 客户端健康状态 + */ + @Column(ignore = true) + private Boolean clientOnline; + + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } + + public List getTools() { + return tools; + } + + public void setTools(List tools) { + this.tools = tools; + } + + public Boolean getClientOnline() { + return clientOnline; + } + + public void setClientOnline(Boolean clientOnline) { + this.clientOnline = clientOnline; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Model.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Model.java new file mode 100644 index 0000000..44baafe --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Model.java @@ -0,0 +1,185 @@ + +package tech.easyflow.ai.entity; + +import cn.hutool.core.util.StrUtil; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.embedding.EmbeddingModel; +import com.easyagents.core.model.rerank.RerankModel; +import com.easyagents.core.store.VectorData; +import com.easyagents.embedding.ollama.OllamaEmbeddingConfig; +import com.easyagents.embedding.ollama.OllamaEmbeddingModel; +import com.easyagents.embedding.openai.OpenAIEmbeddingConfig; +import com.easyagents.embedding.openai.OpenAIEmbeddingModel; +import com.easyagents.llm.deepseek.DeepseekChatModel; +import com.easyagents.llm.deepseek.DeepseekConfig; +import com.easyagents.llm.ollama.OllamaChatConfig; +import com.easyagents.llm.ollama.OllamaChatModel; +import com.easyagents.llm.openai.OpenAIChatConfig; +import com.easyagents.llm.openai.OpenAIChatModel; +import com.easyagents.rerank.DefaultRerankModel; +import com.easyagents.rerank.DefaultRerankModelConfig; +import com.easyagents.rerank.gitee.GiteeRerankModel; +import com.easyagents.rerank.gitee.GiteeRerankModelConfig; +import com.mybatisflex.annotation.RelationManyToOne; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.ModelBase; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_model") +public class Model extends ModelBase { + + @RelationManyToOne(selfField = "providerId", targetField = "id") + private ModelProvider modelProvider; + + /** + * 模型类型 + */ + public final static String[] MODEL_TYPES = {"chatModel", "embeddingModel", "rerankModel"}; + + + public ModelProvider getModelProvider() { + return modelProvider; + } + + public void setModelProvider(ModelProvider modelProvider) { + this.modelProvider = modelProvider; + } + + public ChatModel toChatModel() { + String providerType = modelProvider.getProviderType(); + if (StringUtil.noText(providerType)) { + return null; + } + switch (providerType.toLowerCase()) { + case "ollama": + OllamaChatConfig ollamaChatConfig = new OllamaChatConfig(); + ollamaChatConfig.setEndpoint(checkAndGetEndpoint()); + if (StringUtil.hasText(getApiKey())) { + ollamaChatConfig.setApiKey(checkAndGetApiKey()); + } + ollamaChatConfig.setModel(checkAndGetModelName()); + ollamaChatConfig.setProvider(getModelProvider().getProviderName()); + return new OllamaChatModel(ollamaChatConfig); + case "deepseek": + DeepseekConfig deepseekConfig = new DeepseekConfig(); + deepseekConfig.setProvider(getModelProvider().getProviderName()); + deepseekConfig.setEndpoint(checkAndGetEndpoint()); + deepseekConfig.setApiKey(checkAndGetApiKey()); + deepseekConfig.setModel(checkAndGetModelName()); + deepseekConfig.setRequestPath(checkAndGetRequestPath()); + return new DeepseekChatModel(deepseekConfig); + default: + OpenAIChatConfig openAIChatConfig = new OpenAIChatConfig(); + openAIChatConfig.setProvider(getModelProvider().getProviderName()); + openAIChatConfig.setEndpoint(checkAndGetEndpoint()); + openAIChatConfig.setApiKey(checkAndGetApiKey()); + openAIChatConfig.setModel(checkAndGetModelName()); + openAIChatConfig.setRequestPath(checkAndGetRequestPath()); + if (getSupportToolMessage() != null) { + openAIChatConfig.setSupportToolMessage(getSupportToolMessage()); + } + return new OpenAIChatModel(openAIChatConfig); + } + } + + public RerankModel toRerankModel() { + switch (modelProvider.getProviderType().toLowerCase()) { + case "gitee": + GiteeRerankModelConfig giteeRerankModelConfig = new GiteeRerankModelConfig(); + giteeRerankModelConfig.setProvider(getModelProvider().getProviderName()); + giteeRerankModelConfig.setApiKey(checkAndGetApiKey()); + giteeRerankModelConfig.setEndpoint(checkAndGetEndpoint()); + giteeRerankModelConfig.setModel(checkAndGetModelName()); + giteeRerankModelConfig.setRequestPath(checkAndGetRequestPath()); + return new GiteeRerankModel(giteeRerankModelConfig); + default: + DefaultRerankModelConfig defaultRerankModelConfig = new DefaultRerankModelConfig(); + defaultRerankModelConfig.setProvider(getModelProvider().getProviderName()); + defaultRerankModelConfig.setApiKey(checkAndGetApiKey()); + defaultRerankModelConfig.setEndpoint(checkAndGetEndpoint()); + defaultRerankModelConfig.setRequestPath(checkAndGetRequestPath()); + defaultRerankModelConfig.setModel(checkAndGetModelName()); + return new DefaultRerankModel(defaultRerankModelConfig); + } + } + + public EmbeddingModel toEmbeddingModel() { + String providerType = modelProvider.getProviderType(); + if (StringUtil.noText(providerType)) { + return null; + } + try { + switch (providerType.toLowerCase()) { + case "ollama": + OllamaEmbeddingConfig ollamaEmbeddingConfig = new OllamaEmbeddingConfig(); + ollamaEmbeddingConfig.setProvider(getModelProvider().getProviderName()); + ollamaEmbeddingConfig.setEndpoint(checkAndGetEndpoint()); + ollamaEmbeddingConfig.setApiKey(getApiKey()); + ollamaEmbeddingConfig.setModel(checkAndGetModelName()); + ollamaEmbeddingConfig.setRequestPath(getRequestPath()); + return new OllamaEmbeddingModel(ollamaEmbeddingConfig); + default: + OpenAIEmbeddingConfig openAIEmbeddingConfig = new OpenAIEmbeddingConfig(); + openAIEmbeddingConfig.setProvider(getModelProvider().getProviderName()); + openAIEmbeddingConfig.setEndpoint(checkAndGetEndpoint()); + openAIEmbeddingConfig.setApiKey(checkAndGetApiKey()); + openAIEmbeddingConfig.setModel(checkAndGetModelName()); + openAIEmbeddingConfig.setRequestPath(checkAndGetRequestPath()); + return new OpenAIEmbeddingModel(openAIEmbeddingConfig); + } + } catch (Exception e) { + throw new BusinessException("向量模型配置失败:" + e.getMessage()); + } + + } + + /** + * 获取模型向量的维度 + * + * @return + */ + public static int getEmbeddingDimension(EmbeddingModel embeddingModel) { + if (embeddingModel == null) { + throw new BusinessException("embeddingModel不能为空"); + } + VectorData vectorData = embeddingModel.embed("测试向量维度"); + return vectorData.getVector().length; + } + + public String checkAndGetRequestPath() { + if (StrUtil.isEmpty(getRequestPath())) { + throw new BusinessException("请求地址不能为空"); + } + return getRequestPath(); + } + + public String checkAndGetApiKey() { + if (StrUtil.isEmpty(getApiKey())) { + throw new BusinessException("API 密钥不能为空"); + } + return getApiKey(); + } + + public String checkAndGetEndpoint() { + if (StrUtil.isEmpty(getEndpoint())) { + throw new BusinessException("API 地址不能为空"); + } + return getEndpoint(); + } + + public String checkAndGetModelName() { + if (StrUtil.isEmpty(getModelName())) { + throw new BusinessException("模型名称不能为空"); + } + return getModelName(); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ModelProvider.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ModelProvider.java new file mode 100644 index 0000000..a35ebbe --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ModelProvider.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.ModelProviderBase; + + +/** + * 实体类。 + * + * @author 12076 + * @since 2025-12-16 + */ +@Table("tb_model_provider") +public class ModelProvider extends ModelProviderBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Plugin.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Plugin.java new file mode 100644 index 0000000..5a3a16d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Plugin.java @@ -0,0 +1,33 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.RelationOneToMany; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.PluginBase; + +import java.util.List; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-04-25 + */ +@Table("tb_plugin") +public class Plugin extends PluginBase { + + @RelationOneToMany(selfField = "id", targetField = "pluginId", targetTable = "tb_plugin_item") + private List tools; + + public String getTitle() { + return this.getName(); + } + + public List getTools() { + return tools; + } + + public void setTools(List tools) { + this.tools = tools; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategory.java new file mode 100644 index 0000000..c4b69d7 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategory.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.PluginCategoryBase; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-05-21 + */ +@Table("tb_plugin_category") +public class PluginCategory extends PluginCategoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategoryMapping.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategoryMapping.java new file mode 100644 index 0000000..58279bd --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginCategoryMapping.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.PluginCategoryMappingBase; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-05-21 + */ +@Table("tb_plugin_category_mapping") +public class PluginCategoryMapping extends PluginCategoryMappingBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginItem.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginItem.java new file mode 100644 index 0000000..8873e6a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/PluginItem.java @@ -0,0 +1,34 @@ +package tech.easyflow.ai.entity; + +import com.easyagents.core.model.chat.tool.Tool; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.easyagents.tool.PluginTool; +import tech.easyflow.ai.entity.base.PluginItemBase; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-04-27 + */ +@Table("tb_plugin_item") +public class PluginItem extends PluginItemBase { + + @Column(ignore = true) + private boolean joinBot; + + public boolean isJoinBot() { + return joinBot; + } + + public void setJoinBot(boolean joinBot) { + this.joinBot = joinBot; + } + + public Tool toFunction() { + return new PluginTool(this); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Resource.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Resource.java new file mode 100644 index 0000000..ed7a276 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Resource.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.ResourceBase; + + +/** + * 素材库 实体类。 + * + * @author ArkLight + * @since 2025-06-27 + */ +@Table(value = "tb_resource", comment = "素材库") +public class Resource extends ResourceBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ResourceCategory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ResourceCategory.java new file mode 100644 index 0000000..a8c1b1d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/ResourceCategory.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.ResourceCategoryBase; + + +/** + * 素材分类 实体类。 + * + * @author ArkLight + * @since 2025-12-24 + */ +@Table(value = "tb_resource_category", comment = "素材分类") +public class ResourceCategory extends ResourceCategoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Workflow.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Workflow.java new file mode 100644 index 0000000..6b471d0 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/Workflow.java @@ -0,0 +1,21 @@ +package tech.easyflow.ai.entity; + +import com.easyagents.core.model.chat.tool.Tool; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.easyagents.tool.WorkflowTool; +import tech.easyflow.ai.entity.base.WorkflowBase; + +/** + * 实体类。 + * + * @author michael + * @since 2024-08-23 + */ + +@Table("tb_workflow") +public class Workflow extends WorkflowBase { + + public Tool toFunction(boolean needEnglishName) { + return new WorkflowTool(this, needEnglishName); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowCategory.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowCategory.java new file mode 100644 index 0000000..1bfdbdc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowCategory.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.WorkflowCategoryBase; + + +/** + * 实体类。 + * + * @author ArkLight + * @since 2025-12-11 + */ +@Table("tb_workflow_category") +public class WorkflowCategory extends WorkflowCategoryBase { +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecResult.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecResult.java new file mode 100644 index 0000000..89c9e4f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecResult.java @@ -0,0 +1,22 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.WorkflowExecResultBase; + + +/** + * 工作流执行记录 实体类。 + * + * @author ArkLight + * @since 2025-05-28 + */ +@Table(value = "tb_workflow_exec_result", comment = "工作流执行记录") +public class WorkflowExecResult extends WorkflowExecResultBase { + + public Long getExecTime() { + if (getEndTime() == null) { + return null; + } + return getEndTime().getTime() - getStartTime().getTime(); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecStep.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecStep.java new file mode 100644 index 0000000..f4f93a2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/WorkflowExecStep.java @@ -0,0 +1,37 @@ +package tech.easyflow.ai.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import tech.easyflow.ai.entity.base.WorkflowExecStepBase; + + +/** + * 执行记录步骤 实体类。 + * + * @author ArkLight + * @since 2025-05-28 + */ +@Table(value = "tb_workflow_exec_step", comment = "执行记录步骤") +public class WorkflowExecStep extends WorkflowExecStepBase { + + /** + * 节点类型,agentsflex里没有这个属性 + */ + @Column(ignore = true) + private String nodeType; + + public Long getExecTime() { + if (getEndTime() == null) { + return null; + } + return getEndTime().getTime() - getStartTime().getTime(); + } + + public String getNodeType() { + return nodeType; + } + + public void setNodeType(String nodeType) { + this.nodeType = nodeType; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotBase.java new file mode 100644 index 0000000..d717e47 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotBase.java @@ -0,0 +1,242 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class BotBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键ID") + private BigInteger id; + + /** + * 别名 + */ + @Column(comment = "别名") + private String alias; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 分类ID + */ + @Column(comment = "分类ID") + private BigInteger categoryId; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 图标 + */ + @Column(comment = "图标") + private String icon; + + /** + * 模型 ID + */ + @Column(comment = "模型 ID") + private BigInteger modelId; + + /** + * 模型配置 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "模型配置") + private Map modelOptions; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 选项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "选项") + private Map options; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者ID + */ + @Column(comment = "创建者ID") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者ID + */ + @Column(comment = "修改者ID") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + 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 BigInteger getCategoryId() { + return categoryId; + } + + public void setCategoryId(BigInteger categoryId) { + this.categoryId = categoryId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public BigInteger getModelId() { + return modelId; + } + + public void setModelId(BigInteger modelId) { + this.modelId = modelId; + } + + public Map getModelOptions() { + return modelOptions; + } + + public void setModelOptions(Map modelOptions) { + this.modelOptions = modelOptions; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotCategoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotCategoryBase.java new file mode 100644 index 0000000..25c4b02 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotCategoryBase.java @@ -0,0 +1,128 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class BotCategoryBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 分类名称 + */ + @Column(comment = "分类名称") + private String categoryName; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotConversationBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotConversationBase.java new file mode 100644 index 0000000..a940dcd --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotConversationBase.java @@ -0,0 +1,116 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class BotConversationBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 会话id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "会话id") + private BigInteger id; + + /** + * 会话标题 + */ + @Column(comment = "会话标题") + private String title; + + /** + * botid + */ + @Column(comment = "botid") + private BigInteger botId; + + /** + * 账户 id + */ + @Column(comment = "账户 id") + private BigInteger accountId; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + private BigInteger createdBy; + + private Date modified; + + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotDocumentCollectionBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotDocumentCollectionBase.java new file mode 100644 index 0000000..933d5d7 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotDocumentCollectionBase.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Map; + + +public class BotDocumentCollectionBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Auto, value = "snowFlakeId") + private BigInteger id; + + private BigInteger botId; + + private BigInteger documentCollectionId; + + @Column(typeHandler = FastjsonTypeHandler.class) + private Map options; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getDocumentCollectionId() { + return documentCollectionId; + } + + public void setDocumentCollectionId(BigInteger documentCollectionId) { + this.documentCollectionId = documentCollectionId; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMcpBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMcpBase.java new file mode 100644 index 0000000..ac311d1 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMcpBase.java @@ -0,0 +1,73 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class BotMcpBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * botId + */ + @Column(comment = "botId") + private BigInteger botId; + + /** + * mcpId + */ + @Column(comment = "mcpId") + private BigInteger mcpId; + + /** + * mcp工具名称 + */ + @Column(comment = "mcp工具名称") + private String mcpToolName; + + /** + * mcp工具描述 + */ + @Column(comment = "mcp工具描述") + private String mcpToolDescription; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getMcpId() { + return mcpId; + } + + public void setMcpId(BigInteger mcpId) {this.mcpId = mcpId;} + + public String getMcpToolName() {return mcpToolName;} + + public void setMcpToolName(String mcpToolName) {this.mcpToolName = mcpToolName;} + + public String getMcpToolDescription() {return mcpToolDescription;} + + public void setMcpToolDescription(String mcpToolDescription) {this.mcpToolDescription = mcpToolDescription;} +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMessageBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMessageBase.java new file mode 100644 index 0000000..2b044ac --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotMessageBase.java @@ -0,0 +1,158 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class BotMessageBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "ID") + private BigInteger id; + + /** + * botId + */ + @Column(comment = "botId") + private BigInteger botId; + + /** + * 关联的账户ID + */ + @Column(comment = "关联的账户ID") + private BigInteger accountId; + + /** + * 会话ID + */ + @Column(comment = "会话ID") + private BigInteger conversationId; + + /** + * 角色[user|assistant] + */ + @Column(comment = "角色[user|assistant]") + private String role; + + /** + * 内容 + */ + @Column(comment = "内容") + private String content; + + /** + * 图片 + */ + @Column(comment = "图片") + private String image; + + /** + * 选项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "选项") + private Map options; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 更新时间 + */ + @Column(comment = "更新时间") + private Date modified; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public BigInteger getConversationId() { + return conversationId; + } + + public void setConversationId(BigInteger conversationId) { + this.conversationId = conversationId; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotModelBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotModelBase.java new file mode 100644 index 0000000..fca6b73 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotModelBase.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Map; + + +public class BotModelBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + private BigInteger botId; + + private BigInteger modelId; + + @Column(typeHandler = FastjsonTypeHandler.class) + private Map options; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getModelId() { + return modelId; + } + + public void setModelId(BigInteger modelId) { + this.modelId = modelId; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotPluginBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotPluginBase.java new file mode 100644 index 0000000..f94ad2b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotPluginBase.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Map; + + +public class BotPluginBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + private BigInteger botId; + + private BigInteger pluginItemId; + + @Column(typeHandler = FastjsonTypeHandler.class) + private Map options; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getPluginItemId() { + return pluginItemId; + } + + public void setPluginItemId(BigInteger pluginItemId) { + this.pluginItemId = pluginItemId; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotRecentlyUsedBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotRecentlyUsedBase.java new file mode 100644 index 0000000..1376cde --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotRecentlyUsedBase.java @@ -0,0 +1,85 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class BotRecentlyUsedBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * botId + */ + @Column(comment = "botId") + private BigInteger botId; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotWorkflowBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotWorkflowBase.java new file mode 100644 index 0000000..8276545 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/BotWorkflowBase.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Map; + + +public class BotWorkflowBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + private BigInteger botId; + + private BigInteger workflowId; + + @Column(typeHandler = FastjsonTypeHandler.class) + private Map options; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getBotId() { + return botId; + } + + public void setBotId(BigInteger botId) { + this.botId = botId; + } + + public BigInteger getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(BigInteger workflowId) { + this.workflowId = workflowId; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentBase.java new file mode 100644 index 0000000..e96b6ff --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentBase.java @@ -0,0 +1,211 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class DocumentBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + /** + * 知识库ID + */ + @Column(comment = "知识库ID") + private BigInteger collectionId; + + /** + * 文档类型 pdf/word/aieditor 等 + */ + @Column(comment = "文档类型 pdf/word/aieditor 等") + private String documentType; + + /** + * 文档路径 + */ + @Column(comment = "文档路径") + private String documentPath; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 内容 + */ + @Column(comment = "内容") + private String content; + + /** + * 内容类型 + */ + @Column(comment = "内容类型") + private String contentType; + + /** + * URL 别名 + */ + @Column(comment = "URL 别名") + private String slug; + + /** + * 排序序号 + */ + @Column(comment = "排序序号") + private Integer orderNo; + + /** + * 其他配置项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "其他配置项") + private Map options; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建人ID + */ + @Column(comment = "创建人ID") + private BigInteger createdBy; + + /** + * 最后的修改时间 + */ + @Column(comment = "最后的修改时间") + private Date modified; + + /** + * 最后的修改人的ID + */ + @Column(comment = "最后的修改人的ID") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getCollectionId() { + return collectionId; + } + + public void setCollectionId(BigInteger collectionId) { + this.collectionId = collectionId; + } + + public String getDocumentType() { + return documentType; + } + + public void setDocumentType(String documentType) { + this.documentType = documentType; + } + + public String getDocumentPath() { + return documentPath; + } + + public void setDocumentPath(String documentPath) { + this.documentPath = documentPath; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public Integer getOrderNo() { + return orderNo; + } + + public void setOrderNo(Integer orderNo) { + this.orderNo = orderNo; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentChunkBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentChunkBase.java new file mode 100644 index 0000000..d410157 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentChunkBase.java @@ -0,0 +1,81 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class DocumentChunkBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + /** + * 文档ID + */ + @Column(comment = "文档ID") + private BigInteger documentId; + + /** + * 知识库ID + */ + @Column(comment = "知识库ID") + private BigInteger documentCollectionId; + + /** + * 分块内容 + */ + @Column(comment = "分块内容") + private String content; + + /** + * 分割顺序 + */ + @Column(comment = "分割顺序") + private Integer sorting; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getDocumentId() { + return documentId; + } + + public void setDocumentId(BigInteger documentId) { + this.documentId = documentId; + } + + public BigInteger getDocumentCollectionId() { + return documentCollectionId; + } + + public void setDocumentCollectionId(BigInteger documentCollectionId) { + this.documentCollectionId = documentCollectionId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getSorting() { + return sorting; + } + + public void setSorting(Integer sorting) { + this.sorting = sorting; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionBase.java new file mode 100644 index 0000000..55cc5ca --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionBase.java @@ -0,0 +1,340 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class DocumentCollectionBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "Id") + private BigInteger id; + + /** + * 别名 + */ + @Column(comment = "别名") + private String alias; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * ICON + */ + @Column(comment = "ICON") + private String icon; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * URL 别名 + */ + @Column(comment = "URL 别名") + private String slug; + + /** + * 是否启用向量存储 + */ + @Column(comment = "是否启用向量存储") + private Boolean vectorStoreEnable; + + /** + * 向量数据库类型 + */ + @Column(comment = "向量数据库类型") + private String vectorStoreType; + + /** + * 向量数据库集合 + */ + @Column(comment = "向量数据库集合") + private String vectorStoreCollection; + + /** + * 向量数据库配置 + */ + @Column(comment = "向量数据库配置") + private String vectorStoreConfig; + + /** + * Embedding 模型ID + */ + @Column(comment = "Embedding 模型ID") + private BigInteger vectorEmbedModelId; + + /** + * 向量模型维度 + */ + @Column(comment = "向量模型维度") + private Integer dimensionOfVectorModel; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建用户ID + */ + @Column(comment = "创建用户ID") + private BigInteger createdBy; + + /** + * 最后一次修改时间 + */ + @Column(comment = "最后一次修改时间") + private Date modified; + + /** + * 最后一次修改用户ID + */ + @Column(comment = "最后一次修改用户ID") + private BigInteger modifiedBy; + + /** + * 其他配置 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "其他配置") + private Map options; + + /** + * 重排模型id + */ + @Column(comment = "重排模型id") + private BigInteger rerankModelId; + + /** + * 是否启用搜索引擎 + */ + @Column(comment = "是否启用搜索引擎") + private Boolean searchEngineEnable; + + /** + * 英文名称 + */ + @Column(comment = "英文名称") + private String englishName; + + /** + * 分类ID + */ + @Column(comment = "分类ID") + private BigInteger categoryId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + 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 getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public Boolean getVectorStoreEnable() { + return vectorStoreEnable; + } + + public void setVectorStoreEnable(Boolean vectorStoreEnable) { + this.vectorStoreEnable = vectorStoreEnable; + } + + public String getVectorStoreType() { + return vectorStoreType; + } + + public void setVectorStoreType(String vectorStoreType) { + this.vectorStoreType = vectorStoreType; + } + + public String getVectorStoreCollection() { + return vectorStoreCollection; + } + + public void setVectorStoreCollection(String vectorStoreCollection) { + this.vectorStoreCollection = vectorStoreCollection; + } + + public String getVectorStoreConfig() { + return vectorStoreConfig; + } + + public void setVectorStoreConfig(String vectorStoreConfig) { + this.vectorStoreConfig = vectorStoreConfig; + } + + public BigInteger getVectorEmbedModelId() { + return vectorEmbedModelId; + } + + public void setVectorEmbedModelId(BigInteger vectorEmbedModelId) { + this.vectorEmbedModelId = vectorEmbedModelId; + } + + public Integer getDimensionOfVectorModel() { + return dimensionOfVectorModel; + } + + public void setDimensionOfVectorModel(Integer dimensionOfVectorModel) { + this.dimensionOfVectorModel = dimensionOfVectorModel; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public BigInteger getRerankModelId() { + return rerankModelId; + } + + public void setRerankModelId(BigInteger rerankModelId) { + this.rerankModelId = rerankModelId; + } + + public Boolean getSearchEngineEnable() { + return searchEngineEnable; + } + + public void setSearchEngineEnable(Boolean searchEngineEnable) { + this.searchEngineEnable = searchEngineEnable; + } + + public String getEnglishName() { + return englishName; + } + + public void setEnglishName(String englishName) { + this.englishName = englishName; + } + + public BigInteger getCategoryId() { + return categoryId; + } + + public void setCategoryId(BigInteger categoryId) { + this.categoryId = categoryId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionCategoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionCategoryBase.java new file mode 100644 index 0000000..be29036 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentCollectionCategoryBase.java @@ -0,0 +1,128 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class DocumentCollectionCategoryBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 分类名称 + */ + @Column(comment = "分类名称") + private String categoryName; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentHistoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentHistoryBase.java new file mode 100644 index 0000000..77f35bf --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/DocumentHistoryBase.java @@ -0,0 +1,152 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class DocumentHistoryBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Auto, value = "snowFlakeId") + private BigInteger id; + + /** + * 修改的文档ID + */ + @Column(comment = "修改的文档ID") + private Long documentId; + + /** + * 旧标题 + */ + @Column(comment = "旧标题") + private String oldTitle; + + /** + * 新标题 + */ + @Column(comment = "新标题") + private String newTitle; + + /** + * 旧内容 + */ + @Column(comment = "旧内容") + private String oldContent; + + /** + * 新内容 + */ + @Column(comment = "新内容") + private String newContent; + + /** + * 旧的文档类型 + */ + @Column(comment = "旧的文档类型") + private String oldDocumentType; + + /** + * 新的额文档类型 + */ + @Column(comment = "新的额文档类型") + private String newDocumentType; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建人ID + */ + @Column(comment = "创建人ID") + private Long createdBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public Long getDocumentId() { + return documentId; + } + + public void setDocumentId(Long documentId) { + this.documentId = documentId; + } + + public String getOldTitle() { + return oldTitle; + } + + public void setOldTitle(String oldTitle) { + this.oldTitle = oldTitle; + } + + public String getNewTitle() { + return newTitle; + } + + public void setNewTitle(String newTitle) { + this.newTitle = newTitle; + } + + public String getOldContent() { + return oldContent; + } + + public void setOldContent(String oldContent) { + this.oldContent = oldContent; + } + + public String getNewContent() { + return newContent; + } + + public void setNewContent(String newContent) { + this.newContent = newContent; + } + + public String getOldDocumentType() { + return oldDocumentType; + } + + public void setOldDocumentType(String oldDocumentType) { + this.oldDocumentType = oldDocumentType; + } + + public String getNewDocumentType() { + return newDocumentType; + } + + public void setNewDocumentType(String newDocumentType) { + this.newDocumentType = newDocumentType; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Long getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(Long createdBy) { + this.createdBy = createdBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/McpBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/McpBase.java new file mode 100644 index 0000000..5259e1f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/McpBase.java @@ -0,0 +1,170 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class McpBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 完整MCP配置JSON + */ + @Column(comment = "完整MCP配置JSON") + private String configJson; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者ID + */ + @Column(comment = "创建者ID") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者ID + */ + @Column(comment = "修改者ID") + private BigInteger modifiedBy; + + /** + * 是否启用 + */ + @Column(comment = "是否启用") + private Boolean status; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getConfigJson() { + return configJson; + } + + public void setConfigJson(String configJson) { + this.configJson = configJson; + } + + 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 Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Boolean getStatus() { + return status; + } + + public void setStatus(Boolean status) { + this.status = status; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelBase.java new file mode 100644 index 0000000..7547c62 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelBase.java @@ -0,0 +1,352 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Map; + + +public class ModelBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "ID") + private BigInteger id; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 供应商id + */ + @Column(comment = "供应商id") + private BigInteger providerId; + + /** + * 标题或名称 + */ + @Column(comment = "标题或名称") + private String title; + + /** + * ICON + */ + @Column(comment = "ICON") + private String icon; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 大模型请求地址 + */ + @Column(comment = "大模型请求地址") + private String endpoint; + + /** + * 请求路径 + */ + @Column(comment = "请求路径") + private String requestPath; + + /** + * 大模型名称 + */ + @Column(comment = "大模型名称") + private String modelName; + + /** + * 大模型 API KEY + */ + @Column(comment = "大模型 API KEY") + private String apiKey; + + /** + * 大模型其他属性配置 + */ + @Column(comment = "大模型其他属性配置") + private String extraConfig; + + /** + * 其他配置内容 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "其他配置内容") + private Map options; + + /** + * 分组名称 + */ + @Column(comment = "分组名称") + private String groupName; + + /** + * 模型类型: chatModel/embeddingModel/rerankModel/orc.. + */ + @Column(comment = "模型类型: chatModel/embeddingModel/rerankModel/orc..") + private String modelType; + + /** + * 是否使用 + */ + @Column(comment = "是否使用") + private Boolean withUsed; + + /** + * 是否支持推理 + */ + @Column(comment = "是否支持推理") + private Boolean supportThinking; + + /** + * 是否支持工具 + */ + @Column(comment = "是否支持工具") + private Boolean supportTool; + + /** + * 是否支持图片 + */ + @Column(comment = "是否支持图片") + private Boolean supportImage; + + /** + * 仅支持 base64 的图片类型 + */ + @Column(comment = "仅支持 base64 的图片类型") + private Boolean supportImageB64Only; + + /** + * 是否支持视频 + */ + @Column(comment = "是否支持视频") + private Boolean supportVideo; + + /** + * 是否支持音频 + */ + @Column(comment = "是否支持音频") + private Boolean supportAudio; + + /** + * 是否免费 + */ + @Column(comment = "是否免费") + private Boolean supportFree; + + /** + * 是否支持tool消息 + */ + @Column(comment = "是否支持tool消息") + private Boolean supportToolMessage; + + 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 BigInteger getProviderId() { + return providerId; + } + + public void setProviderId(BigInteger providerId) { + this.providerId = providerId; + } + + 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 String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getRequestPath() { + return requestPath; + } + + public void setRequestPath(String requestPath) { + this.requestPath = requestPath; + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getExtraConfig() { + return extraConfig; + } + + public void setExtraConfig(String extraConfig) { + this.extraConfig = extraConfig; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getModelType() { + return modelType; + } + + public void setModelType(String modelType) { + this.modelType = modelType; + } + + public Boolean getWithUsed() { + return withUsed; + } + + public void setWithUsed(Boolean withUsed) { + this.withUsed = withUsed; + } + + public Boolean getSupportThinking() { + return supportThinking; + } + + public void setSupportThinking(Boolean supportThinking) { + this.supportThinking = supportThinking; + } + + public Boolean getSupportTool() { + return supportTool; + } + + public void setSupportTool(Boolean supportTool) { + this.supportTool = supportTool; + } + + public Boolean getSupportImage() { + return supportImage; + } + + public void setSupportImage(Boolean supportImage) { + this.supportImage = supportImage; + } + + public Boolean getSupportImageB64Only() { + return supportImageB64Only; + } + + public void setSupportImageB64Only(Boolean supportImageB64Only) { + this.supportImageB64Only = supportImageB64Only; + } + + public Boolean getSupportVideo() { + return supportVideo; + } + + public void setSupportVideo(Boolean supportVideo) { + this.supportVideo = supportVideo; + } + + public Boolean getSupportAudio() { + return supportAudio; + } + + public void setSupportAudio(Boolean supportAudio) { + this.supportAudio = supportAudio; + } + + public Boolean getSupportFree() { + return supportFree; + } + + public void setSupportFree(Boolean supportFree) { + this.supportFree = supportFree; + } + + public Boolean getSupportToolMessage() { + return supportToolMessage; + } + + public void setSupportToolMessage(Boolean supportToolMessage) { + this.supportToolMessage = supportToolMessage; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelProviderBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelProviderBase.java new file mode 100644 index 0000000..f95ca0b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ModelProviderBase.java @@ -0,0 +1,198 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class ModelProviderBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * 供应商名称 + */ + @Column(comment = "供应商名称") + private String providerName; + + /** + * 不同的 client 实现,默认为 openai + */ + @Column(comment = "不同的 client 实现,默认为 openai") + private String providerType; + + /** + * 图标 + */ + @Column(comment = "图标") + private String icon; + + /** + * apiKey + */ + @Column(comment = "apiKey") + private String apiKey; + + /** + * endPoint + */ + @Column(comment = "endPoint") + private String endpoint; + + /** + * 对话地址 + */ + @Column(comment = "对话地址") + private String chatPath; + + /** + * 向量地址 + */ + @Column(comment = "向量地址") + private String embedPath; + + /** + * 重排路径 + */ + @Column(comment = "重排路径") + private String rerankPath; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getChatPath() { + return chatPath; + } + + public void setChatPath(String chatPath) { + this.chatPath = chatPath; + } + + public String getEmbedPath() { + return embedPath; + } + + public void setEmbedPath(String embedPath) { + this.embedPath = embedPath; + } + + public String getRerankPath() { + return rerankPath; + } + + public void setRerankPath(String rerankPath) { + this.rerankPath = rerankPath; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginBase.java new file mode 100644 index 0000000..47e2c05 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginBase.java @@ -0,0 +1,239 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class PluginBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 插件id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "插件id") + private BigInteger id; + + /** + * 别名 + */ + @Column(comment = "别名") + private String alias; + + /** + * 名称 + */ + @Column(comment = "名称") + private String name; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 类型 + */ + @Column(comment = "类型") + private Integer type; + + /** + * 基础URL + */ + @Column(comment = "基础URL") + private String baseUrl; + + /** + * 认证方式 【apiKey/none】 + */ + @Column(comment = "认证方式 【apiKey/none】") + private String authType; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 图标地址 + */ + @Column(comment = "图标地址") + private String icon; + + /** + * 认证参数位置 【headers, query】 + */ + @Column(comment = "认证参数位置 【headers, query】") + private String position; + + /** + * 请求头 + */ + @Column(comment = "请求头") + private String headers; + + /** + * token键 + */ + @Column(comment = "token键") + private String tokenKey; + + /** + * token值 + */ + @Column(comment = "token值") + private String tokenValue; + + /** + * 部门id + */ + @Column(comment = "部门id") + private Long deptId; + + /** + * 租户id + */ + @Column(tenantId = true, comment = "租户id") + private Long tenantId; + + /** + * 创建人 + */ + @Column(comment = "创建人") + private Long createdBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + 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 Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + + public String getHeaders() { + return headers; + } + + public void setHeaders(String headers) { + this.headers = headers; + } + + public String getTokenKey() { + return tokenKey; + } + + public void setTokenKey(String tokenKey) { + this.tokenKey = tokenKey; + } + + public String getTokenValue() { + return tokenValue; + } + + public void setTokenValue(String tokenValue) { + this.tokenValue = tokenValue; + } + + public Long getDeptId() { + return deptId; + } + + public void setDeptId(Long deptId) { + this.deptId = deptId; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(Long createdBy) { + this.createdBy = createdBy; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryBase.java new file mode 100644 index 0000000..61abd5e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryBase.java @@ -0,0 +1,45 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class PluginCategoryBase implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Auto, value = "snowFlakeId") + private BigInteger id; + + private String name; + + private Date createdAt; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryMappingBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryMappingBase.java new file mode 100644 index 0000000..eca06f9 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginCategoryMappingBase.java @@ -0,0 +1,31 @@ +package tech.easyflow.ai.entity.base; + +import java.io.Serializable; +import java.math.BigInteger; + + +public class PluginCategoryMappingBase implements Serializable { + + private static final long serialVersionUID = 1L; + + private BigInteger categoryId; + + private BigInteger pluginId; + + public BigInteger getCategoryId() { + return categoryId; + } + + public void setCategoryId(BigInteger categoryId) { + this.categoryId = categoryId; + } + + public BigInteger getPluginId() { + return pluginId; + } + + public void setPluginId(BigInteger pluginId) { + this.pluginId = pluginId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginItemBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginItemBase.java new file mode 100644 index 0000000..1d1c189 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/PluginItemBase.java @@ -0,0 +1,197 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class PluginItemBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 插件工具id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "插件工具id") + private BigInteger id; + + /** + * 插件id + */ + @Column(comment = "插件id") + private BigInteger pluginId; + + /** + * 名称 + */ + @Column(comment = "名称") + private String name; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 基础路径 + */ + @Column(comment = "基础路径") + private String basePath; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 是否启用 + */ + @Column(comment = "是否启用") + private Integer status; + + /** + * 输入参数 + */ + @Column(comment = "输入参数") + private String inputData; + + /** + * 输出参数 + */ + @Column(comment = "输出参数") + private String outputData; + + /** + * 请求方式【Post, Get, Put, Delete】 + */ + @Column(comment = "请求方式【Post, Get, Put, Delete】") + private String requestMethod; + + /** + * 服务状态[0 下线 1 上线] + */ + @Column(comment = "服务状态[0 下线 1 上线]") + private Integer serviceStatus; + + /** + * 调试状态【0失败 1成功】 + */ + @Column(comment = "调试状态【0失败 1成功】") + private Integer debugStatus; + + /** + * 英文名称 + */ + @Column(comment = "英文名称") + private String englishName; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getPluginId() { + return pluginId; + } + + public void setPluginId(BigInteger pluginId) { + this.pluginId = pluginId; + } + + 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 getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = basePath; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getInputData() { + return inputData; + } + + public void setInputData(String inputData) { + this.inputData = inputData; + } + + public String getOutputData() { + return outputData; + } + + public void setOutputData(String outputData) { + this.outputData = outputData; + } + + public String getRequestMethod() { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) { + this.requestMethod = requestMethod; + } + + public Integer getServiceStatus() { + return serviceStatus; + } + + public void setServiceStatus(Integer serviceStatus) { + this.serviceStatus = serviceStatus; + } + + public Integer getDebugStatus() { + return debugStatus; + } + + public void setDebugStatus(Integer debugStatus) { + this.debugStatus = debugStatus; + } + + public String getEnglishName() { + return englishName; + } + + public void setEnglishName(String englishName) { + this.englishName = englishName; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceBase.java new file mode 100644 index 0000000..9585425 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceBase.java @@ -0,0 +1,242 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class ResourceBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 素材类型 + */ + @Column(comment = "素材类型") + private Integer resourceType; + + /** + * 素材名称 + */ + @Column(comment = "素材名称") + private String resourceName; + + /** + * 后缀 + */ + @Column(comment = "后缀") + private String suffix; + + /** + * 素材地址 + */ + @Column(comment = "素材地址") + private String resourceUrl; + + /** + * 素材来源 + */ + @Column(comment = "素材来源") + private Integer origin; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 扩展项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项") + private Map options; + + /** + * 文件大小 + */ + @Column(comment = "文件大小") + private BigInteger fileSize; + + /** + * 分类ID + */ + @Column(comment = "分类ID") + private BigInteger categoryId; + + 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 Integer getResourceType() { + return resourceType; + } + + public void setResourceType(Integer resourceType) { + this.resourceType = resourceType; + } + + public String getResourceName() { + return resourceName; + } + + public void setResourceName(String resourceName) { + this.resourceName = resourceName; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getResourceUrl() { + return resourceUrl; + } + + public void setResourceUrl(String resourceUrl) { + this.resourceUrl = resourceUrl; + } + + public Integer getOrigin() { + return origin; + } + + public void setOrigin(Integer origin) { + this.origin = origin; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public BigInteger getFileSize() { + return fileSize; + } + + public void setFileSize(BigInteger fileSize) { + this.fileSize = fileSize; + } + + public BigInteger getCategoryId() { + return categoryId; + } + + public void setCategoryId(BigInteger categoryId) { + this.categoryId = categoryId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceCategoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceCategoryBase.java new file mode 100644 index 0000000..6363d2e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/ResourceCategoryBase.java @@ -0,0 +1,128 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class ResourceCategoryBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 分类名称 + */ + @Column(comment = "分类名称") + private String categoryName; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowBase.java new file mode 100644 index 0000000..b40b93f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowBase.java @@ -0,0 +1,226 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class WorkflowBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "ID 主键") + private BigInteger id; + + /** + * 别名 + */ + @Column(comment = "别名") + private String alias; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * ICON + */ + @Column(comment = "ICON") + private String icon; + + /** + * 工作流设计的 JSON 内容 + */ + @Column(comment = "工作流设计的 JSON 内容") + private String content; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建人 + */ + @Column(comment = "创建人") + private BigInteger createdBy; + + /** + * 最后修改时间 + */ + @Column(comment = "最后修改时间") + private Date modified; + + /** + * 最后修改的人 + */ + @Column(comment = "最后修改的人") + private BigInteger modifiedBy; + + /** + * 英文名称 + */ + @Column(comment = "英文名称") + private String englishName; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 分类ID + */ + @Column(comment = "分类ID") + private BigInteger categoryId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + 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 getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getEnglishName() { + return englishName; + } + + public void setEnglishName(String englishName) { + this.englishName = englishName; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public BigInteger getCategoryId() { + return categoryId; + } + + public void setCategoryId(BigInteger categoryId) { + this.categoryId = categoryId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowCategoryBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowCategoryBase.java new file mode 100644 index 0000000..8f4694f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowCategoryBase.java @@ -0,0 +1,128 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class WorkflowCategoryBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 分类名称 + */ + @Column(comment = "分类名称") + private String categoryName; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecResultBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecResultBase.java new file mode 100644 index 0000000..69be196 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecResultBase.java @@ -0,0 +1,225 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class WorkflowExecResultBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 执行标识 + */ + @Column(comment = "执行标识") + private String execKey; + + /** + * 工作流ID + */ + @Column(comment = "工作流ID") + private BigInteger workflowId; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 输入 + */ + @Column(comment = "输入") + private String input; + + /** + * 输出 + */ + @Column(comment = "输出") + private String output; + + /** + * 工作流执行时的配置 + */ + @Column(comment = "工作流执行时的配置") + private String workflowJson; + + /** + * 开始时间 + */ + @Column(comment = "开始时间") + private Date startTime; + + /** + * 结束时间 + */ + @Column(comment = "结束时间") + private Date endTime; + + /** + * 消耗总token + */ + @Column(comment = "消耗总token") + private BigInteger tokens; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 执行人标识[有可能是用户|外部|定时任务等情况] + */ + @Column(comment = "执行人标识[有可能是用户|外部|定时任务等情况]") + private String createdKey; + + /** + * 执行人 + */ + @Column(comment = "执行人") + private String createdBy; + + /** + * 错误信息 + */ + @Column(comment = "错误信息") + private String errorInfo; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getExecKey() { + return execKey; + } + + public void setExecKey(String execKey) { + this.execKey = execKey; + } + + public BigInteger getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(BigInteger workflowId) { + this.workflowId = workflowId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public String getWorkflowJson() { + return workflowJson; + } + + public void setWorkflowJson(String workflowJson) { + this.workflowJson = workflowJson; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public BigInteger getTokens() { + return tokens; + } + + public void setTokens(BigInteger tokens) { + this.tokens = tokens; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getCreatedKey() { + return createdKey; + } + + public void setCreatedKey(String createdKey) { + this.createdKey = createdKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public String getErrorInfo() { + return errorInfo; + } + + public void setErrorInfo(String errorInfo) { + this.errorInfo = errorInfo; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecStepBase.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecStepBase.java new file mode 100644 index 0000000..6d14aa1 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/entity/base/WorkflowExecStepBase.java @@ -0,0 +1,197 @@ +package tech.easyflow.ai.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class WorkflowExecStepBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 执行记录ID + */ + @Column(comment = "执行记录ID") + private BigInteger recordId; + + /** + * 执行标识 + */ + @Column(comment = "执行标识") + private String execKey; + + /** + * 节点ID + */ + @Column(comment = "节点ID") + private String nodeId; + + /** + * 节点名称 + */ + @Column(comment = "节点名称") + private String nodeName; + + /** + * 输入 + */ + @Column(comment = "输入") + private String input; + + /** + * 输出 + */ + @Column(comment = "输出") + private String output; + + /** + * 节点信息 + */ + @Column(comment = "节点信息") + private String nodeData; + + /** + * 开始时间 + */ + @Column(comment = "开始时间") + private Date startTime; + + /** + * 结束时间 + */ + @Column(comment = "结束时间") + private Date endTime; + + /** + * 消耗总token + */ + @Column(comment = "消耗总token") + private BigInteger tokens; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 错误信息 + */ + @Column(comment = "错误信息") + private String errorInfo; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getRecordId() { + return recordId; + } + + public void setRecordId(BigInteger recordId) { + this.recordId = recordId; + } + + public String getExecKey() { + return execKey; + } + + public void setExecKey(String execKey) { + this.execKey = execKey; + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public String getNodeData() { + return nodeData; + } + + public void setNodeData(String nodeData) { + this.nodeData = nodeData; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public BigInteger getTokens() { + return tokens; + } + + public void setTokens(BigInteger tokens) { + this.tokens = tokens; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getErrorInfo() { + return errorInfo; + } + + public void setErrorInfo(String errorInfo) { + this.errorInfo = errorInfo; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotCategoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotCategoryMapper.java new file mode 100644 index 0000000..e442a8f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotCategoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.BotCategory; + +/** + * bot分类 映射层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +public interface BotCategoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotConversationMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotConversationMapper.java new file mode 100644 index 0000000..fed1056 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotConversationMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.BotConversation; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-04-15 + */ +public interface BotConversationMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotDocumentCollectionMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotDocumentCollectionMapper.java new file mode 100644 index 0000000..3963696 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotDocumentCollectionMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.BotDocumentCollection; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotDocumentCollectionMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMapper.java new file mode 100644 index 0000000..ec085d2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.Bot; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface BotMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMcpMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMcpMapper.java new file mode 100644 index 0000000..6e1074b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMcpMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.BotMcp; + +/** + * 映射层。 + * + * @author wangGangQiang + * @since 2026-01-05 + */ +public interface BotMcpMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMessageMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMessageMapper.java new file mode 100644 index 0000000..a22ce4b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotMessageMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.BotMessage; +import com.mybatisflex.core.BaseMapper; + +/** + * Bot 消息记录表 映射层。 + * + * @author michael + * @since 2024-11-04 + */ +public interface BotMessageMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotModelMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotModelMapper.java new file mode 100644 index 0000000..cac05ab --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotModelMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.BotModel; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotModelMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotPluginMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotPluginMapper.java new file mode 100644 index 0000000..f272d07 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotPluginMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.BotPlugin; + +/** + * 映射层。 + * + * @author michael + * @since 2025-04-07 + */ +public interface BotPluginMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotRecentlyUsedMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotRecentlyUsedMapper.java new file mode 100644 index 0000000..b17dd6e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotRecentlyUsedMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.BotRecentlyUsed; + +/** + * 最近使用 映射层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +public interface BotRecentlyUsedMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotWorkflowMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotWorkflowMapper.java new file mode 100644 index 0000000..0b338b0 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/BotWorkflowMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.BotWorkflow; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotWorkflowMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentChunkMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentChunkMapper.java new file mode 100644 index 0000000..f289dcf --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentChunkMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.DocumentChunk; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentChunkMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionCategoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionCategoryMapper.java new file mode 100644 index 0000000..8ee32f3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionCategoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.DocumentCollectionCategory; + +/** + * 映射层。 + * + * @author 12076 + * @since 2026-01-23 + */ +public interface DocumentCollectionCategoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionMapper.java new file mode 100644 index 0000000..80d3168 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentCollectionMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.DocumentCollection; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentCollectionMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentHistoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentHistoryMapper.java new file mode 100644 index 0000000..9f2c88e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentHistoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.DocumentHistory; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentHistoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentMapper.java new file mode 100644 index 0000000..8cceb84 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/DocumentMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.Document; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/McpMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/McpMapper.java new file mode 100644 index 0000000..5edf1d3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/McpMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.Mcp; + +/** + * 映射层。 + * + * @author wangGangQiang + * @since 2026-01-04 + */ +public interface McpMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelMapper.java new file mode 100644 index 0000000..074730a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.Model; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface ModelMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelProviderMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelProviderMapper.java new file mode 100644 index 0000000..39d3558 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ModelProviderMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.ModelProvider; + +/** + * 映射层。 + * + * @author 12076 + * @since 2025-12-16 + */ +public interface ModelProviderMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMapper.java new file mode 100644 index 0000000..0f8043d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.PluginCategory; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-05-21 + */ +public interface PluginCategoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMappingMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMappingMapper.java new file mode 100644 index 0000000..16eddd2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginCategoryMappingMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.PluginCategoryMapping; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-05-21 + */ +public interface PluginCategoryMappingMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginItemMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginItemMapper.java new file mode 100644 index 0000000..647363e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginItemMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.PluginItem; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-04-27 + */ +public interface PluginItemMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginMapper.java new file mode 100644 index 0000000..5d07e66 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/PluginMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.Plugin; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-04-25 + */ +public interface PluginMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceCategoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceCategoryMapper.java new file mode 100644 index 0000000..aa8a767 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceCategoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.ResourceCategory; + +/** + * 素材分类 映射层。 + * + * @author ArkLight + * @since 2025-12-24 + */ +public interface ResourceCategoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceMapper.java new file mode 100644 index 0000000..b592004 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/ResourceMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.Resource; + +/** + * 素材库 映射层。 + * + * @author ArkLight + * @since 2025-06-27 + */ +public interface ResourceMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowCategoryMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowCategoryMapper.java new file mode 100644 index 0000000..d2b85ee --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowCategoryMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.WorkflowCategory; + +/** + * 映射层。 + * + * @author ArkLight + * @since 2025-12-11 + */ +public interface WorkflowCategoryMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecResultMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecResultMapper.java new file mode 100644 index 0000000..1613a2b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecResultMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.WorkflowExecResult; + +/** + * 工作流执行记录 映射层。 + * + * @author ArkLight + * @since 2025-05-28 + */ +public interface WorkflowExecResultMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecStepMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecStepMapper.java new file mode 100644 index 0000000..9d810ff --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowExecStepMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.ai.entity.WorkflowExecStep; + +/** + * 执行记录步骤 映射层。 + * + * @author ArkLight + * @since 2025-05-28 + */ +public interface WorkflowExecStepMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowMapper.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowMapper.java new file mode 100644 index 0000000..027206b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/mapper/WorkflowMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.mapper; + +import tech.easyflow.ai.entity.Workflow; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface WorkflowMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/CustomFile.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/CustomFile.java new file mode 100644 index 0000000..54fb3db --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/CustomFile.java @@ -0,0 +1,73 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.io.FileUtil; +import org.apache.tika.Tika; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class CustomFile implements MultipartFile { + + private final String fileName; + private final byte[] bytes; + + public CustomFile(String fileName, byte[] bytes) { + this.fileName = fileName; + this.bytes = bytes; + } + + @Override + public String getName() { + return fileName; + } + + @Override + public String getOriginalFilename() { + return fileName; + } + + @Override + public String getContentType() { + Tika tika = new Tika(); + InputStream inputStream = new ByteArrayInputStream(bytes); + String contentType = ""; + try { + contentType = tika.detect(inputStream); + } catch (Exception e) { + System.out.println("获取contentType错误"); + } + return contentType; + } + + @Override + public boolean isEmpty() { + return bytes == null || bytes.length == 0; + } + + @Override + public long getSize() { + return bytes.length; + } + + @Override + public byte[] getBytes() throws IOException { + return bytes; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(bytes); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + if (dest.exists() && !dest.delete()) { + throw new IOException( + "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted"); + } + FileUtil.writeBytes(this.bytes, dest); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DefaultReadService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DefaultReadService.java new file mode 100644 index 0000000..726baa7 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DefaultReadService.java @@ -0,0 +1,20 @@ +package tech.easyflow.ai.node; + +import org.springframework.stereotype.Component; +import tech.easyflow.ai.utils.DocUtil; + +import java.io.InputStream; + +@Component("defaultReader") +public class DefaultReadService implements ReadDocService { + + @Override + public String read(String fileName, InputStream is) { + String suffix = DocUtil.getSuffix(fileName); + if ("pdf".equals(suffix)) { + return DocUtil.readPdfFile(is); + } else { + return DocUtil.readWordFile(suffix, is); + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNode.java new file mode 100644 index 0000000..7f157cc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNode.java @@ -0,0 +1,39 @@ +package tech.easyflow.ai.node; + +import com.easyagents.core.util.StringUtil; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.Parameter; +import com.easyagents.flow.core.node.BaseNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.ai.utils.DocUtil; +import tech.easyflow.common.util.SpringContextUtil; + +import java.io.ByteArrayInputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DocNode extends BaseNode { + + private static final Logger log = LoggerFactory.getLogger(DocNode.class); + + @Override + public Map execute(Chain chain) { + Map map = chain.getState().resolveParameters(this); + Map res = new HashMap<>(); + String url = map.get("fileUrl").toString(); + byte[] bytes = DocUtil.downloadFile(url); + ReaderManager manager = SpringContextUtil.getBean(ReaderManager.class); + String docContent = manager.getReader().read(DocUtil.getFileNameByUrl(url), new ByteArrayInputStream(bytes)); + + String key = "content"; + List outputDefs = getOutputDefs(); + if (outputDefs != null && !outputDefs.isEmpty()) { + String defName = outputDefs.get(0).getName(); + if (StringUtil.hasText(defName)) key = defName; + } + res.put(key, docContent); + return res; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeParser.java new file mode 100644 index 0000000..65417e9 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeParser.java @@ -0,0 +1,17 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +public class DocNodeParser extends BaseNodeParser { + + @Override + public BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + return new DocNode(); + } + + public String getNodeName() { + return "document-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNode.java new file mode 100644 index 0000000..395d17c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNode.java @@ -0,0 +1,99 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.util.IdUtil; +import com.easyagents.core.util.StringUtil; +import com.mybatisflex.core.tenant.TenantManager; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.Parameter; +import com.easyagents.flow.core.node.BaseNode; +import tech.easyflow.ai.entity.Resource; +import tech.easyflow.ai.service.ResourceService; +import tech.easyflow.ai.utils.DocUtil; +import tech.easyflow.ai.utils.WorkFlowUtil; +import tech.easyflow.common.constant.enums.EnumResourceOriginType; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.filestorage.FileStorageManager; +import tech.easyflow.common.util.SpringContextUtil; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DownloadNode extends BaseNode { + + private Integer resourceType; + + public DownloadNode() { + } + + public DownloadNode(Integer resourceType) { + this.resourceType = resourceType; + } + + @Override + public Map execute(Chain chain) { + Map map = chain.getState().resolveParameters(this); + Map res = new HashMap<>(); + + String originUrl = map.get("originUrl").toString(); + + byte[] bytes = DocUtil.downloadFile(originUrl); + + String suffix = FileTypeUtil.getType(new ByteArrayInputStream(bytes)); + + if (suffix == null) { + suffix = "unknown"; + } + + String fileName = IdUtil.simpleUUID() + "." + suffix; + + FileStorageManager manager = SpringContextUtil.getBean(FileStorageManager.class); + + String resourceUrl = manager.save(new CustomFile(fileName, bytes)); + + Resource resource = new Resource(); + + LoginAccount account = WorkFlowUtil.getOperator(chain); + + resource.setDeptId(account.getDeptId()); + resource.setTenantId(account.getTenantId()); + resource.setResourceType(this.resourceType); + resource.setResourceName(DocUtil.getFileNameByUrl(resourceUrl).split("\\.")[0]); + resource.setSuffix(suffix); + resource.setResourceUrl(resourceUrl); + resource.setOrigin(EnumResourceOriginType.GENERATE.getCode()); + resource.setCreated(new Date()); + resource.setCreatedBy(account.getId()); + resource.setModified(new Date()); + resource.setModifiedBy(account.getId()); + resource.setFileSize(BigInteger.valueOf(bytes.length)); + try { + TenantManager.ignoreTenantCondition(); + ResourceService service = SpringContextUtil.getBean(ResourceService.class); + service.save(resource); + } finally { + TenantManager.restoreTenantCondition(); + } + + String key = "resourceUrl"; + List outputDefs = getOutputDefs(); + if (outputDefs != null && !outputDefs.isEmpty()) { + String defName = outputDefs.get(0).getName(); + if (StringUtil.hasText(defName)) key = defName; + } + res.put(key, resourceUrl); + return res; + } + + public Integer getResourceType() { + return resourceType; + } + + public void setResourceType(Integer resourceType) { + this.resourceType = resourceType; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNodeParser.java new file mode 100644 index 0000000..bf20ee4 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DownloadNodeParser.java @@ -0,0 +1,22 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; +import tech.easyflow.common.constant.enums.EnumResourceType; + +public class DownloadNodeParser extends BaseNodeParser { + + @Override + protected BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + Integer resourceType = data.getInteger("resourceType"); + if (resourceType == null) { + resourceType = EnumResourceType.OTHER.getCode(); + } + return new DownloadNode(resourceType); + } + + public String getNodeName() { + return "download-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/GiteeParseService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/GiteeParseService.java new file mode 100644 index 0000000..cc19e7c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/GiteeParseService.java @@ -0,0 +1,195 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alicp.jetcache.Cache; +import com.easyagents.flow.core.util.OkHttpClientUtil; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import tech.easyflow.ai.utils.DocUtil; +import tech.easyflow.common.constant.CacheKey; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +@Component("giteeReader") +public class GiteeParseService implements ReadDocService { + + @Value("${node.gitee.appKey}") + private String appKey; + private static final Logger log = LoggerFactory.getLogger(GiteeParseService.class); + @Resource(name = "defaultCache") + private Cache defaultCache; + + @Override + public String read(String fileName, InputStream is) { + return getDocContent(fileName, is); + } + + private String getDocContent(String fileName, InputStream is) { + + Object cache = defaultCache.get(CacheKey.DOC_NODE_CONTENT_KEY + fileName); + + if (cache != null) { + return cache.toString(); + } + String content; + ExecutorService executor = Executors.newFixedThreadPool(5); + try { + byte[] b = DocUtil.readBytes(is); + Map split = splitDocFile(DocUtil.getSuffix(fileName), b, 30); + + List> tasks = new LinkedList<>(); + for (Map.Entry entry : split.entrySet()) { + int index = entry.getKey(); + byte[] splitBytes = entry.getValue(); + tasks.add(() -> splitContent(index + "-" + fileName, splitBytes)); + } + // 提交所有任务并等待完成 + List> futures = executor.invokeAll(tasks); + StringBuilder res = new StringBuilder(); + for (Future future : futures) { + String call = future.get(); + if (StrUtil.isEmpty(call)) { + throw new RuntimeException("读取文件任务失败:" + call); + } + res.append(call).append("\n"); + } + content = res.toString(); + + defaultCache.put(CacheKey.DOC_NODE_CONTENT_KEY + fileName, content); + } catch (Exception e) { + log.error("读取文档内容失败:", e); + throw new RuntimeException("读取文档内容失败:", e); + } finally { + // 关闭线程池 + executor.shutdown(); + try { + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + return content; + } + + private String giteeParse(String fileName, byte[] bytes) { + String url = "https://ai.gitee.com/v1/async/documents/parse"; + // 创建多部分请求体 + MultipartBody.Builder builder = new MultipartBody.Builder() + .setType(MultipartBody.FORM); + + builder.addFormDataPart("model", "PDF-Extract-Kit-1.0"); + builder.addFormDataPart("file", fileName, + RequestBody.create(bytes, null)); + + RequestBody requestBody = builder.build(); + + Request request = new Request.Builder() + .url(url) + .addHeader("Authorization", "Bearer " + appKey) + .post(requestBody).build(); + + OkHttpClient okHttpClient = OkHttpClientUtil.buildDefaultClient(); + Call call = okHttpClient.newCall(request); + try (Response response = call.execute()) { + if (response.body() == null) { + throw new RuntimeException("返回内容为空"); + } + String jsonStr = response.body().string(); + JSONObject object = JSON.parseObject(jsonStr); + log.info("读取文件接口返回:{}", jsonStr); + String error = object.getString("error"); + if (StrUtil.isNotEmpty(error)) { + throw new RuntimeException(object.getString("message")); + } + if ("failure".equals(object.getString("status"))) { + String msg = "请求读取文件[" + fileName + "],失败:" + jsonStr; + log.info(msg); + throw new RuntimeException(msg); + } else { + return object.getString("task_id"); + } + } catch (Exception e) { + log.error("请求读取文件失败:", e); + throw new RuntimeException(e); + } + } + + private String giteeParseResult(String taskId) { + String getTaskUrl = "https://ai.gitee.com/v1/task/" + taskId; + Request.Builder builder = new Request.Builder() + .url(getTaskUrl); + builder.addHeader("Authorization", "Bearer " + appKey); + Request request = builder.build(); + OkHttpClient client = OkHttpClientUtil.buildDefaultClient(); + + Call call = client.newCall(request); + try (Response response = call.execute()) { + if (response.body() == null) { + throw new RuntimeException("返回内容为空"); + } + String jsonStr = response.body().string(); + JSONObject object = JSON.parseObject(jsonStr); + if ("success".equals(object.getString("status"))) { + JSONArray segments = object.getJSONObject("output").getJSONArray("segments"); + StringBuilder md = new StringBuilder(); + for (Object segment : segments) { + JSONObject json = (JSONObject) segment; + md.append(json.getString("content")); + } + return md.toString(); + } else { + System.out.println(taskId + " >>>>>>>>> " + object); + } + } catch (Exception e) { + log.error("请求失败:", e); + throw new RuntimeException(e); + } + return "waiting"; + } + + + private Map splitDocFile(String suffix, byte[] bytes, int splitSize) { + if ("pdf".equals(suffix)) { + return DocUtil.splitPdf(bytes, splitSize); + } else { + // 暂不处理word文档 + Map res = new HashMap<>(); + res.put(1, bytes); + return res; + } + } + + private String splitContent(String fileName, byte[] b) { + String taskId = giteeParse(fileName, b); + while (true) { + ThreadUtil.sleep(1000); + String result = giteeParseResult(taskId); + if (!"waiting".equals(result)) { + // 去掉 HTML 标签,![images/xx](xxx)的内容,提取纯文本 + return handleMdContent(result); + } + } + } + + private String handleMdContent(String content) { + String txt = content.replaceAll("<[^>]*>", " "); + txt = txt.replaceAll("!\\[images[^>]*\\)", " "); + return txt; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/InputStreamFile.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/InputStreamFile.java new file mode 100644 index 0000000..7c36a7f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/InputStreamFile.java @@ -0,0 +1,93 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.io.FileUtil; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.multipart.MultipartFile; +import tech.easyflow.common.util.StringUtil; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.UUID; + +public class InputStreamFile implements MultipartFile { + + private final InputStream inputStream; + private final Map headers; + private String name; + private byte[] bytes; + + public InputStreamFile(InputStream inputStream, Map headers) { + this.inputStream = inputStream; + this.headers = headers; + + String contentDisposition = headers.get("Content-Disposition"); + if (StringUtil.hasText(contentDisposition)) { + this.name = contentDisposition.split(";")[1].split("=")[1].replace("\"", ""); + } + + String contentType = headers.get("Content-Type"); + if (StringUtil.hasText(contentType)) { + String suffix = contentType.split("/")[1]; + this.name = UUID.randomUUID() + "." + suffix; + } + } + + @NotNull + @Override + public String getName() { + return this.name; + } + + + @Override + public String getOriginalFilename() { + return this.name; + } + + public String getContentType() { + return headers.get("Content-Type"); + } + + @Override + public boolean isEmpty() { + return inputStream != null; + } + + @Override + public long getSize() { + try { + return inputStream.available(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @NotNull + @Override + public byte[] getBytes() throws IOException { + if (this.bytes == null) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[2048]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + bos.write(buffer, 0, bytesRead); + } + this.bytes = bos.toByteArray(); + } + return this.bytes; + } + + @NotNull + @Override + public InputStream getInputStream() throws IOException { + return inputStream; + } + + @Override + public void transferTo(@NotNull File dest) throws IOException, IllegalStateException { + FileUtil.writeBytes(this.getBytes(), dest); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNode.java new file mode 100644 index 0000000..aaf083d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNode.java @@ -0,0 +1,101 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.node.BaseNode; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.apache.poi.xwpf.usermodel.XWPFStyle; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle; +import tech.easyflow.common.filestorage.FileStorageManager; +import tech.easyflow.common.util.SpringContextUtil; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MakeFileNode extends BaseNode { + + private String suffix; + + public MakeFileNode() { + } + + public MakeFileNode(String suffix) { + this.suffix = suffix; + } + + @Override + public Map execute(Chain chain) { + Map map = chain.getState().resolveParameters(this); + + Map res = new HashMap<>(); + + String content = map.get("content").toString(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + createFile(suffix, content, os); + + String fileName = IdUtil.fastSimpleUUID() + "." + suffix; + CustomFile file = new CustomFile(fileName, os.toByteArray()); + + FileStorageManager manager = SpringContextUtil.getBean(FileStorageManager.class); + + String url = manager.save(file); + res.put("url", url); + return res; + } + + private void createFile(String suffix, String content, ByteArrayOutputStream os) { + if ("docx".equals(suffix)) { + docx(content, os); + } + } + + private void docx(String content, ByteArrayOutputStream os) { + String separator = "\n"; + List split = StrUtil.split(content, separator); + // 创建一个新的Word文档 + XWPFDocument doc = new XWPFDocument(); + // 创建样式 +// CTStyle ctStyle = CTStyle.Factory.newInstance(); +// ctStyle.setStyleId("IndentStyle"); +// CTPPrGeneral pPr = ctStyle.addNewPPr(); +// CTInd ind = pPr.addNewInd(); +// ind.setFirstLine(400); +// doc.createStyles().addStyle(new XWPFStyle(ctStyle)); + + for (String str : split) { + // 创建段落 + XWPFParagraph paragraph = doc.createParagraph(); + paragraph.setStyle("IndentStyle"); + XWPFRun run = paragraph.createRun(); + run.setText(str); + } + try { + doc.write(os); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + os.close(); + doc.close(); + } catch (IOException e) { + System.out.println("关闭流异常" + e.getMessage()); + } + } + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNodeParser.java new file mode 100644 index 0000000..a2de647 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/MakeFileNodeParser.java @@ -0,0 +1,22 @@ +package tech.easyflow.ai.node; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +public class MakeFileNodeParser extends BaseNodeParser { + + @Override + public BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + String suffix = data.getString("suffix"); + if (StrUtil.isEmpty(suffix)) { + suffix = "docx"; + } + return new MakeFileNode(suffix); + } + + public String getNodeName() { + return "make-file"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNode.java new file mode 100644 index 0000000..8cbcd17 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNode.java @@ -0,0 +1,59 @@ +package tech.easyflow.ai.node; + +import com.easyagents.core.model.chat.tool.Tool; +import com.alibaba.fastjson.JSON; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.node.BaseNode; +import tech.easyflow.ai.entity.PluginItem; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.common.util.SpringContextUtil; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.Map; + +public class PluginToolNode extends BaseNode { + + private BigInteger pluginId; + + public PluginToolNode() { + } + + public PluginToolNode(BigInteger pluginId) { + this.pluginId = pluginId; + } + + @SuppressWarnings("unchecked") + @Override + public Map execute(Chain chain) { + Map map = chain.getState().resolveParameters(this); + PluginItemService bean = SpringContextUtil.getBean(PluginItemService.class); + PluginItem tool = bean.getById(pluginId); + if (tool == null) { + return Collections.emptyMap(); + } + Tool function = tool.toFunction(); + if (function == null) { + return Collections.emptyMap(); + } + + Object result = function.invoke(map); + if (result == null) { + return Collections.emptyMap(); + } + + if (result instanceof Map) { + return (Map) result; + } + + return JSON.parseObject(JSON.toJSONString(result), Map.class); + } + + public BigInteger getPluginId() { + return pluginId; + } + + public void setPluginId(BigInteger pluginId) { + this.pluginId = pluginId; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNodeParser.java new file mode 100644 index 0000000..71aa955 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/PluginToolNodeParser.java @@ -0,0 +1,20 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +import java.math.BigInteger; + +public class PluginToolNodeParser extends BaseNodeParser { + + @Override + public BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + BigInteger pluginId = data.getBigInteger("pluginId"); + return new PluginToolNode(pluginId); + } + + public String getNodeName() { + return "plugin-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReadDocService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReadDocService.java new file mode 100644 index 0000000..c948581 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReadDocService.java @@ -0,0 +1,8 @@ +package tech.easyflow.ai.node; + +import java.io.InputStream; + +public interface ReadDocService { + + String read(String fileName, InputStream is); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReaderManager.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReaderManager.java new file mode 100644 index 0000000..9280bd2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ReaderManager.java @@ -0,0 +1,16 @@ +package tech.easyflow.ai.node; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import tech.easyflow.common.util.SpringContextUtil; + +@Component +public class ReaderManager { + + @Value("${node.reader}") + private String reader; + + public ReadDocService getReader() { + return SpringContextUtil.getBean(reader); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNode.java new file mode 100644 index 0000000..897f742 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNode.java @@ -0,0 +1,75 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.tenant.TenantManager; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.node.BaseNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.ai.utils.WorkFlowUtil; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.datacenter.service.DatacenterTableService; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +public class SaveToDatacenterNode extends BaseNode { + + private static final Logger log = LoggerFactory.getLogger(SaveToDatacenterNode.class); + + private BigInteger tableId; + + public SaveToDatacenterNode() { + } + + public SaveToDatacenterNode(BigInteger tableId) { + this.tableId = tableId; + } + + @Override + public Map execute(Chain chain) { + + Map map = chain.getState().resolveParameters(this); + JSONObject json = new JSONObject(map); + + Map res = new HashMap<>(); + + // 默认为未知来源 + LoginAccount account = WorkFlowUtil.getOperator(chain); + + DatacenterTableService service = SpringContextUtil.getBean(DatacenterTableService.class); + + JSONArray saveList = json.getJSONArray("saveList"); + + int successRows = 0; + for (Object object : saveList) { + JSONObject obj = new JSONObject((com.alibaba.fastjson.JSONObject) object); + obj.put("table_id", tableId); + try { + TenantManager.ignoreTenantCondition(); + service.saveValue(tableId, obj, account); + } catch (Exception e) { + log.error("工作流保存数据到数据中枢失败,表ID:{},具体值:{}", tableId, obj, e); + throw e; + } finally { + TenantManager.restoreTenantCondition(); + } + successRows++; + } + + res.put("successRows", successRows); + return res; + } + + public BigInteger getTableId() { + return tableId; + } + + public void setTableId(BigInteger tableId) { + this.tableId = tableId; + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNodeParser.java new file mode 100644 index 0000000..ad6407d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SaveToDatacenterNodeParser.java @@ -0,0 +1,23 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +import java.math.BigInteger; + +public class SaveToDatacenterNodeParser extends BaseNodeParser { + + @Override + protected BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + BigInteger tableId = data.getBigInteger("tableId"); + if (tableId == null) { + throw new RuntimeException("请选择数据表"); + } + return new SaveToDatacenterNode(tableId); + } + + public String getNodeName() { + return "save-to-datacenter-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNode.java new file mode 100644 index 0000000..840683a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNode.java @@ -0,0 +1,143 @@ +package tech.easyflow.ai.node; + +import com.easyagents.core.util.StringUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.row.Row; +import com.mybatisflex.core.tenant.TenantManager; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.Parameter; +import com.easyagents.flow.core.node.BaseNode; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.common.entity.DatacenterQuery; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.service.DatacenterTableService; +import tech.easyflow.datacenter.utils.WhereConditionSecurityChecker; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class SearchDatacenterNode extends BaseNode { + + private static final Logger log = LoggerFactory.getLogger(SearchDatacenterNode.class); + private BigInteger tableId; + private String where; + private Long limit; + + public SearchDatacenterNode() { + } + + public SearchDatacenterNode(BigInteger tableId, String where, Long limit) { + this.tableId = tableId; + this.where = where; + this.limit = limit; + } + + @Override + public Map execute(Chain chain) { + + Map map = chain.getState().resolveParameters(this); + Map res = new HashMap<>(); + long limitNum = 10; + if (limit != null) { + limitNum = Long.parseLong(limit.toString()); + } + + DatacenterTableService service = SpringContextUtil.getBean(DatacenterTableService.class); + + DatacenterQuery condition = new DatacenterQuery(); + condition.setTableId(tableId); + condition.setPageNumber(1L); + condition.setPageSize(limitNum); + // 组合查询条件 + if (where != null) { + setCondition(where, condition, map); + } + try { + TenantManager.ignoreTenantCondition(); + Page pageData = service.getPageData(condition); + + String key = "rows"; + List outputDefs = getOutputDefs(); + if (outputDefs != null && !outputDefs.isEmpty()) { + String defName = outputDefs.get(0).getName(); + if (StringUtil.hasText(defName)) key = defName; + } + res.put(key, pageData.getRecords()); + } finally { + TenantManager.restoreTenantCondition(); + } + return res; + } + + 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; + } + + public Long getLimit() { + return limit; + } + + public void setLimit(Long limit) { + this.limit = limit; + } + + private void setCondition(String where, DatacenterQuery condition, Map params) { + // 条件封装 + Pattern pattern = Pattern.compile("\\{\\{(.+?)\\}\\}"); + Matcher matcher = pattern.matcher(where); + + StringBuffer result = new StringBuffer(); + while (matcher.find()) { + String key = matcher.group(1); + Object value = params.get(key); + if (value == null) { + throw new RuntimeException("参数" + key + "不存在"); + } + String replacement = value.toString(); + matcher.appendReplacement(result, "'" + replacement + "'"); + } + matcher.appendTail(result); + + try { + Expression expression = CCJSqlParserUtil.parseCondExpression(result.toString()); + if (expression != null) { + WhereConditionSecurityChecker checker = new WhereConditionSecurityChecker(); + DatacenterTableService service = SpringContextUtil.getBean(DatacenterTableService.class); + List fields = service.getFields(tableId); + Set columns = fields.stream().map(DatacenterTableField::getFieldName).collect(Collectors.toSet()); + columns.add("id"); + columns.add("created"); + columns.add("modified"); + columns.add("created_by"); + columns.add("modified_by"); + checker.checkConditionSafety(expression, columns); + condition.setWhere(expression.toString()); + } + } catch (Exception e) { + log.error("WHERE SQL解析错误:", e); + throw new RuntimeException(e); + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNodeParser.java new file mode 100644 index 0000000..fb2fa70 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SearchDatacenterNodeParser.java @@ -0,0 +1,25 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +import java.math.BigInteger; + +public class SearchDatacenterNodeParser extends BaseNodeParser { + + @Override + protected BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + BigInteger tableId = data.getBigInteger("tableId"); + String where = data.getString("where"); + Long limit = data.getLong("limit"); + if (tableId == null) { + throw new RuntimeException("请选择数据表"); + } + return new SearchDatacenterNode(tableId,where,limit); + } + + public String getNodeName() { + return "search-datacenter-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNode.java new file mode 100644 index 0000000..d9e2474 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNode.java @@ -0,0 +1,149 @@ +package tech.easyflow.ai.node; + +import com.mybatisflex.core.row.Db; +import com.mybatisflex.core.row.Row; +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.node.BaseNode; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Select; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import tech.easyflow.common.web.exceptions.BusinessException; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * SQL查询节点 + * + * @author tao + * @date 2025-05-21 + */ +public class SqlNode extends BaseNode { + + private String sql; + + private static final Logger logger = LoggerFactory.getLogger(SqlNode.class); + + public SqlNode() { + } + + public SqlNode(String sql) { + this.sql = sql; + } + + @Override + public Map execute(Chain chain) { + + Map map = chain.getState().resolveParameters(this); + Map res = new HashMap<>(); + + + Map formatSqlMap = formatSql(sql, map); + String formatSql = (String) formatSqlMap.get("replacedSql"); + + Statement statement = null; + try { + statement = CCJSqlParserUtil.parse(formatSql); + + } catch (JSQLParserException e) { + logger.error("sql 解析报错:", e); + throw new BusinessException("SQL解析失败,请确认SQL语法无误"); + } + + if (!(statement instanceof Select)) { + logger.error("sql 解析报错:statement instanceof Select 结果为false"); + throw new BusinessException("仅支持查询语句!"); + } + + List paramNames = (List) formatSqlMap.get("paramNames"); + + List paramValues = new ArrayList<>(); + paramNames.forEach(paramName -> { + Object o = map.get(paramName); + paramValues.add(o); + }); + + List rows = Db.selectListBySql(formatSql, paramValues.toArray()); + + if (rows == null || rows.isEmpty()) { + return Collections.emptyMap(); + } + + res.put("queryData", rows); + return res; + } + + private Map formatSql(String rawSql, Map paramMap) { + + if (!StringUtils.hasLength(rawSql)) { + logger.error("sql解析报错:sql为空"); + throw new BusinessException("sql 不能为空!"); + } + + // 匹配 {{?...}} 表示可用占位符的参数 + Pattern paramPattern = Pattern.compile("\\{\\{\\?([^}]+)}}"); + + // 匹配 {{...}} 表示直接替换的参数(非占位符) + Pattern directPattern = Pattern.compile("\\{\\{([^}?][^}]*)}}"); + + List paramNames = new ArrayList<>(); + StringBuffer sqlBuffer = new StringBuffer(); + + // 替换 {{?...}} -> ? + Matcher paramMatcher = paramPattern.matcher(rawSql); + while (paramMatcher.find()) { + String paramName = paramMatcher.group(1).trim(); + paramNames.add(paramName); + paramMatcher.appendReplacement(sqlBuffer, "?"); + } + paramMatcher.appendTail(sqlBuffer); + String intermediateSql = sqlBuffer.toString(); + + // 替换 {{...}} -> 实际值(用于表名/列名等) + sqlBuffer = new StringBuffer(); // 清空 buffer 重新处理 + Matcher directMatcher = directPattern.matcher(intermediateSql); + while (directMatcher.find()) { + String key = directMatcher.group(1).trim(); + Object value = paramMap.get(key); + if (value == null) { + logger.error("未找到参数:" + key); + throw new BusinessException("sql解析失败,请确保sql语法正确!"); + } + + String safeValue = value.toString(); + + directMatcher.appendReplacement(sqlBuffer, Matcher.quoteReplacement(safeValue)); + } + directMatcher.appendTail(sqlBuffer); + + String finalSql = sqlBuffer.toString().trim(); + + // 清理末尾分号与中文引号 + if (finalSql.endsWith(";") || finalSql.endsWith(";")) { + finalSql = finalSql.substring(0, finalSql.length() - 1); + } + finalSql = finalSql.replace("“", "\"").replace("”", "\""); + + logger.info("Final SQL: {}", finalSql); + logger.info("Param names: {}", paramNames); + + Map result = new HashMap<>(); + result.put("replacedSql", finalSql); + result.put("paramNames", paramNames); + return result; + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNodeParser.java new file mode 100644 index 0000000..9b2dfcf --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/SqlNodeParser.java @@ -0,0 +1,25 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +/** + * Sql查询节点解析 + * + * @author tao + * @date 2025-05-21 + */ +public class SqlNodeParser extends BaseNodeParser { + + + @Override + public BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + String sql = data.getString("sql"); + return new SqlNode(sql); + } + + public String getNodeName() { + return "sql-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNode.java new file mode 100644 index 0000000..a6d79a6 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNode.java @@ -0,0 +1,43 @@ +package tech.easyflow.ai.node; + +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import com.easyagents.flow.core.node.BaseNode; +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.service.WorkflowService; +import tech.easyflow.common.util.SpringContextUtil; + +import java.util.Map; + +public class WorkflowNode extends BaseNode { + + private String workflowId; + + public WorkflowNode() { + } + + public WorkflowNode(String workflowId) { + this.workflowId = workflowId; + } + + @Override + public Map execute(Chain chain) { + + Map params = chain.getState().resolveParameters(this); + WorkflowService service = SpringContextUtil.getBean(WorkflowService.class); + Workflow workflow = service.getById(workflowId); + if (workflow == null) { + throw new RuntimeException("工作流不存在:" + workflowId); + } + ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class); + return executor.execute(workflowId, params); + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNodeParser.java new file mode 100644 index 0000000..5e07055 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/WorkflowNodeParser.java @@ -0,0 +1,21 @@ +package tech.easyflow.ai.node; + +import com.alibaba.fastjson.JSONObject; +import com.easyagents.flow.core.node.BaseNode; +import com.easyagents.flow.core.parser.BaseNodeParser; + +public class WorkflowNodeParser extends BaseNodeParser { + + @Override + protected BaseNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) { + String workflowId = data.getString("workflowId"); + if (workflowId == null) { + throw new RuntimeException("请选择工作流"); + } + return new WorkflowNode(workflowId); + } + + public String getNodeName() { + return "workflow-node"; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotCategoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotCategoryService.java new file mode 100644 index 0000000..2f56b18 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotCategoryService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotCategory; + +/** + * bot分类 服务层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +public interface BotCategoryService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotConversationService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotConversationService.java new file mode 100644 index 0000000..132ff56 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotConversationService.java @@ -0,0 +1,19 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotConversation; + +import java.math.BigInteger; + +/** + * 服务层。 + * + * @author Administrator + * @since 2025-04-15 + */ +public interface BotConversationService extends IService { + + void deleteConversation(String botId, String conversationId, BigInteger accountId); + + void updateConversation(String botId, String conversationId, String title, BigInteger accountId); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotDocumentCollectionService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotDocumentCollectionService.java new file mode 100644 index 0000000..9c8c10a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotDocumentCollectionService.java @@ -0,0 +1,20 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.BotDocumentCollection; +import com.mybatisflex.core.service.IService; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotDocumentCollectionService extends IService { + + List listByBotId(BigInteger botId); + + void saveBotAndKnowledge(BigInteger botId, BigInteger[] knowledgeIds); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMcpService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMcpService.java new file mode 100644 index 0000000..61d1d64 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMcpService.java @@ -0,0 +1,19 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotMcp; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +/** + * 服务层。 + * + * @author wangGangQiang + * @since 2026-01-05 + */ +public interface BotMcpService extends IService { + + void updateBotMcpToolIds(BigInteger botId, List>>> mcpSelectedData); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMessageService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMessageService.java new file mode 100644 index 0000000..22230f3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotMessageService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotMessage; + +/** + * Bot 消息记录表 服务层。 + * + * @author michael + * @since 2024-11-04 + */ +public interface BotMessageService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotModelService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotModelService.java new file mode 100644 index 0000000..2247b57 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotModelService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.BotModel; +import com.mybatisflex.core.service.IService; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotModelService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotPluginService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotPluginService.java new file mode 100644 index 0000000..b20ed49 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotPluginService.java @@ -0,0 +1,25 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.ai.entity.Plugin; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author michael + * @since 2025-04-07 + */ +public interface BotPluginService extends IService { + + List getList(String botId); + + boolean doRemove(String botId, String pluginId); + + List getBotPluginToolIds(String botId); + + void saveBotAndPluginTool(BigInteger botId, BigInteger[] pluginToolIds); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotRecentlyUsedService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotRecentlyUsedService.java new file mode 100644 index 0000000..813ad9d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotRecentlyUsedService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotRecentlyUsed; + +/** + * 最近使用 服务层。 + * + * @author ArkLight + * @since 2025-12-18 + */ +public interface BotRecentlyUsedService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotService.java new file mode 100644 index 0000000..a25dae2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotService.java @@ -0,0 +1,38 @@ +package tech.easyflow.ai.service; + +import com.easyagents.core.message.Message; +import com.easyagents.core.message.UserMessage; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.chat.ChatOptions; +import com.easyagents.core.prompt.MemoryPrompt; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.entity.Bot; +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.service.impl.BotServiceImpl; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface BotService extends IService { + + Bot getDetail(String id); + + void updateBotLlmId(Bot aiBot); + + Bot getByAlias(String alias); + + SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, BotServiceImpl.ChatCheckResult chatCheckResult); + + SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List> messages, BotServiceImpl.ChatCheckResult chatCheckResult, List attachments); + + SseEmitter startPublicChat(BigInteger botId, String prompt, List messages, BotServiceImpl.ChatCheckResult chatCheckResult); + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotWorkflowService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotWorkflowService.java new file mode 100644 index 0000000..1052dfb --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/BotWorkflowService.java @@ -0,0 +1,20 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.BotWorkflow; +import com.mybatisflex.core.service.IService; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-28 + */ +public interface BotWorkflowService extends IService { + + List listByBotId(BigInteger botId); + + void saveBotAndWorkflowTool(BigInteger botId, BigInteger[] workflowIds); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentChunkService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentChunkService.java new file mode 100644 index 0000000..a70d1a1 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentChunkService.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.DocumentChunk; +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.DocumentCollection; + +import java.math.BigInteger; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentChunkService extends IService { + + boolean removeChunk(DocumentCollection knowledge, BigInteger chunkId); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionCategoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionCategoryService.java new file mode 100644 index 0000000..faa7418 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionCategoryService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.DocumentCollectionCategory; + +/** + * 服务层。 + * + * @author 12076 + * @since 2026-01-23 + */ +public interface DocumentCollectionCategoryService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionService.java new file mode 100644 index 0000000..b5dd444 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentCollectionService.java @@ -0,0 +1,23 @@ +package tech.easyflow.ai.service; + +import com.easyagents.core.document.Document; +import tech.easyflow.ai.entity.DocumentCollection; +import com.mybatisflex.core.service.IService; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentCollectionService extends IService { + + List search(BigInteger id, String keyword); + + DocumentCollection getDetail(String idOrAlias); + + DocumentCollection getByAlias(String idOrAlias); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentHistoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentHistoryService.java new file mode 100644 index 0000000..eaf7549 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentHistoryService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.DocumentHistory; +import com.mybatisflex.core.service.IService; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentHistoryService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentService.java new file mode 100644 index 0000000..c56712d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/DocumentService.java @@ -0,0 +1,28 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.Document; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.DocumentChunk; +import tech.easyflow.ai.entity.DocumentCollectionSplitParams; +import tech.easyflow.common.domain.Result; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface DocumentService extends IService { + + Page getDocumentList(String knowledgeId , int pageSize, int pageNum, String fileName); + + boolean removeDoc(String id); + + Result textSplit(DocumentCollectionSplitParams documentCollectionSplitParams); + + Result saveTextResult(List documentChunks, Document document); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/McpService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/McpService.java new file mode 100644 index 0000000..42127ac --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/McpService.java @@ -0,0 +1,33 @@ +package tech.easyflow.ai.service; + +import com.easyagents.core.model.chat.tool.Tool; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.BotMcp; +import tech.easyflow.ai.entity.Mcp; +import tech.easyflow.common.domain.Result; + +import java.io.Serializable; + +/** + * 服务层。 + * + * @author wangGangQiang + * @since 2026-01-04 + */ +public interface McpService extends IService { + + Result saveMcp(Mcp entity); + + Result updateMcp(Mcp entity); + + void removeMcp(Serializable id); + + Tool toFunction(BotMcp botMcp); + + Result> pageMcp(Result> page); + + Mcp getMcpTools(String id); + + Page pageTools(Page mcpPage); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelProviderService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelProviderService.java new file mode 100644 index 0000000..c37c2bb --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelProviderService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.ModelProvider; + +/** + * 服务层。 + * + * @author 12076 + * @since 2025-12-16 + */ +public interface ModelProviderService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelService.java new file mode 100644 index 0000000..cc2368e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ModelService.java @@ -0,0 +1,30 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.Model; +import com.mybatisflex.core.service.IService; +import tech.easyflow.common.domain.Result; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface ModelService extends IService { + + boolean addAiLlm(Model entity); + + Map verifyModelConfig(Model llm); + + Map>> getList(Model entity); + + void removeByEntity(Model entity); + + Model getModelInstance(BigInteger modelId); + + void updateByEntity(Model entity); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryMappingService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryMappingService.java new file mode 100644 index 0000000..da83456 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryMappingService.java @@ -0,0 +1,22 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.PluginCategory; +import tech.easyflow.ai.entity.PluginCategoryMapping; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * 服务层。 + * + * @author Administrator + * @since 2025-05-21 + */ +public interface PluginCategoryMappingService extends IService { + + boolean updateRelation(BigInteger pluginId, ArrayList categoryIds); + + List getPluginCategories(BigInteger pluginId); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryService.java new file mode 100644 index 0000000..7fd5793 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginCategoryService.java @@ -0,0 +1,17 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.PluginCategory; + +import java.math.BigInteger; + +/** + * 服务层。 + * + * @author Administrator + * @since 2025-05-21 + */ +public interface PluginCategoryService extends IService { + + boolean doRemoveCategory(BigInteger id); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginItemService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginItemService.java new file mode 100644 index 0000000..fbdf62a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginItemService.java @@ -0,0 +1,31 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.PluginItem; +import tech.easyflow.common.domain.Result; + +import java.math.BigInteger; +import java.util.List; + +/** + * 服务层。 + * + * @author WangGangqiang + * @since 2025-04-27 + */ +public interface PluginItemService extends IService { + + boolean savePluginTool(PluginItem pluginItem); + + Result searchPlugin(BigInteger aiPluginToolId); + + boolean updatePlugin(PluginItem pluginItem); + + List searchPluginToolByPluginId(BigInteger pluginId, BigInteger botId); + + List getPluginToolList(BigInteger botId); + + Result pluginToolTest(String inputData, BigInteger pluginToolId); + + List getByPluginId(String id); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java new file mode 100644 index 0000000..0a5bc13 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java @@ -0,0 +1,26 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.common.domain.Result; + +import java.util.List; + +/** + * 服务层。 + * + * @author WangGangqiang + * @since 2025-04-25 + */ +public interface PluginService extends IService { + + boolean savePlugin(Plugin plugin); + + boolean removePlugin(String id); + + List getList(); + + Result pageByCategory(Long pageNumber, Long pageSize, int category); + + boolean updatePlugin(Plugin plugin); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceCategoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceCategoryService.java new file mode 100644 index 0000000..e00e3a5 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceCategoryService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.ResourceCategory; + +/** + * 素材分类 服务层。 + * + * @author ArkLight + * @since 2025-12-24 + */ +public interface ResourceCategoryService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceService.java new file mode 100644 index 0000000..296f395 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/ResourceService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.Resource; + +/** + * 素材库 服务层。 + * + * @author ArkLight + * @since 2025-06-27 + */ +public interface ResourceService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowCategoryService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowCategoryService.java new file mode 100644 index 0000000..f7c2ac8 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowCategoryService.java @@ -0,0 +1,14 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.WorkflowCategory; + +/** + * 服务层。 + * + * @author ArkLight + * @since 2025-12-11 + */ +public interface WorkflowCategoryService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecResultService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecResultService.java new file mode 100644 index 0000000..8d35a00 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecResultService.java @@ -0,0 +1,15 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.WorkflowExecResult; + +/** + * 工作流执行记录 服务层。 + * + * @author ArkLight + * @since 2025-05-28 + */ +public interface WorkflowExecResultService extends IService { + + WorkflowExecResult getByExecKey(String execKey); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecStepService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecStepService.java new file mode 100644 index 0000000..0a700e6 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowExecStepService.java @@ -0,0 +1,16 @@ +package tech.easyflow.ai.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.ai.entity.WorkflowExecStep; + +/** + * 执行记录步骤 服务层。 + * + * @author ArkLight + * @since 2025-05-28 + */ +public interface WorkflowExecStepService extends IService { + + // 根据 execKey 获取记录 + WorkflowExecStep getByExecKey(String execKey); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowService.java new file mode 100644 index 0000000..4fd7171 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/WorkflowService.java @@ -0,0 +1,21 @@ +package tech.easyflow.ai.service; + +import tech.easyflow.ai.entity.Workflow; +import com.mybatisflex.core.service.IService; + +/** + * 服务层。 + * + * @author michael + * @since 2024-08-23 + */ +public interface WorkflowService extends IService { + + /** + * 根据别名或 id 查询详情 + */ + Workflow getDetail(String idOrAlias); + + + Workflow getByAlias(String alias); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotCategoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotCategoryServiceImpl.java new file mode 100644 index 0000000..1ba7376 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotCategoryServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotCategory; +import tech.easyflow.ai.mapper.BotCategoryMapper; +import tech.easyflow.ai.service.BotCategoryService; + +/** + * bot分类 服务层实现。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@Service +public class BotCategoryServiceImpl extends ServiceImpl implements BotCategoryService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotConversationServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotConversationServiceImpl.java new file mode 100644 index 0000000..995b3b7 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotConversationServiceImpl.java @@ -0,0 +1,58 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotConversation; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.ai.mapper.BotConversationMapper; +import tech.easyflow.ai.mapper.BotMessageMapper; +import tech.easyflow.ai.service.BotConversationService; + +import javax.annotation.Resource; +import java.math.BigInteger; + +/** + * 服务层实现。 + * + * @author Administrator + * @since 2025-04-15 + */ +@Service +public class BotConversationServiceImpl extends ServiceImpl implements BotConversationService { + + @Resource + private BotConversationMapper botConversationMapper; + + @Resource + private BotMessageMapper botMessageMapper; + + /** + * 删除指定会话 + */ + @Override + public void deleteConversation(String botId, String conversationId, BigInteger accountId) { + QueryWrapper cqw = QueryWrapper.create(); + cqw.eq(BotConversation::getBotId, botId); + cqw.eq(BotConversation::getId, conversationId); + cqw.eq(BotConversation::getAccountId, accountId); + botConversationMapper.deleteByQuery(cqw); + // 删除消息记录中的数据 + QueryWrapper mqw = QueryWrapper.create(); + mqw.eq(BotMessage::getBotId, botId); + mqw.eq(BotMessage::getConversationId, conversationId); + mqw.eq(BotMessage::getAccountId, accountId); + botMessageMapper.deleteByQuery(mqw); + } + + @Override + public void updateConversation(String botId, String conversationId, String title, BigInteger accountId) { + QueryWrapper cqw = QueryWrapper.create(); + cqw.eq(BotConversation::getBotId, botId); + cqw.eq(BotConversation::getId, conversationId); + cqw.eq(BotConversation::getAccountId, accountId); + BotConversation update = new BotConversation(); + update.setTitle(title); + botConversationMapper.updateByQuery(update, cqw); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotDocumentCollectionServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotDocumentCollectionServiceImpl.java new file mode 100644 index 0000000..fa68497 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotDocumentCollectionServiceImpl.java @@ -0,0 +1,44 @@ +package tech.easyflow.ai.service.impl; + +import tech.easyflow.ai.entity.BotDocumentCollection; +import tech.easyflow.ai.mapper.BotDocumentCollectionMapper; +import tech.easyflow.ai.service.BotDocumentCollectionService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import com.mybatisflex.core.query.QueryWrapper; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-28 + */ +@Service +public class BotDocumentCollectionServiceImpl extends ServiceImpl implements BotDocumentCollectionService { + + @Override + public List listByBotId(BigInteger botId) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(BotDocumentCollection::getBotId,botId); + + return list(queryWrapper); + } + + @Override + public void saveBotAndKnowledge(BigInteger botId, BigInteger[] knowledgeIds) { + this.remove(QueryWrapper.create().eq(BotDocumentCollection::getBotId, botId)); + List list = new ArrayList<>(knowledgeIds.length); + for (BigInteger knowledgeId : knowledgeIds) { + BotDocumentCollection botDocumentCollection = new BotDocumentCollection(); + botDocumentCollection.setBotId(botId); + botDocumentCollection.setDocumentCollectionId(knowledgeId); + list.add(botDocumentCollection); + } + this.saveBatch(list); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMcpServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMcpServiceImpl.java new file mode 100644 index 0000000..958a220 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMcpServiceImpl.java @@ -0,0 +1,50 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.transaction.annotation.Transactional; +import tech.easyflow.ai.entity.BotMcp; +import tech.easyflow.ai.mapper.BotMcpMapper; +import tech.easyflow.ai.service.BotMcpService; +import org.springframework.stereotype.Service; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 服务层实现。 + * + * @author wangGangQiang + * @since 2026-01-05 + */ +@Service +public class BotMcpServiceImpl extends ServiceImpl implements BotMcpService{ + + @Override + @Transactional + public void updateBotMcpToolIds(BigInteger botId, List>>> mcpSelectedData) { + // 删除原来绑定的mcp + this.remove(QueryWrapper.create().eq(BotMcp::getBotId, botId)); + for (Map>> mcpItem : mcpSelectedData) { + for (Map.Entry>> entry : mcpItem.entrySet()) { + String mcpId = entry.getKey(); // 上一级id + List> toolList = entry.getValue(); // 包含name和description的二维数组 + + // 遍历每个工具的[name, description] + for (List toolInfo : toolList) { + String toolName = toolInfo.get(0); // 工具名称 + String toolDesc = toolInfo.get(1); // 工具描述 + System.out.println("工具名称:" + toolName + ",描述:" + toolDesc); + BotMcp botMcp = new BotMcp(); + botMcp.setBotId(botId); + botMcp.setMcpId(new BigInteger(mcpId)); + botMcp.setMcpToolName(toolName); + botMcp.setMcpToolDescription(toolDesc); + this.save(botMcp); + } + } + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMessageServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMessageServiceImpl.java new file mode 100644 index 0000000..0ff6775 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotMessageServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotMessage; +import tech.easyflow.ai.mapper.BotMessageMapper; +import tech.easyflow.ai.service.BotMessageService; + +/** + * Bot 消息记录表 服务层实现。 + * + * @author michael + * @since 2024-11-04 + */ +@Service +public class BotMessageServiceImpl extends ServiceImpl implements BotMessageService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotModelServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotModelServiceImpl.java new file mode 100644 index 0000000..64b07df --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotModelServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import tech.easyflow.ai.entity.BotModel; +import tech.easyflow.ai.mapper.BotModelMapper; +import tech.easyflow.ai.service.BotModelService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-28 + */ +@Service +public class BotModelServiceImpl extends ServiceImpl implements BotModelService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotPluginServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotPluginServiceImpl.java new file mode 100644 index 0000000..3ba7c26 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotPluginServiceImpl.java @@ -0,0 +1,72 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.mapper.BotPluginMapper; +import tech.easyflow.ai.mapper.PluginMapper; +import tech.easyflow.ai.service.BotPluginService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN; + +/** + * 服务层实现。 + * + * @author michael + * @since 2025-04-07 + */ +@Service +public class BotPluginServiceImpl extends ServiceImpl implements BotPluginService { + + @Resource + private BotPluginMapper botPluginMapper; + + @Resource + private PluginMapper pluginMapper; + + @Override + public List getList(String botId) { + QueryWrapper w = QueryWrapper.create(); + w.select(BOT_PLUGIN.PLUGIN_ITEM_ID) + .where(BOT_PLUGIN.BOT_ID.eq(botId)); + List pluginIds = botPluginMapper.selectListByQueryAs(w, BigInteger.class); + return pluginMapper.selectListByIds(pluginIds); + } + + @Override + public boolean doRemove(String botId, String pluginToolId) { + QueryWrapper w = QueryWrapper.create(); + w.select(BOT_PLUGIN.ID) + .where(BOT_PLUGIN.BOT_ID.eq(botId)) + .and(BOT_PLUGIN.PLUGIN_ITEM_ID.eq(pluginToolId)); + BigInteger id = botPluginMapper.selectOneByQueryAs(w, BigInteger.class); + int delete = botPluginMapper.deleteById(id); + return delete > 0; + } + + @Override + public List getBotPluginToolIds(String botId) { + QueryWrapper queryWrapper = QueryWrapper.create().select(BOT_PLUGIN.PLUGIN_ITEM_ID).where(BOT_PLUGIN.BOT_ID.eq(botId)); + return botPluginMapper.selectListByQueryAs(queryWrapper, BigInteger.class); + } + + @Override + public void saveBotAndPluginTool(BigInteger botId, BigInteger[] pluginToolIds) { + this.remove(QueryWrapper.create().eq(BotPlugin::getBotId, botId)); + List list = new ArrayList<>(pluginToolIds.length); + for (BigInteger pluginToolId : pluginToolIds) { + BotPlugin aiBotPluginTool = new BotPlugin(); + aiBotPluginTool.setBotId(botId); + aiBotPluginTool.setPluginItemId(pluginToolId); + list.add(aiBotPluginTool); + } + this.saveBatch(list); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotRecentlyUsedServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotRecentlyUsedServiceImpl.java new file mode 100644 index 0000000..6af562d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotRecentlyUsedServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotRecentlyUsed; +import tech.easyflow.ai.mapper.BotRecentlyUsedMapper; +import tech.easyflow.ai.service.BotRecentlyUsedService; + +/** + * 最近使用 服务层实现。 + * + * @author ArkLight + * @since 2025-12-18 + */ +@Service +public class BotRecentlyUsedServiceImpl extends ServiceImpl implements BotRecentlyUsedService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java new file mode 100644 index 0000000..76e945a --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java @@ -0,0 +1,400 @@ +package tech.easyflow.ai.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import com.easyagents.core.file2text.File2TextService; +import com.easyagents.core.file2text.source.HttpDocumentSource; +import com.easyagents.core.memory.ChatMemory; +import com.easyagents.core.message.Message; +import com.easyagents.core.message.SystemMessage; +import com.easyagents.core.message.UserMessage; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.chat.ChatOptions; +import com.easyagents.core.model.chat.StreamResponseListener; +import com.easyagents.core.model.chat.tool.Tool; +import com.easyagents.core.prompt.MemoryPrompt; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +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.servlet.mvc.method.annotation.SseEmitter; +import tech.easyflow.ai.easyagents.listener.ChatStreamListener; +import tech.easyflow.ai.easyagents.memory.BotMessageMemory; +import tech.easyflow.ai.easyagents.memory.DefaultBotMessageMemory; +import tech.easyflow.ai.easyagents.memory.PublicBotMessageMemory; +import tech.easyflow.ai.entity.*; +import tech.easyflow.ai.mapper.BotMapper; +import tech.easyflow.ai.service.*; +import tech.easyflow.ai.utils.CustomBeanUtils; +import tech.easyflow.ai.utils.RegexUtils; +import tech.easyflow.common.filestorage.FileStorageService; +import tech.easyflow.common.filestorage.utils.PathGeneratorUtil; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.util.MapUtil; +import tech.easyflow.common.util.Maps; +import tech.easyflow.common.util.UrlEncoderUtil; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; +import tech.easyflow.core.chat.protocol.sse.ChatSseUtil; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN; +import static tech.easyflow.ai.entity.table.PluginItemTableDef.PLUGIN_ITEM; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class BotServiceImpl extends ServiceImpl implements BotService { + + private static final Logger log = LoggerFactory.getLogger(BotServiceImpl.class); + + public static class ChatCheckResult { + private Bot aiBot; + private Map modelOptions; + private ChatModel chatModel; + private String conversationIdStr; + + public Bot getAiBot() {return aiBot;} + + public void setAiBot(Bot aiBot) {this.aiBot = aiBot;} + + public Map getModelOptions() {return modelOptions;} + + public void setModelOptions(Map modelOptions) {this.modelOptions = modelOptions;} + + public ChatModel getChatModel() {return chatModel;} + + public void setChatModel(ChatModel chatModel) {this.chatModel = chatModel;} + + public String getConversationIdStr() {return conversationIdStr;} + + public void setConversationIdStr(String conversationIdStr) {this.conversationIdStr = conversationIdStr;} + } + + @Resource + private BotMessageService botMessageService; + + @Resource(name = "sseThreadPool") + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + @Resource + private ModelService modelService; + @Resource + private BotWorkflowService botWorkflowService; + @Resource + private BotDocumentCollectionService botDocumentCollectionService; + @Resource + private BotPluginService botPluginService; + @Resource + private PluginItemService pluginItemService; + @Resource + private BotMcpService botMcpService; + @Resource + private McpService mcpService; + @Resource(name = "default") + FileStorageService storageService; + + @Override + public Bot getDetail(String id) { + Bot aiBot = null; + + if (id.matches(RegexUtils.ALL_NUMBER)) { + aiBot = getById(id); + + if (aiBot == null) { + aiBot = getByAlias(id); + } + + } else { + aiBot = getByAlias(id); + } + + return aiBot; + } + + @Override + public Bot getByAlias(String alias) { + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(Bot::getAlias, alias); + return getOne(queryWrapper); + } + + public SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, ChatCheckResult chatCheckResult) { + if (!StringUtils.hasLength(prompt)) { + return ChatSseUtil.sendSystemError(conversationId, "提示词不能为空"); + } + if (!StringUtils.hasLength(conversationId)) { + return ChatSseUtil.sendSystemError(conversationId, "conversationId不能为空"); + } + Bot aiBot = this.getById(botId); + if (aiBot == null) { + return ChatSseUtil.sendSystemError(conversationId, "聊天助手不存在"); + } + if (aiBot.getModelId() == null) { + return ChatSseUtil.sendSystemError(conversationId, "请配置大模型!"); + } + boolean login = StpUtil.isLogin(); + if (!login && !aiBot.isAnonymousEnabled()) { + return ChatSseUtil.sendSystemError(conversationId, "此聊天助手不支持匿名访问"); + } + Map modelOptions = aiBot.getModelOptions(); + Model model = modelService.getModelInstance(aiBot.getModelId()); + if (model == null) { + return ChatSseUtil.sendSystemError(conversationId, "模型不存在,请检查配置"); + } + ChatModel chatModel = model.toChatModel(); + if (chatModel == null) { + return ChatSseUtil.sendSystemError(conversationId, "对话模型获取失败,请检查配置"); + } + + chatCheckResult.setAiBot(aiBot); + chatCheckResult.setModelOptions(modelOptions); + chatCheckResult.setChatModel(chatModel); + chatCheckResult.setConversationIdStr(conversationId); + return null; + } + + @Override + public SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List> messages, + BotServiceImpl.ChatCheckResult chatCheckResult, List attachments) { + Map modelOptions = chatCheckResult.getModelOptions(); + ChatModel chatModel = chatCheckResult.getChatModel(); + final MemoryPrompt memoryPrompt = new MemoryPrompt(); + String systemPrompt = MapUtil.getString(modelOptions, Bot.KEY_SYSTEM_PROMPT); + Integer maxMessageCount = MapUtil.getInteger(modelOptions, Bot.KEY_MAX_MESSAGE_COUNT); + if (maxMessageCount != null) { + memoryPrompt.setMaxAttachedMessageCount(maxMessageCount); + } + if (StringUtils.hasLength(systemPrompt)) { + memoryPrompt.setSystemMessage(SystemMessage.of(systemPrompt)); + } + String attachmentsToString = attachmentsToString(attachments); + if (StringUtils.hasLength(attachmentsToString)) { + prompt = "【用户问题】:\n" + prompt + "\n\n请基于用户上传的附件内容回答用户问题: \n" + "【用户上传的附件内容】:\n" + attachmentsToString ; + } + UserMessage userMessage = new UserMessage(prompt); + userMessage.addTools(buildFunctionList(Maps.of("botId", botId).set("needEnglishName", false))); + ChatOptions chatOptions = getChatOptions(modelOptions); + Boolean enableDeepThinking = MapUtil.getBoolean(modelOptions, Bot.KEY_ENABLE_DEEP_THINKING, false); + chatOptions.setThinkingEnabled(enableDeepThinking); + ChatSseEmitter chatSseEmitter = new ChatSseEmitter(); + SseEmitter emitter = chatSseEmitter.getEmitter(); + if (messages != null && !messages.isEmpty()) { + ChatMemory defaultChatMemory = new DefaultBotMessageMemory(conversationId, chatSseEmitter, messages); + memoryPrompt.setMemory(defaultChatMemory); + } else { + BotMessageMemory memory = new BotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), conversationId, botMessageService); + memoryPrompt.setMemory(memory); + } + memoryPrompt.addMessage(userMessage); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + threadPoolTaskExecutor.execute(() -> { + ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes; + RequestContextHolder.setRequestAttributes(sra, true); + StreamResponseListener streamResponseListener = new ChatStreamListener(conversationId.toString(), chatModel, memoryPrompt, chatSseEmitter, chatOptions); + chatModel.chatStream(memoryPrompt, streamResponseListener, chatOptions); + }); + + return emitter; + } + + /** + * 第三方使用Apikey访问聊天 + * @param botId + * @return + */ + @Override + public SseEmitter startPublicChat(BigInteger botId, String prompt, List messages, BotServiceImpl.ChatCheckResult chatCheckResult) { + Map modelOptions = chatCheckResult.getModelOptions(); + ChatOptions chatOptions = getChatOptions(modelOptions); + ChatModel chatModel = chatCheckResult.getChatModel(); + UserMessage userMessage = new UserMessage(prompt); + userMessage.addTools(buildFunctionList(Maps.of("botId", botId) + .set("needEnglishName", false) + .set("needAccountId", false) + )); + ChatSseEmitter chatSseEmitter = new ChatSseEmitter(); + SseEmitter emitter = chatSseEmitter.getEmitter(); + ChatMemory defaultChatMemory = new PublicBotMessageMemory(chatSseEmitter, messages); + final MemoryPrompt memoryPrompt = new MemoryPrompt(); + memoryPrompt.setMemory(defaultChatMemory); + memoryPrompt.addMessage(userMessage); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes; + threadPoolTaskExecutor.execute(() -> { + RequestContextHolder.setRequestAttributes(sra, true); + StreamResponseListener streamResponseListener = new ChatStreamListener(chatCheckResult.getConversationIdStr(), chatModel, memoryPrompt, chatSseEmitter, chatOptions); + chatModel.chatStream(memoryPrompt, streamResponseListener, chatOptions); + }); + + return emitter; + } + + @Override + public void updateBotLlmId(Bot aiBot) { + Bot bot = getById(aiBot.getId()); + + if (bot == null) { + log.error("修改bot的llmId失败,bot不存在!"); + throw new BusinessException("bot不存在!"); + } + + bot.setModelId(aiBot.getModelId()); + + updateById(bot, false); + + } + + + @Override + public boolean updateById(Bot entity) { + Bot aiBot = getById(entity.getId()); + if (aiBot == null) { + throw new BusinessException("bot 不存在"); + } + + CustomBeanUtils.copyPropertiesIgnoreNull(entity, aiBot); + + if ("".equals(aiBot.getAlias())) { + aiBot.setAlias(null); + } + + + return super.updateById(aiBot, false); + } + + public static ChatOptions getChatOptions(Map llmOptions) { + ChatOptions defaultOptions = new ChatOptions(); + if (llmOptions != null) { + Object topK = llmOptions.get("topK"); + Object maxReplyLength = llmOptions.get("maxReplyLength"); + Object temperature = llmOptions.get("temperature"); + Object topP = llmOptions.get("topP"); + Object thinkingEnabled = llmOptions.get("thinkingEnabled"); + + if (topK != null) { + defaultOptions.setTopK(Integer.parseInt(String.valueOf(topK))); + } + if (maxReplyLength != null) { + defaultOptions.setMaxTokens(Integer.parseInt(String.valueOf(maxReplyLength))); + } + if (temperature != null) { + defaultOptions.setTemperature(Float.parseFloat(String.valueOf(temperature))); + } + if (topP != null) { + defaultOptions.setTopP(Float.parseFloat(String.valueOf(topP))); + } + if (thinkingEnabled != null) { + defaultOptions.setThinkingEnabled(Boolean.parseBoolean(String.valueOf(thinkingEnabled))); + } + + } + return defaultOptions; + } + + private List buildFunctionList(Map buildParams) { + + if (buildParams == null || buildParams.isEmpty()) { + throw new IllegalArgumentException("buildParams is empty"); + } + + List functionList = new ArrayList<>(); + + BigInteger botId = (BigInteger) buildParams.get("botId"); + if (botId == null) { + throw new IllegalArgumentException("botId is empty"); + } + Boolean needEnglishName = (Boolean) buildParams.get("needEnglishName"); + if (needEnglishName == null) { + needEnglishName = false; + } + + QueryWrapper queryWrapper = QueryWrapper.create(); + + // 工作流 function 集合 + queryWrapper.eq(BotWorkflow::getBotId, botId); + List botWorkflows = botWorkflowService.getMapper() + .selectListWithRelationsByQuery(queryWrapper); + if (botWorkflows != null && !botWorkflows.isEmpty()) { + for (BotWorkflow botWorkflow : botWorkflows) { + Tool function = botWorkflow.getWorkflow().toFunction(needEnglishName); + functionList.add(function); + } + } + + // 知识库 function 集合 + queryWrapper = QueryWrapper.create(); + queryWrapper.eq(BotDocumentCollection::getBotId, botId); + List botDocumentCollections = botDocumentCollectionService.getMapper() + .selectListWithRelationsByQuery(queryWrapper); + if (botDocumentCollections != null && !botDocumentCollections.isEmpty()) { + for (BotDocumentCollection botDocumentCollection : botDocumentCollections) { + Tool function = botDocumentCollection.getKnowledge().toFunction(needEnglishName); + functionList.add(function); + } + } + + // 插件 function 集合 + queryWrapper = QueryWrapper.create(); + queryWrapper.select(BOT_PLUGIN.PLUGIN_ITEM_ID).eq(BotPlugin::getBotId, botId); + List pluginToolIds = botPluginService.getMapper() + .selectListWithRelationsByQueryAs(queryWrapper, BigInteger.class); + if (pluginToolIds != null && !pluginToolIds.isEmpty()) { + QueryWrapper queryTool = QueryWrapper.create() + .select(PLUGIN_ITEM.ALL_COLUMNS) + .from(PLUGIN_ITEM) + .where(PLUGIN_ITEM.ID.in(pluginToolIds)); + List pluginItems = pluginItemService.getMapper().selectListWithRelationsByQuery(queryTool); + if (pluginItems != null && !pluginItems.isEmpty()) { + for (PluginItem pluginItem : pluginItems) { + functionList.add(pluginItem.toFunction()); + } + } + } + + // MCP function 集合 + queryWrapper = QueryWrapper.create(); + queryWrapper.eq(BotMcp::getBotId, botId); + List botMcpList = botMcpService.getMapper().selectListWithRelationsByQuery(queryWrapper); + botMcpList.forEach(botMcp -> { + Tool tool = mcpService.toFunction(botMcp); + functionList.add(tool); + }); + + return functionList; + } + + public String attachmentsToString(List fileList) { + StringBuilder messageBuilder = new StringBuilder(); + if (fileList != null && !fileList.isEmpty()) { + File2TextService fileTextService = new File2TextService(); + for (int i = 0; i < fileList.size(); i++) { + String fileUrl = fileList.get(i); + String encodedUrl = UrlEncoderUtil.getEncodedUrl(fileUrl); + String result = fileTextService.extractTextFromSource(new HttpDocumentSource(encodedUrl)); + if (result != null) { + if (i > 0) { + messageBuilder.append("\n\n"); + } + messageBuilder.append("附件").append(i + 1).append(",文件名为:").append(PathGeneratorUtil.getPureFileName(fileUrl)).append(",内容为: \n").append(result); + } + storageService.delete(fileUrl); + } + } + return messageBuilder.toString(); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotWorkflowServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotWorkflowServiceImpl.java new file mode 100644 index 0000000..d59c4dc --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotWorkflowServiceImpl.java @@ -0,0 +1,44 @@ +package tech.easyflow.ai.service.impl; + +import tech.easyflow.ai.entity.BotWorkflow; +import tech.easyflow.ai.mapper.BotWorkflowMapper; +import tech.easyflow.ai.service.BotWorkflowService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import com.mybatisflex.core.query.QueryWrapper; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-28 + */ +@Service +public class BotWorkflowServiceImpl extends ServiceImpl implements BotWorkflowService { + + @Override + public List listByBotId(BigInteger botId) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(BotWorkflow::getBotId,botId); + + return list(queryWrapper); + } + + @Override + public void saveBotAndWorkflowTool(BigInteger botId, BigInteger[] workflowIds) { + this.remove(QueryWrapper.create().eq(BotWorkflow::getBotId, botId)); + List list = new ArrayList<>(workflowIds.length); + for (BigInteger workflowId : workflowIds) { + BotWorkflow botWorkflow = new BotWorkflow(); + botWorkflow.setBotId(botId); + botWorkflow.setWorkflowId(workflowId); + list.add(botWorkflow); + } + this.saveBatch(list); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentChunkServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentChunkServiceImpl.java new file mode 100644 index 0000000..46221db --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentChunkServiceImpl.java @@ -0,0 +1,39 @@ +package tech.easyflow.ai.service.impl; + +import com.easyagents.search.engine.service.DocumentSearcher; +import org.springframework.beans.factory.annotation.Autowired; +import tech.easyflow.ai.config.SearcherFactory; +import tech.easyflow.ai.entity.DocumentChunk; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.mapper.DocumentChunkMapper; +import tech.easyflow.ai.service.DocumentChunkService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.math.BigInteger; + +import static tech.easyflow.ai.entity.DocumentCollection.KEY_SEARCH_ENGINE_TYPE; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class DocumentChunkServiceImpl extends ServiceImpl implements DocumentChunkService { + + @Autowired + private SearcherFactory searcherFactory; + + @Override + public boolean removeChunk(DocumentCollection knowledge, BigInteger chunkId) { + String searchEngineType = (String) knowledge.getOptionsByKey(KEY_SEARCH_ENGINE_TYPE); + DocumentSearcher searcher = searcherFactory.getSearcher(searchEngineType); + // 删除搜索引擎中的数据 + if (searcherFactory.getSearcher(searchEngineType) == null){ + return true; + } + return searcher.deleteDocument(chunkId); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionCategoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionCategoryServiceImpl.java new file mode 100644 index 0000000..d588c19 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionCategoryServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import tech.easyflow.ai.entity.DocumentCollectionCategory; +import tech.easyflow.ai.mapper.DocumentCollectionCategoryMapper; +import tech.easyflow.ai.service.DocumentCollectionCategoryService; +import org.springframework.stereotype.Service; + +/** + * 服务层实现。 + * + * @author 12076 + * @since 2026-01-23 + */ +@Service +public class DocumentCollectionCategoryServiceImpl extends ServiceImpl implements DocumentCollectionCategoryService{ + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionServiceImpl.java new file mode 100644 index 0000000..d3eb948 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentCollectionServiceImpl.java @@ -0,0 +1,228 @@ +package tech.easyflow.ai.service.impl; + + +import com.easyagents.core.document.Document; +import com.easyagents.core.model.rerank.RerankModel; +import com.easyagents.core.store.DocumentStore; +import com.easyagents.core.store.SearchWrapper; +import com.easyagents.core.store.StoreOptions; +import com.easyagents.search.engine.service.DocumentSearcher; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.config.SearcherFactory; +import tech.easyflow.ai.entity.DocumentChunk; +import tech.easyflow.ai.entity.DocumentCollection; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.mapper.DocumentChunkMapper; +import tech.easyflow.ai.mapper.DocumentCollectionMapper; +import tech.easyflow.ai.service.DocumentChunkService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.ai.utils.CustomBeanUtils; +import tech.easyflow.ai.utils.RegexUtils; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import static tech.easyflow.ai.entity.DocumentCollection.*; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class DocumentCollectionServiceImpl extends ServiceImpl implements DocumentCollectionService { + + @Resource + private ModelService llmService; + + @Resource + private DocumentChunkService chunkService; + + @Autowired + private SearcherFactory searcherFactory; + + @Autowired + private DocumentChunkMapper documentChunkMapper; + + + @Override + public List search(BigInteger id, String keyword) { + DocumentCollection documentCollection = getById(id); + if (documentCollection == null) { + throw new BusinessException("知识库不存在"); + } + + DocumentStore documentStore = documentCollection.toDocumentStore(); + if (documentStore == null) { + throw new BusinessException("知识库没有配置向量库"); + } + + Model model = llmService.getModelInstance(documentCollection.getVectorEmbedModelId()); + if (model == null) { + throw new BusinessException("知识库没有配置向量模型"); + } + + documentStore.setEmbeddingModel(model.toEmbeddingModel()); + // 最大召回知识条数 + Integer docRecallMaxNum = (Integer) documentCollection.getOptionsByKey(KEY_DOC_RECALL_MAX_NUM); + // 最低相似度 + float minSimilarity = (float) documentCollection.getOptionsByKey(KEY_SIMILARITY_THRESHOLD); + SearchWrapper wrapper = new SearchWrapper(); + wrapper.setMaxResults(docRecallMaxNum); + wrapper.setMinScore((double) minSimilarity); + wrapper.setText(keyword); + + StoreOptions options = StoreOptions.ofCollectionName(documentCollection.getVectorStoreCollection()); + options.setIndexName(documentCollection.getVectorStoreCollection()); + + // 并行查询:向量库 + 搜索引擎 + CompletableFuture> vectorFuture = CompletableFuture.supplyAsync(() -> + documentStore.search(wrapper, options) + ); + + CompletableFuture> searcherFuture = CompletableFuture.supplyAsync(() -> { + DocumentSearcher searcher = searcherFactory.getSearcher((String) documentCollection.getOptionsByKey(KEY_SEARCH_ENGINE_TYPE)); + if (searcher == null || !documentCollection.isSearchEngineEnabled()) { + return Collections.emptyList(); + } + List documents = searcher.searchDocuments(keyword); + return documents == null ? Collections.emptyList() : documents; + }); + + // 合并两个查询结果 + CompletableFuture> combinedFuture = vectorFuture.thenCombine( + searcherFuture, + (vectorDocs, searcherDocs) -> { + Map uniqueDocs = new HashMap<>(); + vectorDocs.forEach(doc -> uniqueDocs.putIfAbsent(doc.getId().toString(), doc)); + searcherDocs.forEach(doc -> uniqueDocs.putIfAbsent(doc.getId().toString(), doc)); + return uniqueDocs; + } + ); + + try { + Map uniqueDocs = combinedFuture.get(); // 阻塞等待所有查询完成 + List searchDocuments = new ArrayList<>(uniqueDocs.values()); + searchDocuments.sort((doc1, doc2) -> Double.compare(doc2.getScore(), doc1.getScore())); + searchDocuments.forEach(item ->{ + DocumentChunk documentChunk = documentChunkMapper.selectOneById((Serializable) item.getId()); + if (documentChunk != null && !StringUtil.noText(documentChunk.getContent())){ + item.setContent(documentChunk.getContent()); + } + + }); + if (searchDocuments.isEmpty()) { + return Collections.emptyList(); + } + if (documentCollection.getRerankModelId() == null) { + return formatDocuments(searchDocuments, minSimilarity, docRecallMaxNum); + } + + Model modelRerank = llmService.getModelInstance(documentCollection.getRerankModelId()); + + RerankModel rerankModel = modelRerank.toRerankModel(); + if (rerankModel == null) { + return formatDocuments(searchDocuments, minSimilarity, docRecallMaxNum); + } + + searchDocuments.forEach(item -> item.setScore(null)); + List rerankDocuments = rerankModel.rerank(keyword, searchDocuments); + return formatDocuments(rerankDocuments, minSimilarity, docRecallMaxNum); + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + @Override + public DocumentCollection getDetail(String idOrAlias) { + + DocumentCollection knowledge = null; + + if (idOrAlias.matches(RegexUtils.ALL_NUMBER)) { + knowledge = getById(idOrAlias); + if (knowledge == null) { + knowledge = getByAlias(idOrAlias); + } + } + + if (knowledge == null) { + knowledge = getByAlias(idOrAlias); + } + + return knowledge; + } + + @Override + public DocumentCollection getByAlias(String idOrAlias) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(DocumentCollection::getAlias, idOrAlias); + + return getOne(queryWrapper); + + } + + + @Override + public boolean updateById(DocumentCollection entity) { + DocumentCollection documentCollection = getById(entity.getId()); + if (documentCollection == null) { + throw new BusinessException("bot 不存在"); + } + + CustomBeanUtils.copyPropertiesIgnoreNull(entity, documentCollection); + + if ("".equals(documentCollection.getAlias())) { + documentCollection.setAlias(null); + } + + + return super.updateById(documentCollection, false); + } + + /** + * 格式化文档列表 + * + * @param documents 文档列表 + * @param minSimilarity 最小相似度 + * @return 格式化后的文档列表 + */ + public List formatDocuments(List documents, float minSimilarity, int maxResults) { + return documents.stream() + // 过滤掉分数为空 或 分数低于最小值的文档 + .filter(document -> { + Double score = document.getScore(); + return score != null && score >= minSimilarity; + }) + // 格式化保留四位小数 + .map(document -> { + Double score = document.getScore(); + BigDecimal bd = new BigDecimal(score.toString()); + bd = bd.setScale(4, RoundingMode.HALF_UP); + Double roundedScore = bd.doubleValue(); + document.setScore(roundedScore); + return document; + }) + // 按score降序排序(分数最高的排前面) + .sorted(Comparator.comparing(Document::getScore, Comparator.reverseOrder())) + // 限制只保留前maxResults条 + .limit(maxResults) + .collect(Collectors.toList()); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentHistoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentHistoryServiceImpl.java new file mode 100644 index 0000000..80cb174 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentHistoryServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import tech.easyflow.ai.entity.DocumentHistory; +import tech.easyflow.ai.mapper.DocumentHistoryMapper; +import tech.easyflow.ai.service.DocumentHistoryService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class DocumentHistoryServiceImpl extends ServiceImpl implements DocumentHistoryService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentServiceImpl.java new file mode 100644 index 0000000..d9b8a4b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/DocumentServiceImpl.java @@ -0,0 +1,364 @@ +package tech.easyflow.ai.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import com.easyagents.core.document.DocumentSplitter; +import com.easyagents.core.document.splitter.MarkdownHeaderSplitter; +import com.easyagents.core.document.splitter.RegexDocumentSplitter; +import com.easyagents.core.document.splitter.SimpleDocumentSplitter; +import com.easyagents.core.document.splitter.SimpleTokenizeSplitter; +import com.easyagents.core.file2text.File2TextUtil; +import com.easyagents.core.model.embedding.EmbeddingModel; +import com.easyagents.core.model.embedding.EmbeddingOptions; +import com.easyagents.core.store.DocumentStore; +import com.easyagents.core.store.StoreOptions; +import com.easyagents.core.store.StoreResult; +import com.easyagents.search.engine.service.DocumentSearcher; +import com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryMethods; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.easyflow.ai.config.SearcherFactory; +import tech.easyflow.ai.entity.*; + +import static tech.easyflow.ai.entity.DocumentCollection.KEY_CAN_UPDATE_EMBEDDING_MODEL; +import static tech.easyflow.ai.entity.DocumentCollection.KEY_SEARCH_ENGINE_TYPE; +import static tech.easyflow.ai.entity.table.DocumentChunkTableDef.DOCUMENT_CHUNK; +import static tech.easyflow.ai.entity.table.DocumentTableDef.DOCUMENT; +import tech.easyflow.ai.mapper.DocumentChunkMapper; +import tech.easyflow.ai.mapper.DocumentMapper; +import tech.easyflow.ai.service.DocumentChunkService; +import tech.easyflow.ai.service.DocumentService; +import tech.easyflow.ai.service.DocumentCollectionService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.ai.rag.ExcelDocumentSplitter; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.filestorage.FileStorageService; +import tech.easyflow.common.util.FileUtil; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service("AiService") +public class DocumentServiceImpl extends ServiceImpl implements DocumentService { + protected Logger Log = LoggerFactory.getLogger(DocumentServiceImpl.class); + + @Resource + private DocumentMapper documentMapper; + + @Resource + private DocumentChunkMapper documentChunkMapper; + + @Resource + private DocumentCollectionService knowledgeService; + + @Resource + private ModelService modelService; + + @Resource(name = "default") + FileStorageService storageService; + + @Resource + private DocumentChunkService documentChunkService; + + @Autowired + private SearcherFactory searcherFactory; + + @Override + public Page getDocumentList(String knowledgeId, int pageSize, int pageNum, String fileName) { + QueryWrapper queryWrapper=QueryWrapper.create() + .select( + DOCUMENT.ALL_COLUMNS, + QueryMethods.count(DOCUMENT_CHUNK.DOCUMENT_ID).as("chunk_count") + + ) + .from(Document.class) + .leftJoin(DocumentChunk.class).on(DOCUMENT.ID.eq(DOCUMENT_CHUNK.DOCUMENT_ID)) + .where(DOCUMENT.COLLECTION_ID.eq(knowledgeId)) + .orderBy(DOCUMENT.ID, false) + ; + if (fileName != null && !fileName.trim().isEmpty()) { + queryWrapper.and(DOCUMENT.TITLE.like(fileName)); + } + // 分组 + queryWrapper.groupBy(DOCUMENT.ID); + Page documentVoPage = documentMapper.paginateAs(pageNum, pageSize, queryWrapper, Document.class); + return documentVoPage; + } + + /** + * 根据文档id删除文件 + * + * @param id 文档id + * @return + */ + @Override + public boolean removeDoc(String id) { + // 查询该文档对应哪些分割的字段,先删除 + QueryWrapper queryWrapperDocument = QueryWrapper.create().eq(Document::getId, id); + Document oneByQuery = documentMapper.selectOneByQuery(queryWrapperDocument); + DocumentCollection knowledge = knowledgeService.getById(oneByQuery.getCollectionId()); + if (knowledge == null) { + return false; + } + + // 存储到知识库 + DocumentStore documentStore = knowledge.toDocumentStore(); + if (documentStore == null) { + return false; + } + + Model model = modelService.getById(knowledge.getVectorEmbedModelId()); + if (model == null) { + return false; + } + // 设置向量模型 + StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection()); + EmbeddingOptions embeddingOptions = new EmbeddingOptions(); + embeddingOptions.setModel(model.getModelName()); + options.setEmbeddingOptions(embeddingOptions); + options.setCollectionName(knowledge.getVectorStoreCollection()); + // 查询文本分割表tb_document_chunk中对应的有哪些数据,找出来删除 + QueryWrapper queryWrapper = QueryWrapper.create() + .select(DOCUMENT_CHUNK.ID).eq(DocumentChunk::getDocumentId, id); + List chunkIds = documentChunkMapper.selectListByQueryAs(queryWrapper, BigInteger.class); + documentStore.delete(chunkIds, options); + // 删除搜索引擎中的数据 + if (searcherFactory.getSearcher((String) knowledge.getOptionsByKey(KEY_SEARCH_ENGINE_TYPE)) != null) { + DocumentSearcher searcher = searcherFactory.getSearcher((String) knowledge.getOptionsByKey(KEY_SEARCH_ENGINE_TYPE)); + chunkIds.forEach(searcher::deleteDocument); + } + int ck = documentChunkMapper.deleteByQuery(QueryWrapper.create().eq(DocumentChunk::getDocumentId, id)); + if (ck < 0) { + return false; + } + // 再删除指定路径下的文件 + Document document = documentMapper.selectOneByQuery(queryWrapperDocument); + storageService.delete(document.getDocumentPath()); + return true; + } + + + @Override + @Transactional + public Result textSplit(DocumentCollectionSplitParams documentCollectionSplitParams) { + try { + String filePath = documentCollectionSplitParams.getFilePath(); + String fileOriginName = documentCollectionSplitParams.getFileOriginName(); + InputStream inputStream = storageService.readStream(filePath); + Document aiDocument = new Document(); + List previewList = new ArrayList<>(); + DocumentSplitter documentSplitter = getDocumentSplitter(documentCollectionSplitParams); + String content = File2TextUtil.readFromStream(inputStream, fileOriginName, null); + com.easyagents.core.document.Document document = new com.easyagents.core.document.Document(content);; + inputStream.close(); + List documents = documentSplitter.split(document); + FlexIDKeyGenerator flexIDKeyGenerator = new FlexIDKeyGenerator(); + int sort = 1; + for (com.easyagents.core.document.Document value : documents) { + DocumentChunk chunk = new DocumentChunk(); + chunk.setId(new BigInteger(String.valueOf(flexIDKeyGenerator.generate(chunk, null)))); + chunk.setContent(value.getContent()); + chunk.setSorting(sort); + sort++; + previewList.add(chunk); + } + String fileTypeByExtension = FileUtil.getFileTypeByExtension(filePath); + aiDocument.setDocumentType(fileTypeByExtension); + aiDocument.setCollectionId(documentCollectionSplitParams.getKnowledgeId()); + aiDocument.setDocumentPath(filePath); + aiDocument.setCreated(new Date()); + aiDocument.setModifiedBy(BigInteger.valueOf(StpUtil.getLoginIdAsLong())); + aiDocument.setModified(new Date()); + aiDocument.setContent(document.getContent()); + aiDocument.setChunkSize(documentCollectionSplitParams.getChunkSize()); + aiDocument.setOverlapSize(documentCollectionSplitParams.getOverlapSize()); + aiDocument.setTitle(fileOriginName); + Map res = new HashMap<>(); + + List documentChunks = null; + String operation = documentCollectionSplitParams.getOperation(); + Integer pageNumber = documentCollectionSplitParams.getPageNumber(); + Integer pageSize = documentCollectionSplitParams.getPageSize(); + // 如果是预览拆分,则返回指定页的数据 + if ("textSplit".equals(operation)){ + int startIndex = (pageNumber - 1) * pageSize; + int endIndex = Math.min(startIndex + pageSize, previewList.size()); + + if (startIndex >= previewList.size()) { + documentChunks = new ArrayList<>(); + } else { + documentChunks = new ArrayList<>(previewList.subList(startIndex, endIndex)); + } + + res.put("total", previewList.size()); + // 保存文件到知识库 + } else if ("saveText".equals(operation)){ + documentChunks = previewList; + return this.saveTextResult(documentChunks, aiDocument); + } + + res.put("previewData", documentChunks); + res.put("aiDocumentData", aiDocument); + // 返回分割效果给用户 + return Result.ok(res); + } catch (IOException e) { + Log.error(e.toString(), e); + return Result.fail(e.getMessage()); + + } + } + + @Override + public Result saveTextResult(List documentChunks, Document document) { + Boolean result = storeDocument(document, documentChunks); + if (result) { + this.getMapper().insert(document); + AtomicInteger sort = new AtomicInteger(1); + documentChunks.forEach(item -> { + item.setDocumentCollectionId(document.getCollectionId()); + item.setSorting(sort.get()); + item.setDocumentId(document.getId()); + sort.getAndIncrement(); + documentChunkService.save(item); + }); + return Result.ok(); + } + return Result.fail(1, "保存失败"); + } + + protected Boolean storeDocument(Document entity, List documentChunks) { + DocumentCollection knowledge = knowledgeService.getById(entity.getCollectionId()); + if (knowledge == null) { + throw new BusinessException("知识库不存在"); + } + DocumentStore documentStore = null; + try { + documentStore = knowledge.toDocumentStore(); + } catch (Exception e) { + Log.error(e.getMessage()); + throw new BusinessException("向量数据库配置错误"); + } + + if (documentStore == null) { + throw new BusinessException("向量数据库配置错误"); + } + // 设置向量模型 + Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId()); + if (model == null) { + throw new BusinessException("该知识库未配置大模型"); + } + // 设置向量模型 + EmbeddingModel embeddingModel = model.toEmbeddingModel(); + documentStore.setEmbeddingModel(embeddingModel); + + StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection()); + EmbeddingOptions embeddingOptions = new EmbeddingOptions(); + embeddingOptions.setModel(model.getModelName()); + embeddingOptions.setDimensions(knowledge.getDimensionOfVectorModel()); + options.setEmbeddingOptions(embeddingOptions); + options.setIndexName(options.getCollectionName()); + List documents = new ArrayList<>(); + documentChunks.forEach(item -> { + com.easyagents.core.document.Document document = new com.easyagents.core.document.Document(); + document.setId(item.getId()); + document.setContent(item.getContent()); + documents.add(document); + } + ); + StoreResult result = null; + try { + result = documentStore.store(documents, options); + } catch (Exception e) { + Log.error(e.getMessage()); + throw new BusinessException("向量过程中发生错误,错误信息为:" + e.getMessage()); + } + if (result == null || !result.isSuccess()) { + Log.error("DocumentStore.store failed: " + result); + throw new BusinessException("DocumentStore.store failed"); + } + + if (knowledge.isSearchEngineEnabled()) { + // 获取搜索引擎 + DocumentSearcher searcher = searcherFactory.getSearcher((String) knowledge.getOptionsByKey(KEY_SEARCH_ENGINE_TYPE)); + // 添加到搜索引擎 + documents.forEach(searcher::addDocument); + } + + DocumentCollection documentCollection = new DocumentCollection(); + documentCollection.setId(entity.getCollectionId()); + Map knowledgeOptions = knowledge.getOptions(); + knowledgeOptions.put(KEY_CAN_UPDATE_EMBEDDING_MODEL, false); + documentCollection.setOptions(knowledgeOptions); + knowledgeService.updateById(documentCollection); + if (knowledge.getDimensionOfVectorModel() == null) { + int dimension = Model.getEmbeddingDimension(embeddingModel); + knowledge.setDimensionOfVectorModel(dimension); + knowledgeService.updateById(knowledge); + } + return true; + } + + public DocumentSplitter getDocumentSplitter(DocumentCollectionSplitParams params) { + String splitterName = params.getSplitterName(); + int chunkSize = params.getChunkSize(); + int overlapSize = params.getOverlapSize(); + String regex = params.getRegex(); + int excelRows = params.getRowsPerChunk(); + if (StringUtil.noText(splitterName)) { + return null; + } + switch (splitterName) { + case "SimpleDocumentSplitter": + return new SimpleDocumentSplitter(chunkSize, overlapSize); + case "RegexDocumentSplitter": + return new RegexDocumentSplitter(regex); + case "SimpleTokenizeSplitter": + if (overlapSize == 0) { + return new SimpleTokenizeSplitter(chunkSize); + } else { + return new SimpleTokenizeSplitter(chunkSize, overlapSize); + } + case "ExcelDocumentSplitter": + return new ExcelDocumentSplitter(excelRows); + case "MarkdownHeaderSplitter": + return new MarkdownHeaderSplitter(params.getMdSplitterLevel()); + default: + return null; + } + + } + + public static String getFileExtension(String filePath) { + int lastDotIndex = filePath.lastIndexOf('.'); + if (lastDotIndex != -1) { + return filePath.substring(lastDotIndex + 1); + } + return null; + } + + public static String getFileName(String filePath) { + int lastDotIndex = filePath.lastIndexOf('.'); + if (lastDotIndex != -1) { + return filePath.substring(lastDotIndex + 1); + } + return null; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/McpServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/McpServiceImpl.java new file mode 100644 index 0000000..2991c0c --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/McpServiceImpl.java @@ -0,0 +1,245 @@ +package tech.easyflow.ai.service.impl; + +import com.easyagents.core.model.chat.tool.Parameter; +import com.easyagents.core.model.chat.tool.Tool; +import com.easyagents.mcp.client.McpClientManager; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.spec.McpSchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.easyagents.tool.McpTool; +import tech.easyflow.ai.entity.BotMcp; +import tech.easyflow.ai.entity.Mcp; +import tech.easyflow.ai.mapper.McpMapper; +import tech.easyflow.ai.service.McpService; +import tech.easyflow.ai.utils.CommonFiledUtil; +import tech.easyflow.common.constant.enums.EnumRes; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; + +import java.io.Serializable; +import java.util.*; + +/** + * 服务层实现。 + * + * @author wangGangQiang + * @since 2026-01-04 + */ +@Service +public class McpServiceImpl extends ServiceImpl implements McpService { + private final McpClientManager mcpClientManager = McpClientManager.getInstance(); + protected Logger Log = LoggerFactory.getLogger(DocumentServiceImpl.class); + + @Override + public Result saveMcp(Mcp entity) { + Result validateResult = validateMcpConfig(entity); + if (!(EnumRes.SUCCESS.getCode() == validateResult.getErrorCode())) { + return validateResult; + } + String serverName = getFirstMcpServerName(entity.getConfigJson()); + if (!StringUtil.hasText(serverName)) { + return Result.fail("未找到mcp服务名称", serverName); + } + try { + mcpClientManager.registerFromJson(entity.getConfigJson()); + } catch (Exception e) { + Log.error("MCP服务名称:{} 注册失败", serverName, e); + } + if (entity.getStatus()) { + try { + getMcpClient(entity, mcpClientManager); + } catch (Exception e) { + Log.error("MCP服务名称:{} 启动失败", serverName, e); + return Result.fail("MCP服务名称:" + serverName + " 启动失败", serverName); + } + } + + LoginAccount loginAccount = SaTokenUtil.getLoginAccount(); + CommonFiledUtil.commonFiled(entity, loginAccount.getId(), loginAccount.getTenantId(), loginAccount.getDeptId()); + this.save(entity); + return Result.ok(); + } + + @Override + public Result updateMcp(Mcp entity) { + Result validateResult = validateMcpConfig(entity); + if (!(EnumRes.SUCCESS.getCode() == validateResult.getErrorCode())) { + return validateResult; + } + String serverName = getFirstMcpServerName(entity.getConfigJson()); + if (!StringUtil.hasText(serverName)) { + return Result.fail("未找到mcp服务名称", serverName); + } + if (entity.getStatus()) { + try { + mcpClientManager.registerFromJson(entity.getConfigJson()); + } catch (Exception e) { + Log.error("MCP服务名称:{} 注册失败", serverName, e); + } + try { + getMcpClient(entity, mcpClientManager); + } catch (Exception e) { + Log.error("MCP服务名称:{} 启动失败", serverName, e); + return Result.fail("MCP服务名称:" + serverName + " 启动失败," + "请尝试重新启动!"); + } + + } else { + entity.setClientOnline(false); + if (StringUtil.hasText(serverName)) { + if (mcpClientManager.isClientOnline(serverName)) { + mcpClientManager.getMcpClient(serverName).close(); + } + } + } + entity.setModified(new Date()); + this.updateById(entity); + return Result.ok(); + } + + @Override + public void removeMcp(Serializable id) { + Mcp mcp = this.getById(id); + if (mcp != null && mcp.getStatus()) { + McpSyncClient mcpClient = getMcpClient(mcp, mcpClientManager); + mcpClient.close(); + } + this.removeById(id); + } + + @Override + public Result> pageMcp(Result> page) { + List records = page.getData().getRecords(); + records.forEach(mcp -> { + boolean clientOnline = mcpClientManager.isClientOnline(getFirstMcpServerName(mcp.getConfigJson())); + mcp.setClientOnline(clientOnline); + } + ); + page.getData().setRecords(records); + return page; + } + + @Override + public Mcp getMcpTools(String id) { + Mcp mcp = this.getById(id); + if (mcp != null && mcp.getStatus()) { + McpSyncClient mcpClient = getMcpClient(mcp, mcpClientManager); + List tools = null; + if (mcpClient != null) { + tools = mcpClient.listTools().tools(); + } + mcp.setTools(tools); + } + return mcp; + } + + public static McpSyncClient getMcpClient(Mcp mcp, McpClientManager mcpClientManager) { + String configJson = mcp.getConfigJson(); + String mcpServerName = getFirstMcpServerName(configJson); + if (StringUtil.hasText(mcpServerName)) { + return mcpClientManager.getMcpClient(mcpServerName); + } + return null; + } + + @Override + public Tool toFunction(BotMcp botMcp) { + Mcp mcpInfo = this.getById(botMcp.getMcpId()); + String configJson = mcpInfo.getConfigJson(); + String mcpServerName = getFirstMcpServerName(configJson); + if (StringUtil.hasText(mcpServerName)) { + McpSyncClient mcpClient = mcpClientManager.getMcpClient(mcpServerName); + List tools = mcpClient.listTools().tools(); + for (McpSchema.Tool tool : tools) { + if (tool.name().equals(botMcp.getMcpToolName())) { + Map properties = tool.inputSchema().properties(); + List required = tool.inputSchema().required(); + McpTool mcpTool = new McpTool(); + mcpTool.setName(tool.name()); + mcpTool.setDescription(tool.description()); + List paramList = new ArrayList<>(); + Set keySet = properties.keySet(); + keySet.forEach(key -> { + Parameter parameter = new Parameter(); + parameter.setName(key); + LinkedHashMap params = (LinkedHashMap) properties.get(key); + Set paramsKeySet = params.keySet(); + paramsKeySet.forEach(paramsKey -> { + if (paramsKey.equals("type")) { + parameter.setType((String) params.get(paramsKey)); + } else if (paramsKey.equals("description")) { + parameter.setDescription((String) params.get(paramsKey)); + } + }); + paramList.add(parameter); + Parameter[] parametersArr = paramList.toArray(new Parameter[properties.size()]); + mcpTool.setParameters(parametersArr); + }); + mcpTool.setMcpId(mcpInfo.getId()); + return mcpTool; + } + } + } + return null; + } + + public static Set getMcpServerNames(String mcpJson) { + JSONObject rootJson = JSON.parseObject(mcpJson); + + JSONObject mcpServersJson = rootJson.getJSONObject("mcpServers"); + if (mcpServersJson == null) { + return Set.of(); + } + + // 提取 mcpServers 的所有键 → 即为 MCP 服务名称(如 everything) + return mcpServersJson.keySet(); + } + + public static String getFirstMcpServerName(String mcpJson) { + Set serverNames = getMcpServerNames(mcpJson); + Optional firstServerName = serverNames.stream().findFirst(); + return firstServerName.orElse(null); + } + + @Override + public Page pageTools(Page page) { + page.getRecords().forEach(mcp -> { + // mcp 未启用,不查询工具 + if (!mcp.getStatus()) { + return; + } + String configJson = mcp.getConfigJson(); + String serverName = getFirstMcpServerName(configJson); + if (StringUtil.hasText(serverName)) { + mcpClientManager.registerFromJson(configJson); + try { + McpSyncClient mcpClient = mcpClientManager.getMcpClient(serverName); + List tools = mcpClient.listTools().tools(); + mcp.setTools(tools); + } catch (Exception e) { + Log.error("MCP服务名称:{} 启动失败", serverName, e); + } + + } else { + throw new BusinessException("MCP 配置 JSON 中没有找到任何 MCP 服务名称"); + } + }); + return page; + } + + private Result validateMcpConfig(Mcp entity) { + if (entity == null || !StringUtil.hasText(entity.getConfigJson())) { + Log.error("MCP 配置不能为空"); + return Result.fail("MCP 配置 JSON 不能为空"); + } + return Result.ok(); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelProviderServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelProviderServiceImpl.java new file mode 100644 index 0000000..8e8748b --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelProviderServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.ModelProvider; +import tech.easyflow.ai.mapper.ModelProviderMapper; +import tech.easyflow.ai.service.ModelProviderService; + +/** + * 服务层实现。 + * + * @author 12076 + * @since 2025-12-16 + */ +@Service +public class ModelProviderServiceImpl extends ServiceImpl implements ModelProviderService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelServiceImpl.java new file mode 100644 index 0000000..6f932ed --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ModelServiceImpl.java @@ -0,0 +1,224 @@ + +package tech.easyflow.ai.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.easyagents.core.document.Document; +import com.easyagents.core.model.chat.ChatModel; +import com.easyagents.core.model.chat.ChatOptions; +import com.easyagents.core.model.embedding.EmbeddingModel; +import com.easyagents.core.model.rerank.RerankModel; +import com.easyagents.core.store.VectorData; +import com.alicp.jetcache.Cache; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import tech.easyflow.ai.entity.Model; +import tech.easyflow.ai.entity.ModelProvider; +import tech.easyflow.ai.mapper.ModelMapper; +import tech.easyflow.ai.service.ModelProviderService; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class ModelServiceImpl extends ServiceImpl implements ModelService { + + @Autowired + ModelMapper modelMapper; + + @Autowired + ModelProviderService modelProviderService; + + @Resource + private Cache cache; + + + @Override + public boolean addAiLlm(Model entity) { + int insert = modelMapper.insert(entity); + if (insert <= 0) { + return false; + } + return true; + } + + private static final Logger log = LoggerFactory.getLogger(ModelServiceImpl.class); + + @Override + public Map verifyModelConfig(Model model) { + String modelType = model.getModelType(); + Map resMap = new HashMap<>(); + // 走聊天验证逻辑 + if (Model.MODEL_TYPES[0].equals(modelType)) { + verifyChatLlm(model); + return null; + } + // 走向量化验证逻辑 + if (Model.MODEL_TYPES[1].equals(modelType)) { + int dimension = verifyEmbedLlm(model); + resMap.put("dimension", dimension); + return resMap; + } + // 走重排验证逻辑 + if (Model.MODEL_TYPES[2].equals(modelType)) { + verifyRerankLlm(model); + return null; + + } + + // 以上不满足,视为验证失败 + throw new BusinessException("校验失败!"); + + } + + @Override + public Map>> getList(Model entity) { + Map>> result = new HashMap<>(); + + QueryWrapper queryWrapper = new QueryWrapper() + .eq(Model::getProviderId, entity.getProviderId()); + queryWrapper.eq(Model::getWithUsed, entity.getWithUsed()); + List totalList = modelMapper.selectListWithRelationsByQuery(queryWrapper); + for (String modelType : Model.MODEL_TYPES) { + Map> groupMap = groupLlmByGroupName(totalList, modelType); + if (!CollectionUtils.isEmpty(groupMap)) { + result.put(modelType, groupMap); + } + } + + return result; + } + + private Map> groupLlmByGroupName(List totalList, String targetModelType) { + if (CollectionUtils.isEmpty(totalList)) { + return Collections.emptyMap(); + } + + return totalList.stream() + .filter(aiLlm -> targetModelType.equals(aiLlm.getModelType()) + && aiLlm.getGroupName() != null) + .collect(Collectors.groupingBy(Model::getGroupName)); + } + + + private void verifyRerankLlm(Model model) { + RerankModel rerankModel = model.toRerankModel(); + List documents = new ArrayList<>(); + documents.add(Document.of("Paris is the capital of France.")); + documents.add(Document.of("London is the capital of England.")); + documents.add(Document.of("Tokyo is the capital of Japan.")); + documents.add(Document.of("Beijing is the capital of China.")); + documents.add(Document.of("Washington, D.C. is the capital of the United States.")); + documents.add(Document.of("Moscow is the capital of Russia.")); + try { + List rerank = rerankModel.rerank("What is the capital of France?", documents); + if (rerank == null || rerank.isEmpty()) { + throw new BusinessException("校验未通过,请前往后端日志查看详情!"); + } + } catch (Exception e) { + log.error("校验失败:{}", e.getMessage()); + throw new BusinessException(e.getMessage()); + } + } + + private int verifyEmbedLlm(Model model) { + try { + EmbeddingModel embeddingModel = model.toEmbeddingModel(); + VectorData vectorData = embeddingModel.embed("这是一条校验模型配置的文本"); + if (vectorData.getVector() == null) { + throw new BusinessException("校验未通过,请前往后端日志查看详情!"); + } + log.info("取到向量数据,校验结果通过"); + return vectorData.getVector().length; + } catch (Exception e) { + log.error("模型配置校验失败:{}", e.getMessage()); + throw new BusinessException(e.getMessage()); + } + } + + private void verifyChatLlm(Model llm) { + + ChatModel chatModel = llm.toChatModel(); + if (chatModel == null) { + throw new BusinessException("chatModel为空"); + } + try { + ChatOptions options=new ChatOptions(); + options.setThinkingEnabled(false); + String response = chatModel.chat("我在对模型配置进行校验,你收到这条消息无需做任何思考,直接回复一个“你好”即可!",options); + if (response == null) { + throw new BusinessException("校验未通过,请前往后端日志查看详情!"); + } + log.info("校验结果:{}", response); + } catch (Exception e) { + log.error("校验失败:{}", e.getMessage()); + throw new BusinessException(e.getMessage()); + } + + } + + @Override + public void removeByEntity(Model entity) { + QueryWrapper queryWrapper = QueryWrapper.create().eq(Model::getProviderId, entity.getProviderId()).eq(Model::getGroupName, entity.getGroupName()); + modelMapper.deleteByQuery(queryWrapper); + } + + @Override + public Model getModelInstance(BigInteger modelId) { + if (modelId == null) { + throw new BusinessException("模型ID不能为空"); + } + Model model = modelMapper.selectOneWithRelationsById(modelId); + if (model == null) { + return null; + } + ModelProvider modelProvider = model.getModelProvider(); + model.setModelProvider(modelProvider); + if (StrUtil.isBlank(model.getApiKey())) { + model.setApiKey(modelProvider.getApiKey()); + } + if (StrUtil.isBlank(model.getEndpoint())) { + model.setEndpoint(modelProvider.getEndpoint()); + } + + // 请求路径为空,从modelProvider中获取 + if (StrUtil.isBlank(model.getRequestPath())) { + // 模型类型为chatModel + if (model.getModelType().equals(Model.MODEL_TYPES[0])) { + model.setRequestPath(modelProvider.getChatPath()); + // 模型类型为embeddingModel + } else if (model.getModelType().equals(Model.MODEL_TYPES[1])) { + model.setRequestPath(modelProvider.getEmbedPath()); + // 模型类型为rerankModel + } else if (model.getModelType().equals(Model.MODEL_TYPES[2])) { + model.setRequestPath(modelProvider.getRerankPath()); + } + } + + return model; + } + + @Override + public void updateByEntity(Model entity) { + QueryWrapper queryWrapper = QueryWrapper.create().eq(Model::getProviderId, entity.getProviderId()) + .eq(Model::getGroupName, entity.getGroupName()); + Model model = new Model(); + model.setWithUsed(entity.getWithUsed()); + modelMapper.updateByQuery(model, queryWrapper); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryMappingServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryMappingServiceImpl.java new file mode 100644 index 0000000..5c949ef --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryMappingServiceImpl.java @@ -0,0 +1,93 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import tech.easyflow.ai.entity.PluginCategory; +import tech.easyflow.ai.entity.PluginCategoryMapping; +import tech.easyflow.ai.mapper.PluginCategoryMapper; +import tech.easyflow.ai.mapper.PluginCategoryMappingMapper; +import tech.easyflow.ai.service.PluginCategoryMappingService; +import org.springframework.stereotype.Service; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * 服务层实现。 + * + * @author Administrator + * @since 2025-05-21 + */ +@Service +public class PluginCategoryMappingServiceImpl extends ServiceImpl implements PluginCategoryMappingService { + + @Resource + private PluginCategoryMappingMapper relationMapper; + + @Resource + private PluginCategoryMapper pluginCategoryMapper; + + @Override + public boolean updateRelation(BigInteger pluginId, ArrayList categoryIds) { + if (categoryIds == null){ + QueryWrapper queryWrapper = QueryWrapper.create().select("*") + .from("tb_plugin_category_mapping") + .where("plugin_id = ?", pluginId); + int delete = relationMapper.deleteByQuery(queryWrapper); + if (delete <= 0){ + throw new BusinessException("删除失败"); + } + return true; + } + for (BigInteger categoryId : categoryIds) { + QueryWrapper queryWrapper = QueryWrapper.create().select("*") + .from("tb_plugin_category_mapping") + .where("plugin_id = ?", pluginId) + .where("category_id = ?", categoryId); + PluginCategoryMapping selectedOneByQuery = relationMapper.selectOneByQuery(queryWrapper); + PluginCategoryMapping pluginCategoryMapping = new PluginCategoryMapping(); + pluginCategoryMapping.setCategoryId(categoryId); + pluginCategoryMapping.setPluginId(pluginId); + if (selectedOneByQuery == null) { + int insert = relationMapper.insert(pluginCategoryMapping); + if (insert <= 0) { + throw new BusinessException("新增失败"); + } + } else { + QueryWrapper queryWrapperUpdate = QueryWrapper.create().select("*") + .from("tb_plugin_category_mapping") + .where("plugin_id = ?", pluginId); + PluginCategoryMapping selectedOne = relationMapper.selectOneByQuery(queryWrapper); + if (selectedOne != null){ + continue; + } + int update = relationMapper.updateByQuery(pluginCategoryMapping, queryWrapperUpdate); + if (update <= 0){ + throw new BusinessException("更新失败"); + } + } + + } + return true; + } + + @Override + public List getPluginCategories(BigInteger pluginId) { + QueryWrapper categoryQueryWrapper = QueryWrapper.create().select("category_id") + .from("tb_plugin_category_mapping") + .where("plugin_id = ?", pluginId); + List categoryIdList = relationMapper.selectListByQueryAs(categoryQueryWrapper, BigInteger.class); + List pluginCategories = new ArrayList(); + if (categoryIdList.isEmpty()){ + return pluginCategories; + } + QueryWrapper categoryQuery = QueryWrapper.create().select("id, name") + .from("tb_plugin_category") + .in("id", categoryIdList); + pluginCategories = pluginCategoryMapper.selectListByQuery(categoryQuery); + return pluginCategories; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryServiceImpl.java new file mode 100644 index 0000000..86f192f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginCategoryServiceImpl.java @@ -0,0 +1,49 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import tech.easyflow.ai.entity.PluginCategory; +import tech.easyflow.ai.entity.PluginCategoryMapping; +import tech.easyflow.ai.mapper.PluginCategoryMapper; +import tech.easyflow.ai.mapper.PluginCategoryMappingMapper; +import tech.easyflow.ai.service.PluginCategoryService; +import org.springframework.stereotype.Service; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; + +/** + * 服务层实现。 + * + * @author Administrator + * @since 2025-05-21 + */ +@Service +public class PluginCategoryServiceImpl extends ServiceImpl implements PluginCategoryService { + + @Resource + private PluginCategoryMappingMapper relationMapper; + + @Resource + private PluginCategoryMapper pluginCategoryMapper; + + @Override + public boolean doRemoveCategory(BigInteger id) { + QueryWrapper queryWrapper = QueryWrapper.create().select() + .eq(PluginCategoryMapping::getCategoryId, id); + long relationCount = relationMapper.selectCountByQuery(queryWrapper); + if (relationCount > 0){ + int deletePluginRelation = relationMapper.deleteByQuery(queryWrapper); + if (deletePluginRelation <= 0){ + throw new BusinessException("删除失败"); + } + } + + int deleteCategory = pluginCategoryMapper.deleteById(id); + if (deleteCategory <= 0){ + throw new BusinessException("删除失败"); + } + return true; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginItemServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginItemServiceImpl.java new file mode 100644 index 0000000..2a7ccf0 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginItemServiceImpl.java @@ -0,0 +1,132 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.easyagents.tool.PluginTool; +import tech.easyflow.ai.entity.PluginItem; +import tech.easyflow.ai.mapper.BotPluginMapper; +import tech.easyflow.ai.mapper.PluginMapper; +import tech.easyflow.ai.mapper.PluginItemMapper; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.*; + +import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN; + +/** + * 服务层实现。 + * + * @author WangGangqiang + * @since 2025-04-27 + */ +@Service +public class PluginItemServiceImpl extends ServiceImpl implements PluginItemService { + + @Resource + private PluginItemMapper pluginItemMapper; + + @Resource + private PluginMapper pluginMapper; + + @Resource + private BotPluginMapper botPluginMapper; + + @Override + public boolean savePluginTool(PluginItem pluginItem) { + pluginItem.setCreated(new Date()); + pluginItem.setRequestMethod("Post"); + int insert = pluginItemMapper.insert(pluginItem); + if (insert <= 0) { + throw new BusinessException("保存失败"); + } + return true; + } + + @Override + public Result searchPlugin(BigInteger aiPluginToolId) { + //查询当前插件工具 + QueryWrapper queryAiPluginToolWrapper = QueryWrapper.create() + .select() + .eq(PluginItem::getId, aiPluginToolId); + PluginItem pluginItem = pluginItemMapper.selectOneByQuery(queryAiPluginToolWrapper); + // 查询当前的插件信息 + QueryWrapper queryAiPluginWrapper = QueryWrapper.create() + .select() + .eq(Plugin::getId, pluginItem.getPluginId()); + Plugin plugin = pluginMapper.selectOneByQuery(queryAiPluginWrapper); + Map result = new HashMap<>(); + result.put("data", pluginItem); + result.put("aiPlugin", plugin); + return Result.ok(result); + } + + @Override + public boolean updatePlugin(PluginItem pluginItem) { + int update = pluginItemMapper.update(pluginItem); + if (update <= 0) { + throw new BusinessException("修改失败"); + } + return true; + } + + @Override + public List searchPluginToolByPluginId(BigInteger pluginId, BigInteger botId) { + QueryWrapper queryAiPluginToolWrapper = QueryWrapper.create() + .select() + .eq(PluginItem::getPluginId, pluginId); + List pluginItems = pluginItemMapper.selectListByQueryAs(queryAiPluginToolWrapper, PluginItem.class); + // 查询当前bot有哪些插件工具方法 + QueryWrapper queryBotPluginTools = QueryWrapper.create() + .select() + .eq(BotPlugin::getBotId, botId); + List aiBotPluginToolIds = botPluginMapper.selectListWithRelationsByQueryAs(queryBotPluginTools, BigInteger.class); + aiBotPluginToolIds.forEach(botPluginTooId -> { + pluginItems.forEach(item -> { + if (Objects.equals(botPluginTooId, item.getId())) { + item.setJoinBot(true); + } + }); + }); + return pluginItems; + } + + @Override + public List getPluginToolList(BigInteger botId) { + QueryWrapper queryAiPluginToolWrapper = QueryWrapper.create() + .select(BOT_PLUGIN.PLUGIN_ITEM_ID) + .from(BOT_PLUGIN) + .where(BOT_PLUGIN.BOT_ID.eq(botId)); + List pluginToolIds = botPluginMapper.selectListByQueryAs(queryAiPluginToolWrapper, BigInteger.class); + if (pluginToolIds == null || pluginToolIds.isEmpty()) { + return Collections.emptyList(); + } + // 查询当前bots对应的有哪些pluginTool + return pluginItemMapper.selectListByIds(pluginToolIds); + } + + @Override + public Result pluginToolTest(String inputData, BigInteger pluginToolId) { + PluginItem pluginItem = new PluginItem(); + pluginItem.setId(pluginToolId); + pluginItem.setInputData(inputData); + PluginTool pluginTool = new PluginTool(pluginItem); + return Result.ok(pluginTool.runPluginTool(null, inputData, pluginToolId)); + } + + @Override + public List getByPluginId(String id) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq(PluginItem::getPluginId, id); + + return list(queryWrapper); + } + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java new file mode 100644 index 0000000..cc705f2 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java @@ -0,0 +1,133 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; +import tech.easyflow.ai.entity.*; +import tech.easyflow.ai.mapper.PluginCategoryMappingMapper; +import tech.easyflow.ai.mapper.PluginMapper; +import tech.easyflow.ai.service.BotPluginService; +import tech.easyflow.ai.service.PluginService; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.web.exceptions.BusinessException; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 服务层实现。 + * + * @author WangGangqiang + * @since 2025-04-25 + */ +@Service +public class PluginServiceImpl extends ServiceImpl implements PluginService { + + private static final Logger log = LoggerFactory.getLogger(PluginServiceImpl.class); + + @Resource + PluginMapper pluginMapper; + + @Resource + PluginCategoryMappingMapper pluginCategoryMappingMapper; + + @Resource + private BotPluginService botPluginService; + + @Resource + private PluginItemService pluginItemService; + + @Override + public boolean savePlugin(Plugin plugin) { + plugin.setCreated(new Date()); + int insert = pluginMapper.insert(plugin); + if (insert <= 0) { + throw new BusinessException("保存失败"); + } + return true; + } + + @Override + @Transactional + public boolean removePlugin(String id) { + + List pluginItems = pluginItemService.getByPluginId(id); + List pluginToolIds = new ArrayList<>(); + + if (pluginItems != null && !pluginItems.isEmpty()) { + + pluginToolIds = pluginItems.stream().map(PluginItem::getId).collect(Collectors.toList()); + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.in(BotPlugin::getPluginItemId, pluginToolIds); + boolean exists = botPluginService.exists(queryWrapper); + + if (exists){ + throw new BusinessException("插件中有工具还关联着bot,请先取消关联!"); + } + + } + + if ( !pluginToolIds.isEmpty()) { + boolean result = pluginItemService.removeByIds(pluginToolIds); + if (!result){ + log.error("删除插件工具表结果为0"); + throw new BusinessException("删除失败,请稍后重试!"); + } + } + + + int remove = pluginMapper.deleteById(id); + if (remove <= 0) { + log.error("删除插件结果为0"); + throw new BusinessException("删除失败,请稍后重试!"); + } + + return true; + + } + + @Override + public List getList() { + QueryWrapper queryWrapper = QueryWrapper.create().select(); + return pluginMapper.selectListByQueryAs(queryWrapper, Plugin.class); + } + + @Override + public Result> pageByCategory(Long pageNumber, Long pageSize, int category) { + // 通过分类查询插件 + QueryWrapper queryWrapper = QueryWrapper.create().select(PluginCategoryMapping::getPluginId) + .eq(PluginCategoryMapping::getCategoryId, category); + // 分页查询该分类中的插件 + Page pagePluginIds = pluginCategoryMappingMapper.paginateAs(new Page<>(pageNumber, pageSize), queryWrapper, BigInteger.class); + Page paginateCategories = pluginCategoryMappingMapper.paginate(pageNumber, pageSize, queryWrapper); + List plugins = Collections.emptyList(); + if (paginateCategories.getRecords().isEmpty()) { + return Result.ok(new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow())); + } + List pluginIds = pagePluginIds.getRecords(); + // 查询对应的插件信息 + QueryWrapper queryPluginWrapper = QueryWrapper.create().select() + .in(Plugin::getId, pluginIds); + plugins = pluginMapper.selectListByQuery(queryPluginWrapper); + Page aiPluginPage = new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow()); + return Result.ok(aiPluginPage); + } + + @Override + public boolean updatePlugin(Plugin plugin) { + pluginMapper.update(plugin); + return true; + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceCategoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceCategoryServiceImpl.java new file mode 100644 index 0000000..427021d --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceCategoryServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import tech.easyflow.ai.entity.ResourceCategory; +import tech.easyflow.ai.mapper.ResourceCategoryMapper; +import tech.easyflow.ai.service.ResourceCategoryService; +import org.springframework.stereotype.Service; + +/** + * 素材分类 服务层实现。 + * + * @author ArkLight + * @since 2025-12-24 + */ +@Service +public class ResourceCategoryServiceImpl extends ServiceImpl implements ResourceCategoryService{ + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceServiceImpl.java new file mode 100644 index 0000000..3de6617 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/ResourceServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.Resource; +import tech.easyflow.ai.mapper.ResourceMapper; +import tech.easyflow.ai.service.ResourceService; + +/** + * 素材库 服务层实现。 + * + * @author ArkLight + * @since 2025-06-27 + */ +@Service +public class ResourceServiceImpl extends ServiceImpl implements ResourceService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowCategoryServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowCategoryServiceImpl.java new file mode 100644 index 0000000..8e95ac9 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowCategoryServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.WorkflowCategory; +import tech.easyflow.ai.mapper.WorkflowCategoryMapper; +import tech.easyflow.ai.service.WorkflowCategoryService; + +/** + * 服务层实现。 + * + * @author ArkLight + * @since 2025-12-11 + */ +@Service +public class WorkflowCategoryServiceImpl extends ServiceImpl implements WorkflowCategoryService { + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecResultServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecResultServiceImpl.java new file mode 100644 index 0000000..810888f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecResultServiceImpl.java @@ -0,0 +1,25 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.WorkflowExecResult; +import tech.easyflow.ai.mapper.WorkflowExecResultMapper; +import tech.easyflow.ai.service.WorkflowExecResultService; + +/** + * 工作流执行记录 服务层实现。 + * + * @author ArkLight + * @since 2025-05-28 + */ +@Service +public class WorkflowExecResultServiceImpl extends ServiceImpl implements WorkflowExecResultService { + + @Override + public WorkflowExecResult getByExecKey(String execKey) { + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecResult::getExecKey, execKey); + return getOne(w); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecStepServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecStepServiceImpl.java new file mode 100644 index 0000000..4a8508e --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowExecStepServiceImpl.java @@ -0,0 +1,25 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.entity.WorkflowExecStep; +import tech.easyflow.ai.mapper.WorkflowExecStepMapper; +import tech.easyflow.ai.service.WorkflowExecStepService; + +/** + * 执行记录步骤 服务层实现。 + * + * @author ArkLight + * @since 2025-05-28 + */ +@Service +public class WorkflowExecStepServiceImpl extends ServiceImpl implements WorkflowExecStepService { + + @Override + public WorkflowExecStep getByExecKey(String execKey) { + QueryWrapper w = QueryWrapper.create(); + w.eq(WorkflowExecStep::getExecKey, execKey); + return getOne(w); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowServiceImpl.java new file mode 100644 index 0000000..284c3c1 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/WorkflowServiceImpl.java @@ -0,0 +1,74 @@ + +package tech.easyflow.ai.service.impl; + +import tech.easyflow.ai.entity.Workflow; +import tech.easyflow.ai.mapper.WorkflowMapper; +import tech.easyflow.ai.service.WorkflowService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.ai.utils.RegexUtils; +import com.mybatisflex.core.query.QueryWrapper; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.ai.utils.CustomBeanUtils; + +/** + * 服务层实现。 + * + * @author michael + * @since 2024-08-23 + */ +@Service +public class WorkflowServiceImpl extends ServiceImpl implements WorkflowService { + + /** + * 根据别名或 id 查询详情 + */ + @Override + public Workflow getDetail(String idOrAlias) { + + Workflow workflow = null; + + if (idOrAlias.matches(RegexUtils.ALL_NUMBER)) { + workflow = getById(idOrAlias); + if (workflow == null) { + workflow = getByAlias(idOrAlias); + } + } + + if (workflow == null) { + workflow = getByAlias(idOrAlias); + } + + return workflow; + } + + @Override + public Workflow getByAlias(String idOrAlias) { + + QueryWrapper queryWrapper = QueryWrapper.create(); + queryWrapper.eq("alias",idOrAlias); + + return getOne(queryWrapper); + + } + + + @Override + public boolean updateById(Workflow entity, boolean ignoreNulls) { + Workflow workFlow = getById(entity.getId()); + if (workFlow == null) { + throw new BusinessException("工作流不存在"); + } + + CustomBeanUtils.copyPropertiesIgnoreNull(entity,workFlow); + + if ("".equals(workFlow.getAlias())){ + workFlow.setAlias(null); + } + + + return super.updateById(workFlow,false); + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CommonFiledUtil.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CommonFiledUtil.java new file mode 100644 index 0000000..ac248e3 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CommonFiledUtil.java @@ -0,0 +1,38 @@ +package tech.easyflow.ai.utils; + +import tech.easyflow.common.web.exceptions.ProgramException; + +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.Date; + +public class CommonFiledUtil { + public static void commonFiled(Object t, BigInteger userId, BigInteger tenantId, BigInteger deptId) { + Method[] methods = t.getClass().getMethods(); + try { + for (Method m : methods) { + String name = m.getName(); + if ("setDeptId".equals(name)) { + m.invoke(t, deptId); + } + if ("setTenantId".equals(name)) { + m.invoke(t, tenantId); + } + if ("setCreatedBy".equals(name)) { + m.invoke(t, userId); + } + if ("setModifiedBy".equals(name)) { + m.invoke(t, userId); + } + if ("setCreated".equals(name)) { + m.invoke(t, new Date()); + } + if ("setModified".equals(name)) { + m.invoke(t, new Date()); + } + } + } catch (Exception e) { + throw new ProgramException("commonFiled反射出错:" + e.getMessage()); + } + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CustomBeanUtils.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CustomBeanUtils.java new file mode 100644 index 0000000..070a7a8 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/CustomBeanUtils.java @@ -0,0 +1,33 @@ + +package tech.easyflow.ai.utils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; + +import java.beans.PropertyDescriptor; +import java.util.HashSet; +import java.util.Set; + +public class CustomBeanUtils { + + public static void copyPropertiesIgnoreNull(Object source, Object target) { + BeanUtils.copyProperties(source, target, getNullPropertyNames(source)); + } + + private static String[] getNullPropertyNames(Object source) { + final BeanWrapper src = new BeanWrapperImpl(source); + PropertyDescriptor[] pds = src.getPropertyDescriptors(); + + Set emptyNames = new HashSet<>(); + for (PropertyDescriptor pd : pds) { + Object srcValue = src.getPropertyValue(pd.getName()); + if (srcValue == null) { + emptyNames.add(pd.getName()); + } + } + String[] result = new String[emptyNames.size()]; + return emptyNames.toArray(result); + } + + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/DocUtil.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/DocUtil.java new file mode 100644 index 0000000..570b5f7 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/DocUtil.java @@ -0,0 +1,197 @@ +package tech.easyflow.ai.utils; + +import com.easyagents.flow.core.util.OkHttpClientUtil; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.pdfbox.multipdf.Splitter; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageTree; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DocUtil { + + private static final Logger log = LoggerFactory.getLogger(DocUtil.class); + + public static byte[] downloadFile(String url) { + Request.Builder reqBuilder = new Request.Builder() + .url(url); + Request build = reqBuilder.build(); + OkHttpClient client = OkHttpClientUtil.buildDefaultClient(); + Call call = client.newCall(build); + try (Response response = call.execute()) { + if (response.body() != null) { + return response.body().bytes(); + } else { + throw new RuntimeException("下载内容为空"); + } + } catch (Exception e) { + log.error("下载文件失败:", e); + throw new RuntimeException(e); + } + } + + public static String readWordFile(String suffix, InputStream is) { + String content = ""; + try { + if ("docx".equals(suffix)) { + XWPFDocument document = new XWPFDocument(is); + XWPFWordExtractor extractor = new XWPFWordExtractor(document); + content = extractor.getText(); + // 关闭资源 + extractor.close(); + document.close(); + } + if ("doc".equals(suffix)) { + HWPFDocument document = new HWPFDocument(is); + WordExtractor extractor = new WordExtractor(document); + // 获取全部文本 + content = extractor.getText(); + // 关闭资源 + extractor.close(); + document.close(); + } + } catch (IOException e) { + log.error("读取word文件失败:", e); + throw new RuntimeException(e); + } + return content; + } + + public static String readPdfFile(InputStream is) { + try (PDDocument document = PDDocument.load(is)) { + PDFTextStripper stripper = new PDFTextStripper(); + return stripper.getText(document); + } catch (Exception e) { + log.error("读取pdf文件失败:", e); + throw new RuntimeException(e); + } + } + + public static Map splitPdf(byte[] bytes, int splitSize) { + + Map map = new HashMap<>(); + + int i = 0; + try (PDDocument document = PDDocument.load(bytes)) { + + PDPageTree pages = document.getPages(); + // 判断页面数量是否小于等于拆分大小 + if (pages.getCount() <= splitSize) { + map.put(1, bytes); + return map; + } + // 创建Splitter实例 + Splitter splitter = new Splitter(); + + // 设置拆分大小(每份文档的页数) + splitter.setSplitAtPage(splitSize); + + // 拆分文档 + List splitDocuments = splitter.split(document); + + // 保存拆分后的文档 + i = 1; + for (PDDocument splitDoc : splitDocuments) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + splitDoc.save(baos); + map.put(i, baos.toByteArray()); + splitDoc.close(); + i++; + } + } catch (Exception e) { + log.error("PDF拆分失败:", e); + throw new RuntimeException(e); + } + System.out.println("PDF拆分完成,共生成 " + (i - 1) + " 个文件。"); + return map; + } + + public static Map splitWord(byte[] bytes, int splitSize) { + + Map map = new HashMap<>(); + + InputStream is = new ByteArrayInputStream(bytes); + try { + XWPFDocument document = new XWPFDocument(is); + + List paragraphs = document.getParagraphs(); + List tables = document.getTables(); + + int totalParts = (int) Math.ceil((double) paragraphs.size() / splitSize); + + for (int i = 0; i < totalParts; i++) { + XWPFDocument newDoc = new XWPFDocument(); + + int start = i * splitSize; + int end = Math.min((i + 1) * splitSize, paragraphs.size()); + + // 复制段落 + for (int j = start; j < end; j++) { + XWPFParagraph newPara = newDoc.createParagraph(); + newPara.getCTP().set(paragraphs.get(j).getCTP()); + } + + // 复制表格(可选,可以根据需要调整) + for (XWPFTable table : tables) { + XWPFTable newTable = newDoc.createTable(); + newTable.getCTTbl().set(table.getCTTbl()); + } + + // 保存拆分后的文档 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDoc.write(baos); + map.put(i, baos.toByteArray()); + newDoc.close(); + } + document.close(); + is.close(); + } catch (IOException e) { + log.error("Word文档拆分失败:", e); + throw new RuntimeException(e); + } + return map; + } + + public static String getSuffix(String name) { + return name.substring(name.lastIndexOf(".") + 1); + } + + public static byte[] readBytes(InputStream inputStream) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try { + byte[] data = new byte[1024]; + int bytesRead; + + while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, bytesRead); + } + buffer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return buffer.toByteArray(); + } + + + public static String getFileNameByUrl(String url) { + return url.substring(url.lastIndexOf("/") + 1); + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/RegexUtils.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/RegexUtils.java new file mode 100644 index 0000000..b0b2510 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/RegexUtils.java @@ -0,0 +1,11 @@ + +package tech.easyflow.ai.utils; + +public class RegexUtils { + + + public static final String ALL_NUMBER = "\\d+"; + + public static final String NUMBER_LETTER_INCLUDE_ALL_LETTER = "[a-zA-Z0-9]+"; + +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/WorkFlowUtil.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/WorkFlowUtil.java new file mode 100644 index 0000000..7d6d07f --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/utils/WorkFlowUtil.java @@ -0,0 +1,45 @@ +package tech.easyflow.ai.utils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.easyagents.flow.core.chain.Chain; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.entity.LoginAccount; + +import java.math.BigInteger; + +public class WorkFlowUtil { + + public final static String USER_KEY = "user"; + public final static String WORKFLOW_KEY = "workflow"; + + public static String removeSensitiveInfo(String originJson) { + JSONObject workflowInfo = JSON.parseObject(originJson); + JSONArray nodes = workflowInfo.getJSONArray("nodes"); + for (Object node : nodes) { + JSONObject nodeInfo = (JSONObject) node; + JSONObject data = nodeInfo.getJSONObject("data"); + JSONObject newData = new JSONObject(); + newData.put("outputDefs", data.get("outputDefs")); + newData.put("parameters", data.get("parameters")); + newData.put("title", data.get("title")); + newData.put("description", data.get("description")); + nodeInfo.put("data", newData); + } + return workflowInfo.toJSONString(); + } + + public static LoginAccount getOperator(Chain chain) { + Object cache = chain.getState().getMemory().get(Constants.LOGIN_USER_KEY); + return cache == null ? defaultAccount() : (LoginAccount) cache; + } + + public static LoginAccount defaultAccount() { + LoginAccount account = new LoginAccount(); + account.setId(new BigInteger("0")); + account.setDeptId(new BigInteger("0")); + account.setTenantId(new BigInteger("0")); + return account; + } +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/vo/ChatMessageVO.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/vo/ChatMessageVO.java new file mode 100644 index 0000000..0900ee9 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/vo/ChatMessageVO.java @@ -0,0 +1,61 @@ +package tech.easyflow.ai.vo; + +import java.util.Date; + +public class ChatMessageVO { + + private String key; + private String role; + private String content; + private String placement; + private Boolean typing = true; + private Date created; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPlacement() { + return placement; + } + + public void setPlacement(String placement) { + this.placement = placement; + } + + public Boolean getTyping() { + return typing; + } + + public void setTyping(Boolean typing) { + this.typing = typing; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/easyflow-modules/easyflow-module-auth/pom.xml b/easyflow-modules/easyflow-module-auth/pom.xml new file mode 100644 index 0000000..666c324 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-auth + easyflow-module-auth + + + + tech.easyflow + easyflow-common-satoken + + + tech.easyflow + easyflow-common-web + + + tech.easyflow + easyflow-module-system + + + diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/AuthModuleConfig.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/AuthModuleConfig.java new file mode 100644 index 0000000..8b0fcb8 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/AuthModuleConfig.java @@ -0,0 +1,11 @@ +package tech.easyflow.auth.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; + +@AutoConfiguration +public class AuthModuleConfig { + + public AuthModuleConfig() { + System.out.println("启用模块 >>>>>>>>>> module-auth"); + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/CurdInterceptor.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/CurdInterceptor.java new file mode 100644 index 0000000..0dab329 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/CurdInterceptor.java @@ -0,0 +1,88 @@ +package tech.easyflow.auth.config; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import tech.easyflow.common.annotation.UsePermission; + +public class CurdInterceptor implements HandlerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(CurdInterceptor.class); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + String requestURI = request.getRequestURI(); + + log.info("进入 CurdInterceptor requestURI:{}", requestURI); + + String groupName = ""; + // 检查handler是否是HandlerMethod类型 + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + + + SaIgnore saIgnoreAnnotation = handlerMethod.getBeanType().getAnnotation(SaIgnore.class); + SaIgnore saIgnoreAnnotationMethod = handlerMethod.getMethodAnnotation(SaIgnore.class); + if (saIgnoreAnnotation != null || saIgnoreAnnotationMethod != null) { + log.info("{}-----------------> 放行: saIgnoreAnnotation:{}", requestURI,saIgnoreAnnotation); + return true; + } + + + // 获取类上的特定注解 + UsePermission classAnnotation = handlerMethod.getBeanType().getAnnotation(UsePermission.class); + if (classAnnotation != null) { + // 处理注解逻辑 + groupName = classAnnotation.moduleName(); + } + // 有此注解,交给 sa token 自行判断 + SaCheckPermission saCheckPermission = handlerMethod.getMethodAnnotation(SaCheckPermission.class); + if (saCheckPermission != null) { + return true; + } + } + String requestUri = request.getRequestURI(); + // 查询 + String finalGroupName = groupName; + SaRouter.match("/**/list", + "/**/page", + "/**/detail", + "/**/intelligentFilling" + ).check(r -> { + checkBaseCurd(requestUri, finalGroupName, "query"); + }); + // 保存 + SaRouter.match("/**/save", + "/**/update" + ).check(r -> { + checkBaseCurd(requestUri, finalGroupName, "save"); + }); + // 删除 + SaRouter.match("/**/remove", + "/**/removeBatch" + ).check(r -> { + checkBaseCurd(requestUri, finalGroupName, "remove"); + }); + + return true; + } + + private void checkBaseCurd(String uri, String groupName, String permission) { + int idx = uri.lastIndexOf("/"); + String per = uri.substring(0,idx + 1) + permission; + if (StrUtil.isNotEmpty(groupName)) { + // 如果指定了继承的模块,就改为该模块的权限校验 + per = groupName + "/" + permission; + } + StpUtil.checkPermission(per); + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginAutoConfig.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginAutoConfig.java new file mode 100644 index 0000000..54bf357 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginAutoConfig.java @@ -0,0 +1,42 @@ +package tech.easyflow.auth.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +@Configuration +public class LoginAutoConfig implements WebMvcConfigurer { + + @Resource + private LoginProperties properties; + + @Resource + private NeedApiKeyInterceptor needApiKeyInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + SaInterceptor saInterceptor = new SaInterceptor(handle -> { + StpUtil.checkLogin(); + }); + registry.addInterceptor(new CurdInterceptor()) + .order(101) + .addPathPatterns("/**"); + registry.addInterceptor(saInterceptor) + .order(100) + .addPathPatterns("/**") + .excludePathPatterns("/") + .excludePathPatterns("/error") + .excludePathPatterns("/attachment/**") + .excludePathPatterns("/api/v1/public/*") + .excludePathPatterns("/api/v1/account/login") + .excludePathPatterns("/api/v1/account/register") + .excludePathPatterns("/thirdAuth/**") + .excludePathPatterns("/public-api/**") + .excludePathPatterns(properties.getExcludesOrEmpty()); + registry.addInterceptor(needApiKeyInterceptor); + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginProperties.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginProperties.java new file mode 100644 index 0000000..ea33f04 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/LoginProperties.java @@ -0,0 +1,23 @@ +package tech.easyflow.auth.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "easyflow.login") +public class LoginProperties { + + private String[] excludes; + + public String[] getExcludes() { + return excludes; + } + + public String[] getExcludesOrEmpty() { + return excludes != null ? excludes : new String[0]; + } + + public void setExcludes(String[] excludes) { + this.excludes = excludes; + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/NeedApiKeyInterceptor.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/NeedApiKeyInterceptor.java new file mode 100644 index 0000000..a975920 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/config/NeedApiKeyInterceptor.java @@ -0,0 +1,36 @@ +package tech.easyflow.auth.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import tech.easyflow.common.annotation.NeedApiKeyAccess; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.system.service.SysApiKeyService; + +import javax.annotation.Resource; + +@Component +public class NeedApiKeyInterceptor implements HandlerInterceptor { + @Resource + private SysApiKeyService sysApiKeyService; + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String requestURI = request.getRequestURI(); + if ( handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + NeedApiKeyAccess needApiKeyAccess = handlerMethod.getMethodAnnotation(NeedApiKeyAccess.class); + if (needApiKeyAccess != null) { + String apiKey = request.getHeader("Authorization"); + if (StringUtil.noText(apiKey)) { + throw new BusinessException("请传入apiKey"); + } + sysApiKeyService.checkApikeyPermission(apiKey, requestURI); + + } + } + return true; + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginDTO.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginDTO.java new file mode 100644 index 0000000..dd5ce41 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginDTO.java @@ -0,0 +1,34 @@ +package tech.easyflow.auth.entity; + +import javax.validation.constraints.NotEmpty; + +public class LoginDTO { + + /** + * 账号 + */ + @NotEmpty(message = "账号不能为空") + private String account; + + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String password; + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginVO.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginVO.java new file mode 100644 index 0000000..1442534 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/entity/LoginVO.java @@ -0,0 +1,41 @@ +package tech.easyflow.auth.entity; + +public class LoginVO { + + /** + * token + */ + private String token; + /** + * 昵称 + */ + private String nickname; + /** + * 头像 + */ + private String avatar; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/AuthService.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/AuthService.java new file mode 100644 index 0000000..0fd5c7c --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/AuthService.java @@ -0,0 +1,11 @@ +package tech.easyflow.auth.service; + +import tech.easyflow.auth.entity.LoginDTO; +import tech.easyflow.auth.entity.LoginVO; + +public interface AuthService { + /** + * 登录 + */ + LoginVO login(LoginDTO loginDTO); +} diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..3840418 --- /dev/null +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java @@ -0,0 +1,86 @@ +package tech.easyflow.auth.service.impl; + +import tech.easyflow.auth.entity.LoginDTO; +import tech.easyflow.auth.entity.LoginVO; +import tech.easyflow.auth.service.AuthService; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.constant.enums.EnumDataStatus; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.entity.SysMenu; +import tech.easyflow.system.entity.SysRole; +import tech.easyflow.system.service.SysAccountService; +import tech.easyflow.system.service.SysMenuService; +import tech.easyflow.system.service.SysRoleService; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.crypto.digest.BCrypt; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.tenant.TenantManager; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class AuthServiceImpl implements AuthService, StpInterface { + + @Resource + private SysAccountService sysAccountService; + @Resource + private SysRoleService sysRoleService; + @Resource + private SysMenuService sysMenuService; + + @Override + public LoginVO login(LoginDTO loginDTO) { + LoginVO res = new LoginVO(); + try { + TenantManager.ignoreTenantCondition(); + String pwd = loginDTO.getPassword(); + QueryWrapper w = QueryWrapper.create(); + w.eq(SysAccount::getLoginName, loginDTO.getAccount()); + SysAccount record = sysAccountService.getOne(w); + if (record == null) { + throw new BusinessException("用户名/密码错误"); + } + if (EnumDataStatus.UNAVAILABLE.getCode().equals(record.getStatus())) { + throw new BusinessException("账号未启用,请联系管理员"); + } + String pwdDb = record.getPassword(); + if (!BCrypt.checkpw(pwd, pwdDb)) { + throw new BusinessException("用户名/密码错误"); + } + StpUtil.login(record.getId()); + LoginAccount loginAccount = new LoginAccount(); + BeanUtil.copyProperties(record, loginAccount); + StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount); + String tokenValue = StpUtil.getTokenValue(); + res.setToken(tokenValue); + res.setNickname(record.getNickname()); + res.setAvatar(record.getAvatar()); + } finally { + TenantManager.restoreTenantCondition(); + } + return res; + } + + @Override + public List getPermissionList(Object loginId, String loginType) { + List menus = sysMenuService.getMenusByAccountId(new SysMenu(), BigInteger.valueOf(Long.parseLong(loginId.toString()))); + return menus.stream() + .map(SysMenu::getPermissionTag) + .distinct() + .collect(Collectors.toList()); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + List roles = sysRoleService.getRolesByAccountId(BigInteger.valueOf(Long.parseLong(loginId.toString()))); + return roles.stream().map(SysRole::getRoleKey).collect(Collectors.toList()); + } +} diff --git a/easyflow-modules/easyflow-module-autoconfig/pom.xml b/easyflow-modules/easyflow-module-autoconfig/pom.xml new file mode 100644 index 0000000..002cde0 --- /dev/null +++ b/easyflow-modules/easyflow-module-autoconfig/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-autoconfig + easyflow-module-autoconfig + + + + tech.easyflow + easyflow-common-web + + + diff --git a/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/AutoConfig.java b/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/AutoConfig.java new file mode 100644 index 0000000..4396f5a --- /dev/null +++ b/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/AutoConfig.java @@ -0,0 +1,13 @@ +package tech.easyflow.autoconfig.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ComponentScan({"tech.easyflow"}) +@org.springframework.boot.autoconfigure.AutoConfiguration +public class AutoConfig { + public AutoConfig() { + System.out.println("启用模块 >>>>>>>>>> module-autoconfig"); + } +} diff --git a/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/WebConfig.java b/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/WebConfig.java new file mode 100644 index 0000000..d900c8a --- /dev/null +++ b/easyflow-modules/easyflow-module-autoconfig/src/main/java/tech/easyflow/autoconfig/config/WebConfig.java @@ -0,0 +1,74 @@ +package tech.easyflow.autoconfig.config; + +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.web.error.GlobalErrorResolver; +import tech.easyflow.common.dict.DictManager; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Collections; +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void configureMessageConverters(List> converters) { + converters.removeIf(httpMessageConverter -> { + List supportedMediaTypes = httpMessageConverter.getSupportedMediaTypes(); + return supportedMediaTypes.contains(MediaType.APPLICATION_JSON); + }); + //使用 fastjson 进行序列化 + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + + //设置兼容 web,把 BigInteger 转换为 String,防止精度丢失 + fastJsonHttpMessageConverter.getFastJsonConfig().setSerializerFeatures( + SerializerFeature.BrowserCompatible, //bigInteger 等自适应浏览器 + SerializerFeature.DisableCircularReferenceDetect //取消循环引用的,否则当有应用一个对象时,使用 $ref 替代 + ); + converters.add(fastJsonHttpMessageConverter); + } + + @Override + public void configureHandlerExceptionResolvers(List resolvers) { + resolvers.add(new GlobalErrorResolver()); + } + + @Bean + public org.springframework.web.filter.CorsFilter getCorsFilter(){ + CorsConfiguration cors = new CorsConfiguration(); + // 1,允许任何来源 + // springboot2.4后,当allowCredentials为true时, + // allowingOrigins不能包含特殊值"*",因为无法在“ Access-Control-Allow-Origin”响应标头上设置。 + // 要允许凭据具有一组来源,请明确列出它们或考虑改用"allowedOriginPatterns"。 + cors.setAllowedOriginPatterns(Collections.singletonList("*")); + //2,允许任何请求头 + cors.addAllowedHeader(CorsConfiguration.ALL); + //3,允许任何方法 + cors.addAllowedMethod(CorsConfiguration.ALL); + //4,允许凭证 + cors.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**",cors); + return new CorsFilter(source); + } + + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationStartup() { + DictManager dictManager = SpringContextUtil.getBean(DictManager.class); + System.out.println("onApplicationStartup >>>>>>" + dictManager); + } + +} diff --git a/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring.factories b/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..3f9a74b --- /dev/null +++ b/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Depends On Database Initialization Detectors +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + tech.easyflow.autoconfig.config.AutoConfig diff --git a/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..2e27626 --- /dev/null +++ b/easyflow-modules/easyflow-module-autoconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +tech.easyflow.autoconfig.config.AutoConfig \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-datacenter/pom.xml b/easyflow-modules/easyflow-module-datacenter/pom.xml new file mode 100644 index 0000000..061695c --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-datacenter + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + tech.easyflow + easyflow-common-base + + + tech.easyflow + easyflow-common-satoken + + + tech.easyflow + easyflow-common-web + + + \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleManager.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleManager.java new file mode 100644 index 0000000..fc96b2f --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleManager.java @@ -0,0 +1,16 @@ +package tech.easyflow.datacenter.adapter; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import tech.easyflow.common.util.SpringContextUtil; + +@Component +public class DbHandleManager { + + @Value("${easyflow.datacenter.handler:defaultDbHandleService}") + private String dbHandler; + + public DbHandleService getDbHandler() { + return SpringContextUtil.getBean(dbHandler); + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleService.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleService.java new file mode 100644 index 0000000..2358005 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DbHandleService.java @@ -0,0 +1,49 @@ +package tech.easyflow.datacenter.adapter; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONObject; +import tech.easyflow.common.constant.enums.EnumFieldType; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.datacenter.entity.DatacenterTable; +import tech.easyflow.datacenter.entity.DatacenterTableField; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public abstract class DbHandleService { + + public abstract void createTable(DatacenterTable table); + + public abstract void updateTable(DatacenterTable table, DatacenterTable record); + + public abstract void deleteTable(DatacenterTable table); + + /** + * @see tech.easyflow.common.constant.enums.EnumFieldType + */ + public abstract String convertFieldType(Integer fieldType); + + public abstract void addField(DatacenterTable entity, DatacenterTableField field); + + public abstract void deleteField(DatacenterTable entity, DatacenterTableField field); + + public abstract void updateField(DatacenterTable entity, DatacenterTableField fieldRecord, DatacenterTableField field); + + public abstract void saveValue(DatacenterTable entity, JSONObject object, LoginAccount account); + + public abstract void updateValue(DatacenterTable entity, JSONObject object, LoginAccount account); + + public abstract void removeValue(DatacenterTable entity, BigInteger id, LoginAccount account); + public Object convertFieldValue(Integer fieldType, String fieldValue) { + if (fieldType.equals(EnumFieldType.INTEGER.getCode()) || fieldType.equals(EnumFieldType.BOOLEAN.getCode())) { + return Integer.parseInt(fieldValue); + } + if (fieldType.equals(EnumFieldType.TIME.getCode())) { + return DateUtil.parse(fieldValue); + } + if (fieldType.equals(EnumFieldType.NUMBER.getCode())) { + return new BigDecimal(fieldValue); + } + return fieldValue; + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DefaultDbHandleService.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DefaultDbHandleService.java new file mode 100644 index 0000000..57a4cd4 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/adapter/DefaultDbHandleService.java @@ -0,0 +1,213 @@ +package tech.easyflow.datacenter.adapter; + +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.row.Db; +import com.mybatisflex.core.row.Row; +import com.mybatisflex.core.row.RowKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import tech.easyflow.common.constant.enums.EnumFieldType; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.datacenter.entity.DatacenterTable; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.utils.SqlInjectionUtils; + +import java.math.BigInteger; +import java.util.Date; +import java.util.List; + +@Component("defaultDbHandleService") +public class DefaultDbHandleService extends DbHandleService { + + private static final Logger log = LoggerFactory.getLogger(DefaultDbHandleService.class); + + @Override + public void createTable(DatacenterTable table) { + // 设置为 [tb_dynamic_表名_tableId] 的格式 + String actualTable = table.getActualTable(); + SqlInjectionUtils.checkIdentifier(actualTable); + // 表注释 + String tableDesc = table.getTableDesc(); + SqlInjectionUtils.checkComment(tableDesc); + + List fields = table.getFields(); + StringBuilder sql = new StringBuilder("CREATE TABLE " + actualTable + " ("); + sql.append("`id` bigint unsigned NOT NULL COMMENT '主键',"); + sql.append("`dept_id` bigint unsigned NOT NULL COMMENT '部门ID',"); + sql.append("`tenant_id` bigint unsigned NOT NULL COMMENT '租户ID',"); + sql.append("`created` datetime NOT NULL COMMENT '创建时间',"); + sql.append("`created_by` bigint unsigned NOT NULL COMMENT '创建者',"); + sql.append("`modified` datetime NOT NULL COMMENT '修改时间',"); + sql.append("`modified_by` bigint unsigned NOT NULL COMMENT '修改者',"); + sql.append("`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '备注',"); + for (DatacenterTableField field : fields) { + Integer required = field.getRequired(); + String fieldName = SqlInjectionUtils.checkIdentifier(field.getFieldName()); + String fieldDesc = SqlInjectionUtils.checkComment(field.getFieldDesc()); + sql.append("`").append(fieldName).append("` ") + .append(convertFieldType(field.getFieldType())).append(" ") + .append(required == 1 ? "NOT NULL" : "NULL").append(" ") + .append("COMMENT '").append(fieldDesc).append("',"); + } + sql.append("PRIMARY KEY (id) USING BTREE"); + sql.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='").append(tableDesc).append("';"); + log.info("建表语句 >>> {}", sql); + Db.selectObject(sql.toString()); + } + + @Override + public void updateTable(DatacenterTable table, DatacenterTable record) { + String tableDesc = table.getTableDesc(); + SqlInjectionUtils.checkComment(tableDesc); + String actualTable = record.getActualTable(); + // 只允许改表备注 + if (!tableDesc.equals(record.getTableDesc())) { + String sql = "ALTER TABLE `" + actualTable + "` " + + "COMMENT '" + tableDesc + "';"; + log.info("修改表备注语句 >>> {}", sql); + Db.selectObject(sql); + } + } + + @Override + public void deleteTable(DatacenterTable table) { + String actualTable = table.getActualTable(); + String sql = "DROP TABLE IF EXISTS `" + actualTable + "`;"; + log.info("删除表语句 >>> {}", sql); + Db.selectObject(sql); + } + + @Override + public String convertFieldType(Integer fieldType) { + if (EnumFieldType.INTEGER.getCode().equals(fieldType)) { + return "int"; + } + if (EnumFieldType.BOOLEAN.getCode().equals(fieldType)) { + return "int"; + } + if (EnumFieldType.TIME.getCode().equals(fieldType)) { + return "datetime"; + } + if (EnumFieldType.NUMBER.getCode().equals(fieldType)) { + return "decimal(20,6)"; + } + return "text"; + } + + @Override + public void addField(DatacenterTable entity, DatacenterTableField field) { + String fieldName = field.getFieldName(); + SqlInjectionUtils.checkIdentifier(fieldName); + + String fieldDesc = field.getFieldDesc(); + SqlInjectionUtils.checkComment(fieldDesc); + + Integer fieldType = field.getFieldType(); + Integer required = field.getRequired(); + String sql = "ALTER TABLE `" + entity.getActualTable() + "`" + + " ADD COLUMN `" + fieldName + "` " + convertFieldType(fieldType) + " " + + (required == 1 ? "NOT NULL" : "NULL") + " " + + "COMMENT '" + fieldDesc + "';"; + log.info("添加字段语句 >>> {}", sql); + Db.selectObject(sql); + } + + @Override + public void deleteField(DatacenterTable entity, DatacenterTableField field) { + + String fieldName = field.getFieldName(); + SqlInjectionUtils.checkIdentifier(fieldName); + + String sql = "ALTER TABLE `" + entity.getActualTable() + "`" + + " DROP COLUMN `" + fieldName + "`;"; + log.info("删除字段语句 >>> {}", sql); + Db.selectObject(sql); + } + + @Override + public void updateField(DatacenterTable entity, DatacenterTableField fieldRecord, DatacenterTableField field) { + String actualTable = entity.getActualTable(); + // 是否必填 + Integer required = field.getRequired(); + // 字段名称 + String fieldName = field.getFieldName(); + SqlInjectionUtils.checkIdentifier(fieldName); + // 字段描述 + String fieldDesc = field.getFieldDesc(); + SqlInjectionUtils.checkComment(fieldDesc); + + String nullable = required == 1 ? "NOT NULL " : "NULL "; + String desc = "COMMENT '" + fieldDesc + "';"; + + boolean isUpdate = false; + String handleType = "MODIFY COLUMN `" + fieldRecord.getFieldName() + "` "; + + if (!required.equals(fieldRecord.getRequired())) { + isUpdate = true; + } + + if (!fieldDesc.equals(fieldRecord.getFieldDesc())) { + isUpdate = true; + } + + if (!fieldName.equals(fieldRecord.getFieldName())) { + isUpdate = true; + handleType = "CHANGE COLUMN `" + fieldRecord.getFieldName() + "` `" + fieldName + "` "; + } + + if (isUpdate) { + String sql = "ALTER TABLE `" + actualTable + "` " + + handleType + + convertFieldType(field.getFieldType()) + " " + + nullable + + desc; + log.info("更新字段语句 >>> {}", sql); + Db.selectObject(sql); + } + } + + @Override + public void saveValue(DatacenterTable entity, JSONObject object, LoginAccount account) { + String actualTable = entity.getActualTable(); + List fields = entity.getFields(); + + Row row = Row.ofKey(RowKey.SNOW_FLAKE_ID); + row.put("dept_id", account.getDeptId()); + row.put("tenant_id", account.getTenantId()); + row.put("created", new Date()); + row.put("created_by", account.getId()); + row.put("modified", new Date()); + row.put("modified_by", account.getId()); + row.put("remark", object.get("remark")); + for (DatacenterTableField field : fields) { + String fieldName = field.getFieldName(); + row.put(fieldName, object.get(fieldName)); + } + + Db.insert(actualTable, row); + } + + @Override + public void updateValue(DatacenterTable entity, JSONObject object, LoginAccount account) { + String actualTable = entity.getActualTable(); + List fields = entity.getFields(); + + Row row = Row.ofKey("id", object.get("id")); + row.put("modified", new Date()); + row.put("modified_by", account.getId()); + for (DatacenterTableField field : fields) { + String fieldName = field.getFieldName(); + row.put(fieldName, object.get(fieldName)); + } + + Db.updateById(actualTable, row); + } + + @Override + public void removeValue(DatacenterTable entity, BigInteger id, LoginAccount account) { + String actualTable = entity.getActualTable(); + Row row = Row.ofKey("id", id); + Db.deleteById(actualTable, row); + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/config/DatacenterModuleConfig.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/config/DatacenterModuleConfig.java new file mode 100644 index 0000000..b91df92 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/config/DatacenterModuleConfig.java @@ -0,0 +1,13 @@ +package tech.easyflow.datacenter.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("tech.easyflow.datacenter.mapper") +public class DatacenterModuleConfig { + + public DatacenterModuleConfig() { + System.out.println("启用模块 >>>>>>>>>> module-datacenter"); + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTable.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTable.java new file mode 100644 index 0000000..c29141b --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTable.java @@ -0,0 +1,29 @@ +package tech.easyflow.datacenter.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import tech.easyflow.datacenter.entity.base.DatacenterTableBase; + +import java.util.ArrayList; +import java.util.List; + +/** + * 数据中枢表 实体类。 + * + * @author ArkLight + * @since 2025-07-10 + */ +@Table(value = "tb_datacenter_table", comment = "数据中枢表") +public class DatacenterTable extends DatacenterTableBase { + + @Column(ignore = true) + private List fields = new ArrayList<>(); + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTableField.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTableField.java new file mode 100644 index 0000000..bbbe4ec --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/DatacenterTableField.java @@ -0,0 +1,40 @@ +package tech.easyflow.datacenter.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import tech.easyflow.datacenter.entity.base.DatacenterTableFieldBase; + +import java.math.BigInteger; + + +/** + * 实体类。 + * + * @author ArkLight + * @since 2025-07-10 + */ +@Table(value = "tb_datacenter_table_field", comment = "数据中枢字段表") +public class DatacenterTableField extends DatacenterTableFieldBase { + + /** + * 是否删除该字段 + * 前端传入 true 则删除该字段 + */ + @Column(ignore = true) + private Boolean handleDelete = false; + + public Boolean isHandleDelete() { + return handleDelete; + } + + public void setHandleDelete(Boolean handleDelete) { + this.handleDelete = handleDelete; + } + + /** + * 前端区分 rowKey + */ + public BigInteger getRowKey() { + return this.getId(); + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableBase.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableBase.java new file mode 100644 index 0000000..820491b --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableBase.java @@ -0,0 +1,186 @@ +package tech.easyflow.datacenter.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class DatacenterTableBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 数据表名 + */ + @Column(comment = "数据表名") + private String tableName; + + /** + * 数据表描述 + */ + @Column(comment = "数据表描述") + private String tableDesc; + + /** + * 物理表名 + */ + @Column(comment = "物理表名") + private String actualTable; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 扩展项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项") + private Map options; + + 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 getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getTableDesc() { + return tableDesc; + } + + public void setTableDesc(String tableDesc) { + this.tableDesc = tableDesc; + } + + public String getActualTable() { + return actualTable; + } + + public void setActualTable(String actualTable) { + this.actualTable = actualTable; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableFieldBase.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableFieldBase.java new file mode 100644 index 0000000..b4926e4 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/base/DatacenterTableFieldBase.java @@ -0,0 +1,172 @@ +package tech.easyflow.datacenter.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class DatacenterTableFieldBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 数据表ID + */ + @Column(comment = "数据表ID") + private BigInteger tableId; + + /** + * 字段名称 + */ + @Column(comment = "字段名称") + private String fieldName; + + /** + * 字段描述 + */ + @Column(comment = "字段描述") + private String fieldDesc; + + /** + * 字段类型 + */ + @Column(comment = "字段类型") + private Integer fieldType; + + /** + * 是否必填 + */ + @Column(comment = "是否必填") + private Integer required; + + /** + * 扩展项 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项") + private Map options; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getTableId() { + return tableId; + } + + public void setTableId(BigInteger tableId) { + this.tableId = tableId; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldDesc() { + return fieldDesc; + } + + public void setFieldDesc(String fieldDesc) { + this.fieldDesc = fieldDesc; + } + + public Integer getFieldType() { + return fieldType; + } + + public void setFieldType(Integer fieldType) { + this.fieldType = fieldType; + } + + public Integer getRequired() { + return required; + } + + public void setRequired(Integer required) { + this.required = required; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/vo/HeaderVo.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/vo/HeaderVo.java new file mode 100644 index 0000000..e3e2db4 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/entity/vo/HeaderVo.java @@ -0,0 +1,70 @@ +package tech.easyflow.datacenter.entity.vo; + +import java.math.BigInteger; + +public class HeaderVo { + + private String key; + private String dataIndex; + private String title; + private Integer fieldType; + private Integer required; + private BigInteger fieldId; + private BigInteger tableId; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getDataIndex() { + return dataIndex; + } + + public void setDataIndex(String dataIndex) { + this.dataIndex = dataIndex; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Integer getFieldType() { + return fieldType; + } + + public void setFieldType(Integer fieldType) { + this.fieldType = fieldType; + } + + public Integer getRequired() { + return required; + } + + public void setRequired(Integer required) { + this.required = required; + } + + public BigInteger getFieldId() { + return fieldId; + } + + public void setFieldId(BigInteger fieldId) { + this.fieldId = fieldId; + } + + public BigInteger getTableId() { + return tableId; + } + + public void setTableId(BigInteger tableId) { + this.tableId = tableId; + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadDataListener.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadDataListener.java new file mode 100644 index 0000000..f8d83ad --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadDataListener.java @@ -0,0 +1,107 @@ +package tech.easyflow.datacenter.excel; + +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.read.listener.ReadListener; +import com.alibaba.fastjson2.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.service.DatacenterTableService; + +import java.math.BigInteger; +import java.util.*; + +public class ReadDataListener implements ReadListener> { + + private static final Logger log = LoggerFactory.getLogger(ReadDataListener.class); + + private BigInteger tableId; + + private List fields; + + private LoginAccount loginAccount; + + private final Map headFieldIndex = new HashMap<>(); + + private int successCount = 0; + private int errorCount = 0; + private int totalCount = 0; + + private final List errorRows = new ArrayList<>(); + + public ReadDataListener() { + } + + public ReadDataListener(BigInteger tableId, List fields, LoginAccount loginAccount) { + this.tableId = tableId; + this.fields = fields; + this.loginAccount = loginAccount; + } + + @Override + public void invoke(LinkedHashMap o, AnalysisContext analysisContext) { + DatacenterTableService service = SpringContextUtil.getBean(DatacenterTableService.class); + JSONObject obj = new JSONObject(); + for (DatacenterTableField field : fields) { + String fieldName = field.getFieldName(); + Integer i = headFieldIndex.get(fieldName); + if (i != null) { + obj.put(fieldName, o.get(i)); + } + } + try { + service.saveValue(tableId, obj, loginAccount); + successCount++; + } catch (Exception e) { + errorCount++; + log.error("导入数据到数据中枢失败,具体值:{}", obj, e); + errorRows.add(obj); + } + totalCount++; + } + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + Set>> entries = headMap.entrySet(); + for (Map.Entry> entry : entries) { + Integer key = entry.getKey(); + String field = entry.getValue().getStringValue(); + headFieldIndex.put(field, key); + } + if (headFieldIndex.size() != fields.size()) { + throw new RuntimeException("表头字段数量与表结构对应不上!"); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public int getSuccessCount() { + return successCount; + } + + public int getErrorCount() { + return errorCount; + } + + public int getTotalCount() { + return totalCount; + } + + public List getErrorRows() { + return errorRows; + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadResVo.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadResVo.java new file mode 100644 index 0000000..f4e636a --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/excel/ReadResVo.java @@ -0,0 +1,68 @@ +package tech.easyflow.datacenter.excel; + +import com.alibaba.fastjson2.JSONObject; + +import java.util.List; + +public class ReadResVo { + + /** + * 成功数 + */ + private int successCount = 0; + /** + * 失败数 + */ + private int errorCount = 0; + /** + * 总数 + */ + private int totalCount = 0; + + /** + * 错误行 + */ + private List errorRows; + + public ReadResVo() { + } + + public ReadResVo(int successCount, int errorCount, int totalCount, List errorRows) { + this.successCount = successCount; + this.errorCount = errorCount; + this.totalCount = totalCount; + this.errorRows = errorRows; + } + + public int getSuccessCount() { + return successCount; + } + + public void setSuccessCount(int successCount) { + this.successCount = successCount; + } + + public int getErrorCount() { + return errorCount; + } + + public void setErrorCount(int errorCount) { + this.errorCount = errorCount; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public List getErrorRows() { + return errorRows; + } + + public void setErrorRows(List errorRows) { + this.errorRows = errorRows; + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableFieldMapper.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableFieldMapper.java new file mode 100644 index 0000000..4f2e807 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableFieldMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.datacenter.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.datacenter.entity.DatacenterTableField; + +/** + * 映射层。 + * + * @author ArkLight + * @since 2025-07-10 + */ +public interface DatacenterTableFieldMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableMapper.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableMapper.java new file mode 100644 index 0000000..0a07715 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/mapper/DatacenterTableMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.datacenter.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.datacenter.entity.DatacenterTable; + +/** + * 数据中枢表 映射层。 + * + * @author ArkLight + * @since 2025-07-10 + */ +public interface DatacenterTableMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableFieldService.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableFieldService.java new file mode 100644 index 0000000..3b4dc41 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableFieldService.java @@ -0,0 +1,14 @@ +package tech.easyflow.datacenter.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.datacenter.entity.DatacenterTableField; + +/** + * 服务层。 + * + * @author ArkLight + * @since 2025-07-10 + */ +public interface DatacenterTableFieldService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableService.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableService.java new file mode 100644 index 0000000..e3dec4a --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/DatacenterTableService.java @@ -0,0 +1,35 @@ +package tech.easyflow.datacenter.service; + +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.row.Row; +import com.mybatisflex.core.service.IService; +import tech.easyflow.common.entity.DatacenterQuery; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.datacenter.entity.DatacenterTable; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.entity.vo.HeaderVo; + +import java.math.BigInteger; +import java.util.List; + +public interface DatacenterTableService extends IService { + + void saveTable(DatacenterTable entity, LoginAccount loginUser); + + void removeTable(BigInteger tableId); + + Long getCount(DatacenterQuery where); + + List getListData(DatacenterQuery where); + + Page getPageData(DatacenterQuery where); + + List getHeaders(BigInteger tableId); + + void saveValue(BigInteger tableId, JSONObject object, LoginAccount account); + + void removeValue(BigInteger tableId, BigInteger id, LoginAccount account); + + List getFields(BigInteger tableId); +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableFieldServiceImpl.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableFieldServiceImpl.java new file mode 100644 index 0000000..b89ae80 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableFieldServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.datacenter.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper; +import tech.easyflow.datacenter.service.DatacenterTableFieldService; + +/** + * 服务层实现。 + * + * @author ArkLight + * @since 2025-07-10 + */ +@Service +public class DatacenterTableFieldServiceImpl extends ServiceImpl implements DatacenterTableFieldService { + +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableServiceImpl.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableServiceImpl.java new file mode 100644 index 0000000..1f89c91 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/service/impl/DatacenterTableServiceImpl.java @@ -0,0 +1,260 @@ +package tech.easyflow.datacenter.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.row.Db; +import com.mybatisflex.core.row.Row; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.easyflow.common.entity.DatacenterQuery; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.datacenter.adapter.DbHandleManager; +import tech.easyflow.datacenter.adapter.DbHandleService; +import tech.easyflow.datacenter.entity.DatacenterTable; +import tech.easyflow.datacenter.entity.DatacenterTableField; +import tech.easyflow.datacenter.entity.vo.HeaderVo; +import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper; +import tech.easyflow.datacenter.mapper.DatacenterTableMapper; +import tech.easyflow.datacenter.service.DatacenterTableService; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class DatacenterTableServiceImpl extends ServiceImpl implements DatacenterTableService { + + @Resource + private DbHandleManager dbHandleManager; + @Resource + private DatacenterTableFieldMapper fieldsMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveTable(DatacenterTable entity, LoginAccount loginUser) { + + DbHandleService dbHandler = dbHandleManager.getDbHandler(); + + List fields = entity.getFields(); + + BigInteger tableId = entity.getId(); + + if (tableId == null) { + long snowId = new SnowFlakeIDKeyGenerator().nextId(); + entity.setId(new BigInteger(String.valueOf(snowId))); + + String actualTable = getActualTableName(entity); + entity.setActualTable(actualTable); + // 先 DDL 操作,DDL会默认提交事务,不然报错了事务不会回滚。 + dbHandler.createTable(entity); + // 保存主表和字段表 + save(entity); + for (DatacenterTableField field : fields) { + // 插入 + field.setCreated(new Date()); + field.setCreatedBy(loginUser.getId()); + field.setModified(new Date()); + field.setModifiedBy(loginUser.getId()); + field.setTableId(entity.getId()); + fieldsMapper.insert(field); + } + } else { + // actualTable 前端不可见,所以要设置 + DatacenterTable tableRecord = getById(tableId); + entity.setActualTable(tableRecord.getActualTable()); + dbHandler.updateTable(entity, tableRecord); + updateById(entity); + // 查询所有字段 + QueryWrapper w = QueryWrapper.create(); + w.eq(DatacenterTableField::getTableId, entity.getId()); + List fieldRecords = fieldsMapper.selectListByQuery(w); + + Map fieldsMap = fieldRecords.stream() + .collect(Collectors.toMap(DatacenterTableField::getId, field -> field)); + + for (DatacenterTableField field : fields) { + BigInteger id = field.getId(); + if (id == null) { + // 新增字段到物理表 + dbHandler.addField(entity, field); + // 插入 + field.setCreated(new Date()); + field.setCreatedBy(loginUser.getId()); + field.setModified(new Date()); + field.setModifiedBy(loginUser.getId()); + field.setTableId(entity.getId()); + fieldsMapper.insert(field); + } else { + // 删除的字段 + if (field.isHandleDelete()) { + // 删除物理表中的字段 + dbHandler.deleteField(entity, field); + // 删除字段 + fieldsMapper.deleteById(id); + } else { + // 修改物理表中的字段 + DatacenterTableField fieldRecord = fieldsMap.get(id); + dbHandler.updateField(entity, fieldRecord, field); + // 更新字段,字段类型不允许修改 + field.setFieldType(field.getFieldType()); + field.setModified(new Date()); + field.setModifiedBy(loginUser.getId()); + fieldsMapper.update(field); + } + } + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeTable(BigInteger tableId) { + DatacenterTable record = getById(tableId); + dbHandleManager.getDbHandler().deleteTable(record); + removeById(tableId); + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.eq(DatacenterTableField::getTableId, tableId); + fieldsMapper.deleteByQuery(wrapper); + } + + @Override + public Long getCount(DatacenterQuery where) { + String actualTable = getActualTable(where.getTableId()); + QueryWrapper wrapper = QueryWrapper.create(); + buildCondition(wrapper, where); + return Db.selectCountByQuery(actualTable, wrapper); + } + + @Override + public List getListData(DatacenterQuery where) { + String actualTable = getActualTable(where.getTableId()); + QueryWrapper wrapper = QueryWrapper.create(); + buildCondition(wrapper, where); + List rows = Db.selectListByQuery(actualTable, wrapper); + handleBigNumber(rows); + return rows; + } + + @Override + public Page getPageData(DatacenterQuery where) { + Long pageNumber = where.getPageNumber(); + Long pageSize = where.getPageSize(); + + Long count = getCount(where); + if (count == 0) { + return new Page<>(new ArrayList<>(), pageNumber, pageSize, count); + } + + String actualTable = getActualTable(where.getTableId()); + QueryWrapper wrapper = QueryWrapper.create(); + buildCondition(wrapper, where); + + Page page = new Page<>(pageNumber, pageSize, count); + Page paginate = Db.paginate(actualTable, page, wrapper); + handleBigNumber(paginate.getRecords()); + return paginate; + } + + private void handleBigNumber(List records) { + for (Row record : records) { + Map newMap = new LinkedHashMap<>(); + for (Map.Entry entry : record.entrySet()) { + Object value = entry.getValue(); + if ((value instanceof BigInteger || + value instanceof BigDecimal || + value instanceof Long)) { + newMap.put(entry.getKey(), value.toString()); + } else { + newMap.put(entry.getKey(), value); + } + } + record.clear(); + record.putAll(newMap); + } + } + + @Override + public List getHeaders(BigInteger tableId) { + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.eq(DatacenterTableField::getTableId, tableId); + wrapper.orderBy("id"); + List fields = fieldsMapper.selectListByQuery(wrapper); + List headers = new ArrayList<>(); + for (DatacenterTableField field : fields) { + HeaderVo header = new HeaderVo(); + header.setKey(field.getFieldName()); + header.setDataIndex(field.getFieldName()); + header.setTitle(field.getFieldDesc()); + header.setFieldType(field.getFieldType()); + header.setRequired(field.getRequired()); + header.setFieldId(field.getId()); + header.setTableId(field.getTableId()); + headers.add(header); + } + return headers; + } + + @Override + public void saveValue(BigInteger tableId, JSONObject object, LoginAccount account) { + + DatacenterTable table = getById(tableId); + + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.eq(DatacenterTableField::getTableId, tableId); + List fields = fieldsMapper.selectListByQuery(wrapper); + + if (CollectionUtil.isEmpty(fields)) { + throw new BusinessException("请先添加字段"); + } + table.setFields(fields); + Object valueId = object.get("id"); + if (valueId == null) { + dbHandleManager.getDbHandler().saveValue(table, object, account); + } else { + dbHandleManager.getDbHandler().updateValue(table, object, account); + } + } + + @Override + public void removeValue(BigInteger tableId, BigInteger id, LoginAccount account) { + DatacenterTable record = getById(tableId); + dbHandleManager.getDbHandler().removeValue(record, id, account); + } + + @Override + public List getFields(BigInteger tableId) { + QueryWrapper wrapper = QueryWrapper.create(); + wrapper.eq(DatacenterTableField::getTableId, tableId); + return fieldsMapper.selectListByQuery(wrapper); + } + + private String getActualTable(BigInteger tableId) { + DatacenterTable record = getById(tableId); + return record.getActualTable(); + } + + private String getActualTableName(DatacenterTable table) { + String tableName = table.getTableName(); + BigInteger id = table.getId(); + return "tb_dynamic_" + tableName + "_" + id; + } + + /** + * 构建查询条件 + */ + private void buildCondition(QueryWrapper wrapper, DatacenterQuery where) { + // 构建查询条件 + String condition = where.getWhere(); + if (StrUtil.isNotEmpty(condition)) { + wrapper.where(condition); + } + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/SqlInjectionUtils.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/SqlInjectionUtils.java new file mode 100644 index 0000000..2007f17 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/SqlInjectionUtils.java @@ -0,0 +1,68 @@ +package tech.easyflow.datacenter.utils; + +import tech.easyflow.common.web.exceptions.BusinessException; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class SqlInjectionUtils { + + private static final Set SQL_KEYWORDS = new HashSet<>(Arrays.asList( + "select", "insert", "update", "delete", "drop", "alter", "create", + "table", "where", "from", "join", "union", "truncate", "execute", + "grant", "revoke", "commit", "rollback" + )); + + /** + * 校验字段或表名 + */ + public static String checkIdentifier(String identifier) { + if (identifier == null || identifier.isEmpty()) { + throw new BusinessException("标识符不能为空"); + } + if (identifier.length() > 64) { + throw new BusinessException("标识符过长"); + } + // 检查是否只包含字母、数字和下划线 + if (!identifier.matches("^[a-zA-Z0-9_]+$")) { + throw new BusinessException("只允许字母、数字和下划线"); + } + if (isSqlKeyword(identifier)) { + throw new BusinessException("非法字符"); + } + return identifier; + } + + /** + * 校验注释 + * 允许的字符包括以下 Unicode 类别或符号: + * \p{L}:任何语言的字母(包括中文、英文、日文等)。 + * \p{N}:任何数字(包括阿拉伯数字 0-9 或其他语言的数字符号)。 + * \p{Zs}:空白分隔符(如空格,但不包括换行符、制表符等)。 + * 标点符号:. , - : ? !(基础标点)。 + */ + public static String checkComment(String comment) { + if (comment == null) { + return ""; + } + if (comment.length() > 255) { + throw new BusinessException("注释过长"); + } + if (!comment.matches("^[\\p{L}\\p{N}\\p{Zs}\\.\\,\\-\\:\\?\\!]+$")) { + throw new BusinessException("包含非法字符"); + } + if (comment.contains("--")) { + throw new BusinessException("包含非法字符!"); + } + if (comment.chars().anyMatch(c -> c <= 31 || c == 127)) { + throw new BusinessException("存在非法字符"); + } + return comment; + } + + // 检查是否是数据库关键字 + public static boolean isSqlKeyword(String word) { + return SQL_KEYWORDS.contains(word.toLowerCase()); + } +} diff --git a/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/WhereConditionSecurityChecker.java b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/WhereConditionSecurityChecker.java new file mode 100644 index 0000000..ea11d09 --- /dev/null +++ b/easyflow-modules/easyflow-module-datacenter/src/main/java/tech/easyflow/datacenter/utils/WhereConditionSecurityChecker.java @@ -0,0 +1,142 @@ +package tech.easyflow.datacenter.utils; + +import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.LikeExpression; +import net.sf.jsqlparser.schema.Column; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class WhereConditionSecurityChecker { + + // 允许的运算符白名单 + private static final Set ALLOWED_OPERATORS = new HashSet<>(Arrays.asList( + "=", "!=", "<>", "<", ">", "<=", ">=", "LIKE", "IN", "IS NULL", "IS NOT NULL", + "NOT IN", "NOT LIKE", "BETWEEN", "AND", "OR" + )); + + // 最大条件嵌套深度 + private static final int MAX_CONDITION_DEPTH = 3; + + // 最大表达式节点数 + private static final int MAX_EXPRESSION_NODES = 20; + + private int currentDepth = 0; + private int nodeCount = 0; + + public void checkConditionSafety(Expression expr, Set allowColumns) { + try { + // 重置计数器 + currentDepth = 0; + nodeCount = 0; + + // 开始安全检查 + expr.accept(new ExpressionVisitorAdapter() { + @Override + protected void visitBinaryExpression(BinaryExpression expr) { + checkNodeCount(); + checkOperator(expr.getStringExpression()); + super.visitBinaryExpression(expr); + } + + @Override + public void visit(Column column) { + checkNodeCount(); + String colName = column.getColumnName(); + if (!allowColumns.contains(colName)) { + throw new SecurityException("非法查询列: " + colName); + } + } + + @Override + public void visit(Function function) { + throw new SecurityException("where 条件不允许使用函数"); + } + + @Override + public void visit(AndExpression expr) { + enterNestedCondition(); + super.visit(expr); + exitNestedCondition(); + } + + @Override + public void visit(OrExpression expr) { + enterNestedCondition(); + super.visit(expr); + exitNestedCondition(); + } + + @Override + public void visit(NotExpression expr) { + enterNestedCondition(); + super.visit(expr); + exitNestedCondition(); + } + + @Override + public void visit(LikeExpression expr) { + // 检查LIKE模式是否包含通配符攻击 + String pattern = expr.getRightExpression().toString(); + if (pattern.matches(".*%[^%]{50,}.*")) { + throw new SecurityException("非法通配符"); + } + super.visit(expr); + } + + @Override + public void visit(JdbcParameter parameter) { + // 允许参数化查询参数 + checkNodeCount(); + } + + @Override + public void visit(LongValue value) { + checkNodeCount(); + } + + @Override + public void visit(StringValue value) { + checkNodeCount(); + // 检查字符串值是否包含潜在危险内容 + String str = value.getValue(); + if (str.length() > 100) { + throw new SecurityException("字符串过长"); + } + if (str.matches(".*[\\x00-\\x1F].*")) { + throw new SecurityException("非法字符串"); + } + } + + private void checkOperator(String operator) { + if (!ALLOWED_OPERATORS.contains(operator.toUpperCase())) { + throw new SecurityException("非法操作: " + operator); + } + } + + private void enterNestedCondition() { + currentDepth++; + if (currentDepth > MAX_CONDITION_DEPTH) { + throw new SecurityException("条件嵌套深度过深"); + } + } + + private void exitNestedCondition() { + currentDepth--; + } + + private void checkNodeCount() { + nodeCount++; + if (nodeCount > MAX_EXPRESSION_NODES) { + throw new SecurityException("条件表达式节点数过多"); + } + } + }); + } catch (Exception e) { + throw new SecurityException("条件语句校验失败:", e); + } + } +} diff --git a/easyflow-modules/easyflow-module-job/pom.xml b/easyflow-modules/easyflow-module-job/pom.xml new file mode 100644 index 0000000..af85467 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-job + + + + org.springframework.boot + spring-boot-starter-quartz + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + tech.easyflow + easyflow-common-base + + + tech.easyflow + easyflow-common-satoken + + + tech.easyflow + easyflow-common-web + + + tech.easyflow + easyflow-module-log + + + tech.easyflow + easyflow-module-ai + + + diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/config/JobModuleConfig.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/config/JobModuleConfig.java new file mode 100644 index 0000000..c7ac9c0 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/config/JobModuleConfig.java @@ -0,0 +1,13 @@ +package tech.easyflow.job.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("tech.easyflow.job.mapper") +public class JobModuleConfig { + + public JobModuleConfig() { + System.out.println("启用模块 >>>>>>>>>> module-job"); + } +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJob.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJob.java new file mode 100644 index 0000000..ece2273 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJob.java @@ -0,0 +1,15 @@ +package tech.easyflow.job.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.job.entity.base.SysJobBase; + + +/** + * 系统任务表 实体类。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@Table(value = "tb_sys_job", comment = "系统任务表") +public class SysJob extends SysJobBase { +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJobLog.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJobLog.java new file mode 100644 index 0000000..80a0754 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/SysJobLog.java @@ -0,0 +1,15 @@ +package tech.easyflow.job.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.job.entity.base.SysJobLogBase; + + +/** + * 系统任务日志 实体类。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@Table(value = "tb_sys_job_log", comment = "系统任务日志") +public class SysJobLog extends SysJobLogBase { +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobBase.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobBase.java new file mode 100644 index 0000000..708a8c5 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobBase.java @@ -0,0 +1,242 @@ +package tech.easyflow.job.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class SysJobBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 任务名称 + */ + @Column(comment = "任务名称") + private String jobName; + + /** + * 任务类型 + */ + @Column(comment = "任务类型") + private Integer jobType; + + /** + * 任务参数 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "任务参数") + private Map jobParams; + + /** + * cron表达式 + */ + @Column(comment = "cron表达式") + private String cronExpression; + + /** + * 是否并发执行 + */ + @Column(comment = "是否并发执行") + private Integer allowConcurrent; + + /** + * 错过策略 + */ + @Column(comment = "错过策略") + private Integer misfirePolicy; + + /** + * 其他配置 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "其他配置") + private Map options; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + 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 getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public Integer getJobType() { + return jobType; + } + + public void setJobType(Integer jobType) { + this.jobType = jobType; + } + + public Map getJobParams() { + return jobParams; + } + + public void setJobParams(Map jobParams) { + this.jobParams = jobParams; + } + + public String getCronExpression() { + return cronExpression; + } + + public void setCronExpression(String cronExpression) { + this.cronExpression = cronExpression; + } + + public Integer getAllowConcurrent() { + return allowConcurrent; + } + + public void setAllowConcurrent(Integer allowConcurrent) { + this.allowConcurrent = allowConcurrent; + } + + public Integer getMisfirePolicy() { + return misfirePolicy; + } + + public void setMisfirePolicy(Integer misfirePolicy) { + this.misfirePolicy = misfirePolicy; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobLogBase.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobLogBase.java new file mode 100644 index 0000000..688e37b --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/entity/base/SysJobLogBase.java @@ -0,0 +1,171 @@ +package tech.easyflow.job.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; + + +public class SysJobLogBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 任务ID + */ + @Column(comment = "任务ID") + private BigInteger jobId; + + /** + * 任务名称 + */ + @Column(comment = "任务名称") + private String jobName; + + /** + * 任务参数 + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "任务参数") + private Map jobParams; + + /** + * 执行结果 + */ + @Column(comment = "执行结果") + private String jobResult; + + /** + * 错误信息 + */ + @Column(comment = "错误信息") + private String errorInfo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 开始时间 + */ + @Column(comment = "开始时间") + private Date startTime; + + /** + * 结束时间 + */ + @Column(comment = "结束时间") + private Date endTime; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getJobId() { + return jobId; + } + + public void setJobId(BigInteger jobId) { + this.jobId = jobId; + } + + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public Map getJobParams() { + return jobParams; + } + + public void setJobParams(Map jobParams) { + this.jobParams = jobParams; + } + + public String getJobResult() { + return jobResult; + } + + public void setJobResult(String jobResult) { + this.jobResult = jobResult; + } + + public String getErrorInfo() { + return errorInfo; + } + + public void setErrorInfo(String errorInfo) { + this.errorInfo = errorInfo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/BaseQuartzJob.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/BaseQuartzJob.java new file mode 100644 index 0000000..bf500a6 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/BaseQuartzJob.java @@ -0,0 +1,72 @@ +package tech.easyflow.job.job; + +import cn.hutool.core.exceptions.ExceptionUtil; +import com.alibaba.fastjson.JSON; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.easyflow.common.constant.enums.EnumJobExecStatus; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.entity.SysJobLog; +import tech.easyflow.job.service.SysJobLogService; + +import java.util.Date; + +public abstract class BaseQuartzJob implements Job { + + protected Logger log = LoggerFactory.getLogger(getClass()); + + private final static ThreadLocal TIME_RECORD = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext ctx) throws JobExecutionException { + + SysJob job = (SysJob) ctx.getMergedJobDataMap().get(JobConstant.JOB_MAP_BEAN_NAME); + + try { + beforeExecute(ctx, job); + Object result = doExecute(ctx, job); + afterExecute(ctx, job, result, null); + } catch (Exception e) { + log.error("quartz 任务执行报错:", e); + afterExecute(ctx, job, null, e); + } + } + + protected void beforeExecute(JobExecutionContext ctx, SysJob job) { + TIME_RECORD.set(new Date()); + } + + protected void afterExecute(JobExecutionContext ctx, SysJob job, Object result, Exception e) { + Date startTime = TIME_RECORD.get(); + TIME_RECORD.remove(); + Date endTime = new Date(); + + SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobId(job.getId()); + sysJobLog.setJobName(job.getJobName()); + sysJobLog.setStatus(EnumJobExecStatus.SUCCESS.getCode()); + sysJobLog.setJobParams(job.getJobParams()); + if (result != null) { + sysJobLog.setJobResult(JSON.toJSONString(result)); + } + if (e != null) { + String message = ExceptionUtil.getRootCauseMessage(e); + if (message.length() > 1000) { + message = message.substring(0, 1000); + } + sysJobLog.setErrorInfo(message); + sysJobLog.setStatus(EnumJobExecStatus.FAIL.getCode()); + } + sysJobLog.setStartTime(startTime); + sysJobLog.setEndTime(endTime); + sysJobLog.setCreated(new Date()); + SysJobLogService service = SpringContextUtil.getBean(SysJobLogService.class); + service.save(sysJobLog); + } + + protected abstract Object doExecute(JobExecutionContext ctx, SysJob job) throws Exception; +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/JobConstant.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/JobConstant.java new file mode 100644 index 0000000..80d3ccd --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/JobConstant.java @@ -0,0 +1,13 @@ +package tech.easyflow.job.job; + +public interface JobConstant { + + String JOB_GROUP = "easyflow"; + String JOB_MAP_BEAN_NAME = "jobMapBean"; + + String BEAN_METHOD_KEY = "beanMethod"; + String JAVA_METHOD_KEY = "javaMethod"; + String WORKFLOW_KEY = "workflowId"; + String WORKFLOW_PARAMS_KEY = "workflowParams"; + String ACCOUNT_ID = "accountId"; +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJob.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJob.java new file mode 100644 index 0000000..1ff0d3e --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJob.java @@ -0,0 +1,16 @@ +package tech.easyflow.job.job; + +import org.quartz.JobExecutionContext; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.util.JobUtil; + +/** + * 可并发执行 + */ +public class QuartzJob extends BaseQuartzJob { + + @Override + protected Object doExecute(JobExecutionContext ctx, SysJob job) throws Exception { + return JobUtil.execute(job); + } +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJobNoConcurrent.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJobNoConcurrent.java new file mode 100644 index 0000000..3ec2305 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/job/QuartzJobNoConcurrent.java @@ -0,0 +1,18 @@ +package tech.easyflow.job.job; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.util.JobUtil; + +/** + * 禁止并发执行 + */ +@DisallowConcurrentExecution +public class QuartzJobNoConcurrent extends BaseQuartzJob { + + @Override + protected Object doExecute(JobExecutionContext ctx, SysJob job) throws Exception { + return JobUtil.execute(job); + } +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobLogMapper.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..8ea980d --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobLogMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.job.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.job.entity.SysJobLog; + +/** + * 系统任务日志 映射层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +public interface SysJobLogMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobMapper.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobMapper.java new file mode 100644 index 0000000..a9fc8cf --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/mapper/SysJobMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.job.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.job.entity.SysJob; + +/** + * 系统任务表 映射层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +public interface SysJobMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobLogService.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobLogService.java new file mode 100644 index 0000000..54a0d8a --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobLogService.java @@ -0,0 +1,14 @@ +package tech.easyflow.job.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.job.entity.SysJobLog; + +/** + * 系统任务日志 服务层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +public interface SysJobLogService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobService.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobService.java new file mode 100644 index 0000000..f340c06 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/SysJobService.java @@ -0,0 +1,24 @@ +package tech.easyflow.job.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.job.entity.SysJob; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 系统任务表 服务层。 + * + * @author xiaoma + * @since 2025-05-20 + */ +public interface SysJobService extends IService { + + void test(); + + void testParam(String a, Boolean b, Integer c); + + void addJob(SysJob job); + + void deleteJob(Collection ids); +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobLogServiceImpl.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..16ab062 --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.job.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.job.entity.SysJobLog; +import tech.easyflow.job.mapper.SysJobLogMapper; +import tech.easyflow.job.service.SysJobLogService; + +/** + * 系统任务日志 服务层实现。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@Service +public class SysJobLogServiceImpl extends ServiceImpl implements SysJobLogService { + +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobServiceImpl.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..b5f30cc --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/service/impl/SysJobServiceImpl.java @@ -0,0 +1,95 @@ +package tech.easyflow.job.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.quartz.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import tech.easyflow.common.constant.enums.EnumMisfirePolicy; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.job.JobConstant; +import tech.easyflow.job.job.QuartzJob; +import tech.easyflow.job.job.QuartzJobNoConcurrent; +import tech.easyflow.job.mapper.SysJobMapper; +import tech.easyflow.job.service.SysJobService; +import tech.easyflow.job.util.JobUtil; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Collection; + +/** + * 系统任务表 服务层实现。 + * + * @author xiaoma + * @since 2025-05-20 + */ +@Service +public class SysJobServiceImpl extends ServiceImpl implements SysJobService { + + protected Logger log = LoggerFactory.getLogger(SysJobServiceImpl.class); + + @Resource + private Scheduler scheduler; + + @Override + public void test() { + System.out.println("java bean 动态执行"); + } + + @Override + public void testParam(String a, Boolean b, Integer c) { + System.out.println("动态执行spring bean,执行参数:" + "a="+ a + ",b="+ b + ",c="+ c); + } + + @Override + public void addJob(SysJob job) { + Integer allowConcurrent = job.getAllowConcurrent(); + Class jobClass = allowConcurrent == 1 ? QuartzJob.class : QuartzJobNoConcurrent.class; + + JobDetail jobDetail = JobBuilder.newJob(jobClass) + .withIdentity(JobUtil.getJobKey(job)) + .build(); + + jobDetail.getJobDataMap().put(JobConstant.JOB_MAP_BEAN_NAME, job); + + CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + + Integer misfirePolicy = job.getMisfirePolicy(); + if (EnumMisfirePolicy.MISFIRE_DO_NOTHING.getCode() == misfirePolicy) { + cron.withMisfireHandlingInstructionDoNothing(); + } + if (EnumMisfirePolicy.MISFIRE_FIRE_AND_PROCEED.getCode() == misfirePolicy) { + cron.withMisfireHandlingInstructionFireAndProceed(); + } + if (EnumMisfirePolicy.MISFIRE_IGNORE_MISFIRES.getCode() == misfirePolicy) { + cron.withMisfireHandlingInstructionIgnoreMisfires(); + } + + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity(JobUtil.getTriggerKey(job)) + .withSchedule(cron).build(); + + try { + scheduler.scheduleJob(jobDetail,trigger); + } catch (SchedulerException e) { + log.error("启动任务失败:", e); + throw new RuntimeException(e); + } + } + + @Override + public void deleteJob(Collection ids) { + try { + for (Serializable id : ids) { + SysJob sysJob = new SysJob(); + sysJob.setId(new BigInteger(id.toString())); + scheduler.deleteJob(JobUtil.getJobKey(sysJob)); + } + } catch (SchedulerException e) { + log.error("删除任务失败:", e); + throw new RuntimeException(e); + } + } +} diff --git a/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/util/JobUtil.java b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/util/JobUtil.java new file mode 100644 index 0000000..a0085de --- /dev/null +++ b/easyflow-modules/easyflow-module-job/src/main/java/tech/easyflow/job/util/JobUtil.java @@ -0,0 +1,171 @@ +package tech.easyflow.job.util; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.tenant.TenantManager; +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.runtime.ChainExecutor; +import org.quartz.JobKey; +import org.quartz.TriggerKey; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.constant.enums.EnumJobType; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.job.entity.SysJob; +import tech.easyflow.job.job.JobConstant; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.service.SysAccountService; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; + +public class JobUtil { + + /** + * sysJobService.test() + */ + public static Object execSpringBean(SysJob job) { + Map jobParams = job.getJobParams(); + if (jobParams != null) { + String beanMethod = jobParams.get(JobConstant.BEAN_METHOD_KEY).toString(); + String[] strings = StrUtil.subBefore(beanMethod, "(", false).split("\\."); + Object bean = SpringContextUtil.getBean(strings[0]); + String param = StrUtil.subBetween(beanMethod, "(", ")"); + try { + // 调用方法并传递参数 + return invoke(bean, strings[1], getParams(param)); + } catch (Exception e) { + throw new RuntimeException("执行 beanMethod 报错:", e); + } + } + return null; + } + + /** + * tech.easyflow.job.util.JobUtil.execTest("test",1,0.52D,100L) + * @param job + */ + public static Object execJavaClass(SysJob job) { + Map jobParams = job.getJobParams(); + if (jobParams != null) { + try { + String javaMethod = jobParams.get(JobConstant.JAVA_METHOD_KEY).toString(); + String before = StrUtil.subBefore(javaMethod, "(", false); + String[] strings = before.split("\\."); + String className = String.join(".", Arrays.copyOf(strings, strings.length - 1)); + String methodName = strings[strings.length - 1]; + String param = StrUtil.subBetween(javaMethod, "(", ")"); + Object obj = Class.forName(className).getDeclaredConstructor().newInstance();; + return invoke(obj, methodName, getParams(param)); + } catch (Exception e) { + throw new RuntimeException("执行 javaMethod 报错: ",e); + } + } + return null; + } + + public static Object execWorkFlow(SysJob job) { + Map jobParams = job.getJobParams(); + JSONObject obj = new JSONObject(jobParams); + String workflowId = obj.getString(JobConstant.WORKFLOW_KEY); + JSONObject params = obj.getJSONObject(JobConstant.WORKFLOW_PARAMS_KEY); + + ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class); + Object accountId = obj.get(JobConstant.ACCOUNT_ID); + SysAccountService accountService = SpringContextUtil.getBean(SysAccountService.class); + + try { + TenantManager.ignoreTenantCondition(); + + ChainDefinition chain = executor.getDefinitionRepository().getChainDefinitionById(workflowId); + if (chain != null) { + if (accountId != null) { + // 设置的归属者 + SysAccount account = accountService.getById(accountId.toString()); + if (account != null) { + params.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount()); + } + } + return executor.execute(workflowId, params); + } + } finally { + TenantManager.restoreTenantCondition(); + } + return null; + } + + public static Object execute(SysJob job) { + Object res = null; + Integer jobType = job.getJobType(); + if (EnumJobType.TINY_FLOW.getCode() == jobType) { + res = execWorkFlow(job); + } + if (EnumJobType.SPRING_BEAN.getCode() == jobType) { + res = execSpringBean(job); + } + if (EnumJobType.JAVA_CLASS.getCode() == jobType) { + res = execJavaClass(job); + } + return res; + } + + public void execTest(String a,Integer b,Double c,Long d) { + System.out.println("动态执行方法,执行参数:" + "a="+ a + ",b="+ b + ",c="+ c + ",d="+ d); + } + + private static Object[] getParams(String param) { + if (StrUtil.isEmpty(param)) { + return new Object[]{new Class[]{}, new Object[]{}}; + } + String[] splits = param.split(","); + Object[] res = new Object[2]; + Object[] params = new Object[splits.length]; + Class[] paramTypes = new Class[splits.length]; + for (int i = 0; i < splits.length; i++) { + String split = splits[i].trim(); + if (split.startsWith("\"")) { + params[i] = split.substring(1, split.length() - 1); + paramTypes[i] = String.class; + } else if ("true".equals(split) || "false".equals(split)) { + params[i] = Boolean.valueOf(split); + paramTypes[i] = Boolean.class; + } else if (split.endsWith("L")) { + params[i] = Long.valueOf(split.substring(0, split.length() - 1)); + paramTypes[i] = Long.class; + } else if (split.endsWith("D")) { + params[i] = Double.valueOf(split.substring(0, split.length() - 1)); + paramTypes[i] = Double.class; + } else if (split.endsWith("F")) { + params[i] = Float.valueOf(split.substring(0, split.length() - 1)); + paramTypes[i] = Float.class; + } else { + params[i] = Integer.valueOf(split); + paramTypes[i] = Integer.class; + } + } + res[0] = paramTypes; + res[1] = params; + return res; + } + + private static Object invoke(Object bean, String methodName, Object[] params) throws Exception { + Object[] args = (Object[]) params[1]; + if (ArrayUtil.isEmpty(params[1])) { + Method method = bean.getClass().getDeclaredMethod(methodName); + return method.invoke(bean); + } else { + Method method = bean.getClass().getDeclaredMethod(methodName, (Class[]) params[0]); + return method.invoke(bean, args); + } + } + + public static JobKey getJobKey(SysJob job) { + return JobKey.jobKey(job.getId().toString(), JobConstant.JOB_GROUP); + } + + public static TriggerKey getTriggerKey(SysJob job) { + return TriggerKey.triggerKey(job.getId().toString(), JobConstant.JOB_GROUP); + } +} diff --git a/easyflow-modules/easyflow-module-log/pom.xml b/easyflow-modules/easyflow-module-log/pom.xml new file mode 100644 index 0000000..622af57 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-log + easyflow-module-log + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + tech.easyflow + easyflow-common-web + + + tech.easyflow + easyflow-common-satoken + + + + org.javassist + javassist + 3.29.2-GA + + + diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/ActionTypes.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/ActionTypes.java new file mode 100644 index 0000000..0fa4efb --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/ActionTypes.java @@ -0,0 +1,9 @@ +package tech.easyflow.log; + +public class ActionTypes { + + public static final String INSERT = "INSERT"; + public static final String DELETE = "DELETE"; + public static final String UPDATE = "UPDATE"; + public static final String QUERY = "QUERY"; +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogAspect.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogAspect.java new file mode 100644 index 0000000..344cc03 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogAspect.java @@ -0,0 +1,127 @@ +package tech.easyflow.log; + +import jakarta.servlet.http.HttpServletRequest; +import tech.easyflow.common.util.RequestUtil; +import tech.easyflow.common.util.StringUtil; + +import tech.easyflow.log.annotation.LogRecord; +import tech.easyflow.log.entity.WriteLog; +import tech.easyflow.log.mapper.WriteLogMapper; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import cn.dev33.satoken.stp.StpUtil; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.Date; +import java.util.Enumeration; + + +@Aspect +@Component +public class LogAspect { + + private static final int maxLengthOfParaValue = 512; + + private final WriteLogMapper logService; + private final LogRecordProperties config; + + public LogAspect(WriteLogMapper logService, LogRecordProperties config) { + this.logService = logService; + this.config = config; + } + + @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) " + + "|| execution(* tech.easyflow.common.web.controller.BaseCurdController.*(..))") + public void pointcut() { + } + + @Around("pointcut()") + public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + + String servletPath = request.getServletPath(); + + //匹配前缀 + if (StringUtil.hasText(config.getRecordActionPrefix()) && !servletPath.startsWith(config.getRecordActionPrefix())) { + return proceedingJoinPoint.proceed(); + } + + MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); + Class controllerClass = signature.getDeclaringType(); + Method method = signature.getMethod(); + String params = getRequestParamsString(request); + + try { + return proceedingJoinPoint.proceed(); + } finally { + WriteLog sysLog = new WriteLog(); + LogRecord logRecord = method.getAnnotation(LogRecord.class); + if (StpUtil.isLogin()) { + + BigInteger accountId = SaTokenUtil.getLoginAccount().getId(); + sysLog.setAccountId(accountId); + } + sysLog.setActionName(buildActionName(logRecord, method)); + sysLog.setActionType(logRecord != null ? logRecord.actionType() : null); + sysLog.setActionClass(controllerClass.getName()); + sysLog.setActionMethod(method.getName()); + sysLog.setActionUrl(request.getRequestURL().toString()); + sysLog.setActionIp(RequestUtil.getIpAddress(request)); + sysLog.setActionParams(params); + sysLog.setStatus(1); + sysLog.setCreated(new Date()); + + logService.insert(sysLog); + } + } + + private String buildActionName(LogRecord logRecord, Method method) { + if (logRecord != null && StringUtil.hasText(logRecord.value())) { + return logRecord.value(); + } else { + //todo 这里可以通过方法名,去获取 Controller 的实体类,在获取其表备注信息,进一步进行判断 + return method.getName(); + } + } + + private String getRequestParamsString(HttpServletRequest request) { + StringBuilder sb = new StringBuilder(); + Enumeration e = request.getParameterNames(); + if (e.hasMoreElements()) { + while (e.hasMoreElements()) { + String name = e.nextElement(); + String[] values = request.getParameterValues(name); + if (values.length == 1) { + sb.append(name).append("="); + if (values[0] != null && values[0].length() > maxLengthOfParaValue) { + sb.append(values[0], 0, maxLengthOfParaValue).append("..."); + } else { + sb.append(values[0]); + } + } else { + sb.append(name).append("[]={"); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(values[i]); + } + sb.append("}"); + } + sb.append(" "); + } + } + return sb.toString(); + } + + +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogRecordProperties.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogRecordProperties.java new file mode 100644 index 0000000..30b48a1 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/LogRecordProperties.java @@ -0,0 +1,19 @@ +package tech.easyflow.log; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "easyflow.log-record") +public class LogRecordProperties { + + private String recordActionPrefix; + + public String getRecordActionPrefix() { + return recordActionPrefix; + } + + public void setRecordActionPrefix(String recordActionPrefix) { + this.recordActionPrefix = recordActionPrefix; + } +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogRecord.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogRecord.java new file mode 100644 index 0000000..0f37006 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogRecord.java @@ -0,0 +1,18 @@ +package tech.easyflow.log.annotation; + +import java.lang.annotation.*; + +/** + * @author michael yang (fuhai999@gmail.com) + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface LogRecord { + + String value(); + + String actionType() default ""; + + +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogReporterDisabled.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogReporterDisabled.java new file mode 100644 index 0000000..f7b62d6 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/annotation/LogReporterDisabled.java @@ -0,0 +1,13 @@ +package tech.easyflow.log.annotation; + + +import java.lang.annotation.*; + +/** + * 标记后,LogReporterDisabled 将跳过此方法或类的日志输出 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface LogReporterDisabled { +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/config/LogModuleConfig.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/config/LogModuleConfig.java new file mode 100644 index 0000000..91330d7 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/config/LogModuleConfig.java @@ -0,0 +1,40 @@ +package tech.easyflow.log.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import tech.easyflow.log.reporter.ActionLogReporterProperties; +import tech.easyflow.log.reporter.ActionReportInterceptor; + +@MapperScan("tech.easyflow.log.mapper") +@Configuration +public class LogModuleConfig implements WebMvcConfigurer { + + private final ActionLogReporterProperties logProperties; + private final ActionReportInterceptor actionReportInterceptor; + + public LogModuleConfig(ActionLogReporterProperties logProperties, + ActionReportInterceptor actionReportInterceptor) { + this.logProperties = logProperties; + this.actionReportInterceptor = actionReportInterceptor; + + System.out.println("启用模块 >>>>>>>>>> module-log"); + } + + + /** + * 注册日志拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + if (!logProperties.isEnabled()) { + return; + } + + registry.addInterceptor(actionReportInterceptor) + .addPathPatterns(logProperties.getIncludePatterns().toArray(new String[0])) + .excludePathPatterns(logProperties.getExcludePatterns().toArray(new String[0])); + } + +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/entity/WriteLog.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/entity/WriteLog.java new file mode 100644 index 0000000..e9747e6 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/entity/WriteLog.java @@ -0,0 +1,181 @@ +package tech.easyflow.log.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + +@Table(value = "tb_sys_log", comment = "操作日志表") +public class WriteLog implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id(keyType = KeyType.Generator, value = "snowFlakeId") + private BigInteger id; + + /** + * 操作人 + */ + @Column(comment = "操作人") + private BigInteger accountId; + + /** + * 操作名称 + */ + @Column(comment = "操作名称") + private String actionName; + + /** + * 操作的类型 + */ + @Column(comment = "操作的类型") + private String actionType; + + /** + * 操作涉及的类 + */ + @Column(comment = "操作涉及的类") + private String actionClass; + + /** + * 操作涉及的方法 + */ + @Column(comment = "操作涉及的方法") + private String actionMethod; + + /** + * 操作涉及的 URL 地址 + */ + @Column(comment = "操作涉及的 URL 地址") + private String actionUrl; + + /** + * 操作涉及的用户 IP 地址 + */ + @Column(comment = "操作涉及的用户 IP 地址") + private String actionIp; + + /** + * 操作请求参数 + */ + @Column(comment = "操作请求参数") + private String actionParams; + + /** + * 操作请求body + */ + @Column(comment = "操作请求body") + private String actionBody; + + /** + * 操作状态 1 成功 9 失败 + */ + @Column(comment = "操作状态 1 成功 9 失败") + private Integer status; + + /** + * 操作时间 + */ + @Column(comment = "操作时间") + private Date created; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public String getActionType() { + return actionType; + } + + public void setActionType(String actionType) { + this.actionType = actionType; + } + + public String getActionClass() { + return actionClass; + } + + public void setActionClass(String actionClass) { + this.actionClass = actionClass; + } + + public String getActionMethod() { + return actionMethod; + } + + public void setActionMethod(String actionMethod) { + this.actionMethod = actionMethod; + } + + public String getActionUrl() { + return actionUrl; + } + + public void setActionUrl(String actionUrl) { + this.actionUrl = actionUrl; + } + + public String getActionIp() { + return actionIp; + } + + public void setActionIp(String actionIp) { + this.actionIp = actionIp; + } + + public String getActionParams() { + return actionParams; + } + + public void setActionParams(String actionParams) { + this.actionParams = actionParams; + } + + public String getActionBody() { + return actionBody; + } + + public void setActionBody(String actionBody) { + this.actionBody = actionBody; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/mapper/WriteLogMapper.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/mapper/WriteLogMapper.java new file mode 100644 index 0000000..00b3d9c --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/mapper/WriteLogMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.log.mapper; + +import tech.easyflow.log.entity.WriteLog; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-01-28 + */ +public interface WriteLogMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionLogReporterProperties.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionLogReporterProperties.java new file mode 100644 index 0000000..3a49fc6 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionLogReporterProperties.java @@ -0,0 +1,80 @@ +package tech.easyflow.log.reporter; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +@Component +@ConfigurationProperties(prefix = "easyflow.log.reporter") +public class ActionLogReporterProperties { + + /** + * 是否启用 Action 报告 + */ + private boolean enabled = true; + + /** + * 采样率(0.0 ~ 1.0),1.0 = 100% + */ + private double sampleRate = 1.0; + + /** + * 包含的路径模式 + */ + private List includePatterns = Arrays.asList("/**"); + + /** + * 排除的路径模式 + */ + private List excludePatterns = Arrays.asList( + "/static/**", + "/assets/**", + "/js/**", + "/css/**", + "/images/**", + "/favicon.ico", + "/actuator/**", + "*.js", + "*.css", + "*.png", + "*.jpg", + "*.gif", + "*.ico" + ); + + + // getter and setter + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public double getSampleRate() { + return sampleRate; + } + + public void setSampleRate(double sampleRate) { + this.sampleRate = sampleRate; + } + + public List getIncludePatterns() { + return includePatterns; + } + + public void setIncludePatterns(List includePatterns) { + this.includePatterns = includePatterns; + } + + public List getExcludePatterns() { + return excludePatterns; + } + + public void setExcludePatterns(List excludePatterns) { + this.excludePatterns = excludePatterns; + } +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionReportInterceptor.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionReportInterceptor.java new file mode 100644 index 0000000..b9a118e --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ActionReportInterceptor.java @@ -0,0 +1,282 @@ +package tech.easyflow.log.reporter; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.util.ContentCachingResponseWrapper; +import tech.easyflow.common.util.RequestUtil; +import tech.easyflow.log.annotation.LogReporterDisabled; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +@Component +public class ActionReportInterceptor implements HandlerInterceptor { + + private static final Logger logger = LoggerFactory.getLogger("ACTION_REPORT"); + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + static { + SDF.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 北京时间 + } + + private static final String DIVIDER = "─────────────────────────────────────────────────────────────────────────────"; + private static final int MAX_JSON_LENGTH = 512; + + private static final List SENSITIVE_KEYS = Arrays.asList("password", "passwd", "secret", "token", "key"); + + private final ActionLogReporterProperties logProperties; + + public ActionReportInterceptor(ActionLogReporterProperties logProperties) { + this.logProperties = logProperties; + } + + // 存储是否需要记录日志的标志 + private static final ThreadLocal SHOULD_LOG = ThreadLocal.withInitial(() -> false); + // 存储开始时间 + private static final ThreadLocal START_TIME = new ThreadLocal<>(); + // 存储 HandlerMethod,供 afterCompletion 使用 + private static final ThreadLocal HANDLER_METHOD = new ThreadLocal<>(); + // 存储 ModelAndView,供 afterCompletion 使用 + private static final ThreadLocal MODEL_AND_VIEW = new ThreadLocal<>(); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + // 默认不记录 + SHOULD_LOG.set(false); + + if (!logProperties.isEnabled() || !(handler instanceof HandlerMethod)) { + return true; + } + + HandlerMethod hm = (HandlerMethod) handler; + Method method = hm.getMethod(); + + // 采样 + if (logProperties.getSampleRate() < 1.0 && Math.random() > logProperties.getSampleRate()) { + return true; + } + + // 排除 LogReporterDisabled 注解 + if (method.isAnnotationPresent(LogReporterDisabled.class) || + method.getDeclaringClass().isAnnotationPresent(LogReporterDisabled.class)) { + return true; + } + + // 设置标志:需要记录日志 + SHOULD_LOG.set(true); + START_TIME.set(System.currentTimeMillis()); + HANDLER_METHOD.set(hm); + + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { + // 仅暂存 modelAndView,供 afterCompletion 使用 + if (SHOULD_LOG.get()) { + MODEL_AND_VIEW.set(modelAndView); + } + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + try { + if (!Boolean.TRUE.equals(SHOULD_LOG.get())) { + return; + } + + HandlerMethod hm = HANDLER_METHOD.get(); + if (hm == null) return; + + Method method = hm.getMethod(); + ModelAndView modelAndView = MODEL_AND_VIEW.get(); + + StringBuilder sb = new StringBuilder(2048); + sb.append("\n").append(DIVIDER).append('\n'); + + String timestamp = SDF.format(new Date()); + sb.append("EasyFlow action report -------- ").append(timestamp).append(" -------------------------\n"); + sb.append("Request : ").append(request.getMethod()) + .append(" ").append(request.getRequestURI()).append("\n"); + + // 打印参数(GET / POST 表单参数),脱敏 + Map params = request.getParameterMap(); + if (!params.isEmpty()) { + Map maskedParams = new LinkedHashMap<>(); + for (Map.Entry entry : params.entrySet()) { + String key = entry.getKey(); + String[] values = entry.getValue(); + if (values != null && values.length == 1) { + maskedParams.put(key, isSensitive(key) ? "***" : values[0]); + } else { + maskedParams.put(key, isSensitive(key) ? "***" : values); + } + } + sb.append("Params : ").append(JSON.toJSONString(maskedParams)).append("\n"); + } + + // ====== 读取 POST Body ====== + String methodStr = request.getMethod(); + if ("POST".equalsIgnoreCase(methodStr) || "PUT".equalsIgnoreCase(methodStr) || "PATCH".equalsIgnoreCase(methodStr)) { + String body = RequestUtil.readBodyString(request); + if (body != null && !body.trim().isEmpty()) { + try { + Object obj = JSON.parse(body); + if (obj instanceof Map) { + obj = maskSensitiveValues((Map) obj); + } + String maskedBody = JSON.toJSONString(obj); + if (maskedBody.length() > MAX_JSON_LENGTH) { + maskedBody = maskedBody.substring(0, MAX_JSON_LENGTH) + " ... (truncated)"; + } + sb.append("Body : ").append(maskedBody.replace("\n", " ")).append("\n"); + } catch (Exception e) { + String truncated = body.length() > 200 ? body.substring(0, 200) + "..." : body; + sb.append("Body : ").append(truncated).append("\n"); + } + } + } + + // Controller & Method 位置(含行号) + Class clazz = method.getDeclaringClass(); + String fileName = clazz.getSimpleName() + ".java"; + int lineNumber = JavassistLineNumUtils.getLineNumber(method); + String lineStr = lineNumber > 0 ? String.valueOf(lineNumber) : "?"; + String controllerLocation = clazz.getName() + ".(" + fileName + ":" + lineStr + ")"; + sb.append("Controller : ").append(controllerLocation).append("\n"); + + // 构建方法签名 + sb.append("Method : ").append(buildMethodSignature(method)); + + // ====== 处理 ModelAndView ====== + if (modelAndView != null && modelAndView.getViewName() != null) { + sb.append('\n').append("Render : ").append(modelAndView.getViewName()); + + Map model = modelAndView.getModel(); + if (!model.isEmpty()) { + sb.append("\nModel : "); + try { + String json = JSON.toJSONString(maskSensitiveValues(model), JSONWriter.Feature.WriteMapNullValue); + if (json.length() > MAX_JSON_LENGTH) { + json = json.substring(0, MAX_JSON_LENGTH) + " ... (truncated)"; + } + sb.append(json.replace("\n", " ")); + } catch (Exception e) { + sb.append("(failed to serialize)"); + } + } + } else { + sb.append('\n').append("Render : (none)"); + } + + // ====== 尝试获取最终响应体 ====== + if (response instanceof ContentCachingResponseWrapper) { + ContentCachingResponseWrapper wrapper = (ContentCachingResponseWrapper) response; + byte[] body = wrapper.getContentAsByteArray(); + if (body.length > 0) { + String content = new String(body, 0, Math.min(body.length, 1024), StandardCharsets.UTF_8); + try { + Object obj = JSON.parse(content); + if (obj instanceof Map) { + obj = maskSensitiveValues((Map) obj); + } + String masked = JSON.toJSONString(obj); + if (masked.length() > MAX_JSON_LENGTH) { + masked = masked.substring(0, MAX_JSON_LENGTH) + " ... (truncated)"; + } + sb.append('\n').append("Response : ").append(masked.replace("\n", " ")); + } catch (Exception e) { + String truncated = content.length() > 200 ? content.substring(0, 200) + "..." : content; + sb.append('\n').append("Response : ").append(truncated); + } + } + } + + // ====== 异常信息 ====== + if (ex != null) { + sb.append('\n') + .append("Status : FAILED\n") + .append("Exception : ").append(ex.getClass().getSimpleName()) + .append(": ").append(ex.getMessage() != null ? ex.getMessage().split("\n")[0] : "Unknown"); + } + + // ====== 耗时 ====== + Long start = START_TIME.get(); + long took = start != null ? System.currentTimeMillis() - start : -1; + if (took >= 0) { + sb.append('\n') + .append("----------------------------------- took ").append(took).append(" ms --------------------------------"); + } else { + sb.append('\n').append("----------------------------------- took ? ms --------------------------------"); + } + + sb.append('\n').append(DIVIDER); + + logger.info(sb.toString()); + + } finally { + // 清理 ThreadLocal + SHOULD_LOG.remove(); + START_TIME.remove(); + HANDLER_METHOD.remove(); + MODEL_AND_VIEW.remove(); + } + } + + // ====================== 工具方法 ====================== + + private boolean isSensitive(String key) { + return SENSITIVE_KEYS.stream().anyMatch(s -> key.toLowerCase().contains(s)); + } + + private Map maskSensitiveValues(Map model) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : model.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (isSensitive(key)) { + result.put(key, "***"); + } else if (value instanceof Map) { + result.put(key, maskSensitiveValues((Map) value)); + } else { + result.put(key, value); + } + } + return result; + } + + /** + * 构建方法签名:methodName(paramType paramName, ...) + */ + private String buildMethodSignature(Method method) { + StringBuilder sig = new StringBuilder(); + sig.append(method.getName()).append("("); + + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + sig.append(param.getType().getSimpleName()) + .append(" ") + .append(param.getName()); + + if (i < parameters.length - 1) { + sig.append(", "); + } + } + sig.append(")"); + return sig.toString(); + } +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/JavassistLineNumUtils.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/JavassistLineNumUtils.java new file mode 100644 index 0000000..d3be9c4 --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/JavassistLineNumUtils.java @@ -0,0 +1,44 @@ +package tech.easyflow.log.reporter; + + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.MethodInfo; + +import java.util.concurrent.ConcurrentHashMap; + +public class JavassistLineNumUtils { + + private static final ClassPool CLASS_POOL = ClassPool.getDefault(); + private static final ConcurrentHashMap LINE_CACHE = new ConcurrentHashMap<>(); + + /** + * 获取方法在源码中的起始行号 + */ + public static int getLineNumber(java.lang.reflect.Method method) { + String key = method.getDeclaringClass().getName() + "." + method.getName(); + return LINE_CACHE.computeIfAbsent(key, k -> { + try { + CtClass ctClass = CLASS_POOL.get(method.getDeclaringClass().getName()); + CtClass[] params = toCtClasses(method.getParameterTypes()); + CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName(), params); + + MethodInfo methodInfo = ctMethod.getMethodInfo(); + return methodInfo.getLineNumber(0); + + } catch (Exception e) { + return 0; + } + }); + } + + private static CtClass[] toCtClasses(Class[] parameterTypes) throws NotFoundException { + CtClass[] result = new CtClass[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + result[i] = CLASS_POOL.get(parameterTypes[i].getName()); + } + return result; + } +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ResponseCachingFilter.java b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ResponseCachingFilter.java new file mode 100644 index 0000000..7368b8e --- /dev/null +++ b/easyflow-modules/easyflow-module-log/src/main/java/tech/easyflow/log/reporter/ResponseCachingFilter.java @@ -0,0 +1,122 @@ +package tech.easyflow.log.reporter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; + +import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; + +/** + * 响应缓存 Filter,支持基于路径的排除规则 + */ +@Component +@Order(HIGHEST_PRECEDENCE) +@ConditionalOnProperty( + prefix = "easyflow.log.reporter", + name = "enabled", + havingValue = "true", + matchIfMissing = true // 默认开启 +) +public class ResponseCachingFilter implements Filter { + + @Autowired + private ActionLogReporterProperties logProperties; + + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String uri = httpRequest.getRequestURI(); + String method = httpRequest.getMethod(); + + // 1如果是 OPTIONS 请求,跳过(通常为预检) + if ("OPTIONS".equalsIgnoreCase(method)) { + chain.doFilter(request, response); + return; + } + +// // 检查是否为 SSE 请求 +// if (isSseRequest(httpRequest)) { +// chain.doFilter(request, response); +// return; +// } + + // 检查是否匹配排除路径 + if (isExcluded(uri)) { + chain.doFilter(request, response); + return; + } + + // 检查是否匹配包含路径(一般为 /**,可省略) + if (!isIncluded(uri)) { + chain.doFilter(request, response); + return; + } + + ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpRequest); + if (isSseRequest(httpRequest)) { + // SSE 请求不缓存 + chain.doFilter(requestWrapper, response); + return; + } + + + HttpServletResponse httpResponse = (HttpServletResponse) response; + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse); + try { + chain.doFilter(requestWrapper, responseWrapper); + } finally { + responseWrapper.copyBodyToResponse(); // 必须调用 + } + } + + private boolean isExcluded(String uri) { + return logProperties.getExcludePatterns().stream().anyMatch(p -> match(uri, p)); + } + + private boolean isIncluded(String uri) { + return logProperties.getIncludePatterns().stream().anyMatch(p -> match(uri, p)); + } + + /** + * 判断是否为 SSE 请求(基于标准 Accept 头) + */ + private boolean isSseRequest(HttpServletRequest request) { + String accept = request.getHeader("Accept"); + return accept != null && accept.contains("text/event-stream"); + } + + /** + * 简单的路径匹配(支持 * 和 **) + * 注意:这里简化实现,生产可替换为 AntPathMatcher + */ + private boolean match(String path, String pattern) { + if (pattern.equals("/**")) { + return true; + } + if (pattern.endsWith("/**")) { + String prefix = pattern.substring(0, pattern.length() - 3); + return path.startsWith(prefix); + } + if (pattern.endsWith("*")) { + String prefix = pattern.substring(0, pattern.length() - 1); + return path.startsWith(prefix); + } + if (pattern.contains("*") && !pattern.contains("/**")) { + // 支持 *.js, *.css + String p = pattern.replace("*", "").replace(".", "\\."); + return path.matches(".*" + p + ".*"); + } + return path.equals(pattern); + } +} \ No newline at end of file diff --git a/easyflow-modules/easyflow-module-system/pom.xml b/easyflow-modules/easyflow-module-system/pom.xml new file mode 100644 index 0000000..2d6760c --- /dev/null +++ b/easyflow-modules/easyflow-module-system/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + tech.easyflow + easyflow-modules + ${revision} + + + easyflow-module-system + easyflow-module-system + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + tech.easyflow + easyflow-common-base + + + tech.easyflow + easyflow-common-satoken + + + cn.hutool + hutool-crypto + + + tech.easyflow + easyflow-common-web + + + tech.easyflow + easyflow-module-log + + + diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysDictAutoConfig.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysDictAutoConfig.java new file mode 100644 index 0000000..26b4554 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysDictAutoConfig.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.config; + +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.dict.DictManager; +import tech.easyflow.common.dict.loader.DbDataLoader; +import tech.easyflow.system.entity.SysDict; +import tech.easyflow.system.mapper.*; +import tech.easyflow.system.service.SysDictService; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +import javax.annotation.Resource; +import java.util.List; + +@Configuration +public class SysDictAutoConfig { + + private SysDictService service; + + @Resource + private SysMenuMapper sysMenuMapper; + @Resource + private SysDeptMapper sysDeptMapper; + @Resource + private SysRoleMapper sysRoleMapper; + @Resource + private SysPositionMapper sysPositionMapper; + @Resource + private SysAccountMapper sysAccountMapper; + + public SysDictAutoConfig(SysDictService service) { + this.service = service; + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationStartup() { + + DictManager dictManager = SpringContextUtil.getBean(DictManager.class); + // 菜单表字典 + dictManager.putLoader(new DbDataLoader<>("sysMenu", sysMenuMapper, "id", "menu_title", "parent_id", "sort_no asc", false)); + // 部门表字典 + dictManager.putLoader(new DbDataLoader<>("sysDept", sysDeptMapper, "id", "dept_name", "parent_id", "sort_no asc", false)); + // 角色表字典 + dictManager.putLoader(new DbDataLoader<>("sysRole", sysRoleMapper, "id", "role_name", null, null, true)); + // 职位字典 + dictManager.putLoader(new DbDataLoader<>("sysPosition", sysPositionMapper, "id", "position_name", null, null, true)); + // 用户字典 + dictManager.putLoader(new DbDataLoader<>("sysAccount", sysAccountMapper, "id", "login_name", null, null, true)); + + List sysDicts = service.list(); + if (sysDicts != null) { + sysDicts.forEach(sysDict -> dictManager.putLoader(sysDict.buildLoader())); + } + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysModuleConfig.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysModuleConfig.java new file mode 100644 index 0000000..fad67b2 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/SysModuleConfig.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.ComponentScan; + +@MapperScan("tech.easyflow.system.mapper") +@AutoConfiguration +public class SysModuleConfig { + + public SysModuleConfig() { + System.out.println("启用模块 >>>>>>>>>> module-system"); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccount.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccount.java new file mode 100644 index 0000000..4823979 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccount.java @@ -0,0 +1,71 @@ +package tech.easyflow.system.entity; + +import cn.hutool.core.bean.BeanUtil; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.system.entity.base.SysAccountBase; +import com.alibaba.fastjson.annotation.JSONField; +import com.mybatisflex.annotation.RelationManyToMany; +import com.mybatisflex.annotation.Table; + +import java.math.BigInteger; +import java.util.List; + +/** + * 用户表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_account", comment = "用户表") +public class SysAccount extends SysAccountBase { + + @RelationManyToMany(joinTable = "tb_sys_account_role" + , joinSelfColumn = "account_id" + , joinTargetColumn = "role_id" + , targetTable = "tb_sys_role" + , targetField = "id" + , valueField = "id" + ) + private List roleIds; + + @RelationManyToMany(joinTable = "tb_sys_account_position" + , joinSelfColumn = "account_id" + , joinTargetColumn = "position_id" + , targetTable = "tb_sys_position" + , targetField = "id" + , valueField = "id" + ) + private List positionIds; + + public List getRoleIds() { + return roleIds; + } + + public void setRoleIds(List roleIds) { + this.roleIds = roleIds; + } + + public List getPositionIds() { + return positionIds; + } + + public void setPositionIds(List positionIds) { + this.positionIds = positionIds; + } + + @Override + @JSONField(serialize = false) + public String getPassword() { + return super.getPassword(); + } + + /** + * 转为登录用户 + */ + public LoginAccount toLoginAccount() { + LoginAccount loginAccount = new LoginAccount(); + BeanUtil.copyProperties(this, loginAccount); + return loginAccount; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountPosition.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountPosition.java new file mode 100644 index 0000000..6d9769e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountPosition.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysAccountPositionBase; +import com.mybatisflex.annotation.Table; + +/** + * 用户-职位表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_account_position", comment = "用户-职位表") +public class SysAccountPosition extends SysAccountPositionBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountRole.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountRole.java new file mode 100644 index 0000000..3a34204 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysAccountRole.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysAccountRoleBase; +import com.mybatisflex.annotation.Table; + +/** + * 用户-角色表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_account_role", comment = "用户-角色表") +public class SysAccountRole extends SysAccountRoleBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKey.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKey.java new file mode 100644 index 0000000..ddabb27 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKey.java @@ -0,0 +1,44 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.RelationOneToMany; +import com.mybatisflex.annotation.Table; +import tech.easyflow.system.entity.base.SysApiKeyBase; + +import java.math.BigInteger; +import java.util.List; + + +/** + * 实体类。 + * + * @author Administrator + * @since 2025-04-18 + */ +@Table("tb_sys_api_key") +public class SysApiKey extends SysApiKeyBase { + + public static final String KEY_Apikey = "Apikey"; + + @Column(ignore = true) + List permissionIds; + + @RelationOneToMany(selfField = "id", targetField = "apiKeyId", targetTable = "tb_sys_api_key_resource_mapping") + private List resourcePermissions; + + public List getResourcePermissions() { + return resourcePermissions; + } + + public void setResourcePermissions(List resourcePermissions) { + this.resourcePermissions = resourcePermissions; + } + + public List getPermissionIds() { + return permissionIds; + } + + public void setPermissionIds(List permissionIds) { + this.permissionIds = permissionIds; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResource.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResource.java new file mode 100644 index 0000000..a6dbefe --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResource.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.system.entity.base.SysApiKeyResourceBase; + + +/** + * 请求接口表 实体类。 + * + * @author 12076 + * @since 2025-12-01 + */ +@Table(value = "tb_sys_api_key_resource", comment = "请求接口表") +public class SysApiKeyResource extends SysApiKeyResourceBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResourceMapping.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResourceMapping.java new file mode 100644 index 0000000..37083d4 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysApiKeyResourceMapping.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.system.entity.base.SysApiKeyResourceMappingBase; + + +/** + * apikey-请求接口表 实体类。 + * + * @author 12076 + * @since 2025-12-01 + */ +@Table(value = "tb_sys_api_key_resource_mapping", comment = "apikey-请求接口表") +public class SysApiKeyResourceMapping extends SysApiKeyResourceMappingBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDept.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDept.java new file mode 100644 index 0000000..23a0900 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDept.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysDeptBase; +import com.mybatisflex.annotation.Table; + +/** + * 部门表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_dept", comment = "部门表") +public class SysDept extends SysDeptBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDict.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDict.java new file mode 100644 index 0000000..14f582d --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDict.java @@ -0,0 +1,96 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.common.util.SpringContextUtil; +import tech.easyflow.common.dict.Dict; +import tech.easyflow.common.dict.DictItem; +import tech.easyflow.common.dict.DictLoader; +import tech.easyflow.common.dict.DictType; +import tech.easyflow.common.dict.loader.DatabaseDictLoader; +import tech.easyflow.common.dict.loader.EnumDictLoader; +import tech.easyflow.system.entity.base.SysDictBase; +import tech.easyflow.system.service.SysDictItemService; +import com.mybatisflex.annotation.Table; +import com.mybatisflex.core.query.QueryWrapper; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + + +/** + * 系统配置表 实体类。 + * + * @author michael + * @since 2024-03-04 + */ + +@Table(value = "tb_sys_dict",comment = "系统字典表") +public class SysDict extends SysDictBase { + + public DictLoader buildLoader() { + Integer dictType = getDictType(); + if (dictType == null) { + return null; + } + + if (dictType == DictType.TABLE.getValue()) { + String tableName = (String) this.getOptions().get("tableName"); + String keyColumn = (String) this.getOptions().get("keyColumn"); + String labelColumn = (String) this.getOptions().get("labelColumn"); + String parentColumn = (String) this.getOptions().get("parentColumn"); + return new DatabaseDictLoader(this.getCode(), tableName, keyColumn, labelColumn, parentColumn, null); + } else if (dictType == DictType.ENUM.getValue()) { + String enumClassString = (String) this.getOptions().get("enumClass"); + String keyField = (String) this.getOptions().get("keyField"); + String labelField = (String) this.getOptions().get("labelField"); + + //noinspection rawtypes + Class enumClass = null; + try { + enumClass = Class.forName(enumClassString); + } catch (ClassNotFoundException e) { + //ignore + return null; + } + return new EnumDictLoader<>(this.getName(), this.getCode(), enumClass, keyField, labelField); + } else if (dictType == DictType.CUSTOM.getValue()) { + return new DictItemsLoader(this.getCode(), this.getId()); + } + + return null; + } + + public static class DictItemsLoader implements DictLoader { + private final String code; + private final BigInteger dictId; + private final SysDictItemService itemService; + + public DictItemsLoader(String code, BigInteger dictId) { + this.code = code; + this.dictId = dictId; + this.itemService = SpringContextUtil.getBean(SysDictItemService.class); + } + + @Override + public String code() { + return code; + } + + @Override + public Dict load(String keyword, Map parameters) { + QueryWrapper qw = QueryWrapper.create() + .eq(SysDictItem::getDictId, this.dictId) + .eq(SysDictItem::getStatus, 0) + .like(SysDictItem::getText, keyword); + List sysDictItems = itemService.list(qw); + + Dict dict = new Dict(); + if (sysDictItems != null) { + for (SysDictItem sysDictItem : sysDictItems) { + dict.addItem(new DictItem(sysDictItem.getValue(), sysDictItem.getText())); + } + } + return dict; + } + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDictItem.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDictItem.java new file mode 100644 index 0000000..6371f00 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysDictItem.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysDictItemBase; +import com.mybatisflex.annotation.Table; + +/** + * 数据字典内容 实体类。 + * + * @author michael + * @since 2024-03-04 + */ + +@Table(value = "tb_sys_dict_item", comment = "数据字典内容") +public class SysDictItem extends SysDictItemBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysLog.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysLog.java new file mode 100644 index 0000000..7318dda --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysLog.java @@ -0,0 +1,27 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysLogBase; +import com.mybatisflex.annotation.RelationManyToOne; +import com.mybatisflex.annotation.Table; + +/** + * 操作日志表 实体类。 + * + * @author michael + * @since 2024-03-04 + */ + +@Table(value = "tb_sys_log", comment = "操作日志表") +public class SysLog extends SysLogBase { + + @RelationManyToOne(selfField = "accountId") + private SysAccount account; + + public SysAccount getAccount() { + return account; + } + + public void setAccount(SysAccount account) { + this.account = account; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysMenu.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysMenu.java new file mode 100644 index 0000000..5c93c4c --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysMenu.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysMenuBase; +import com.mybatisflex.annotation.Table; + +/** + * 菜单表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_menu", comment = "菜单表") +public class SysMenu extends SysMenuBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysOption.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysOption.java new file mode 100644 index 0000000..82c9e9d --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysOption.java @@ -0,0 +1,24 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysOptionBase; +import com.mybatisflex.annotation.Table; + +/** + * 系统配置信息表。 实体类。 + * + * @author michael + * @since 2024-03-13 + */ + +@Table(value = "tb_sys_option", comment = "系统配置信息表") +public class SysOption extends SysOptionBase { + + public SysOption() { + } + + public SysOption(String key, String value) { + setKey(key); + setValue(value); + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysPosition.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysPosition.java new file mode 100644 index 0000000..90edb95 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysPosition.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysPositionBase; +import com.mybatisflex.annotation.Table; + +/** + * 职位表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_position", comment = "职位表") +public class SysPosition extends SysPositionBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRole.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRole.java new file mode 100644 index 0000000..eaecea7 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRole.java @@ -0,0 +1,40 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Column; +import tech.easyflow.system.entity.base.SysRoleBase; +import com.mybatisflex.annotation.Table; + +import java.math.BigInteger; +import java.util.List; + +/** + * 系统角色 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_role", comment = "系统角色") +public class SysRole extends SysRoleBase { + + @Column(ignore = true) + private List menuIds; + @Column(ignore = true) + private List deptIds; + + public List getMenuIds() { + return menuIds; + } + + public void setMenuIds(List menuIds) { + this.menuIds = menuIds; + } + + public List getDeptIds() { + return deptIds; + } + + public void setDeptIds(List deptIds) { + this.deptIds = deptIds; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleDept.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleDept.java new file mode 100644 index 0000000..4b19334 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleDept.java @@ -0,0 +1,16 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.system.entity.base.SysRoleDeptBase; + +/** + * 角色-部门表 实体类。 + * + * @author ArkLight + * @since 2025-11-19 + */ + +@Table(value = "tb_sys_role_dept", comment = "角色-部门表") +public class SysRoleDept extends SysRoleDeptBase { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleMenu.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleMenu.java new file mode 100644 index 0000000..f29ae88 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleMenu.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import tech.easyflow.system.entity.base.SysRoleMenuBase; +import com.mybatisflex.annotation.Table; + +/** + * 角色-菜单表 实体类。 + * + * @author ArkLight + * @since 2025-03-14 + */ + +@Table(value = "tb_sys_role_menu", comment = "角色-菜单表") +public class SysRoleMenu extends SysRoleMenuBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysUserFeedback.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysUserFeedback.java new file mode 100644 index 0000000..787897b --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysUserFeedback.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.entity; + +import com.mybatisflex.annotation.Table; +import tech.easyflow.system.entity.base.SysUserFeedbackBase; + + +/** + * 实体类。 + * + * @author 12076 + * @since 2025-12-30 + */ +@Table("tb_sys_user_feedback") +public class SysUserFeedback extends SysUserFeedbackBase { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountBase.java new file mode 100644 index 0000000..d15c3c2 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountBase.java @@ -0,0 +1,240 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class SysAccountBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 登录账号 + */ + @Column(comment = "登录账号") + private String loginName; + + /** + * 密码 + */ + @Column(comment = "密码") + private String password; + + /** + * 账户类型 + */ + @Column(comment = "账户类型") + private Integer accountType; + + /** + * 昵称 + */ + @Column(comment = "昵称") + private String nickname; + + /** + * 手机电话 + */ + @Column(comment = "手机电话") + private String mobile; + + /** + * 邮件 + */ + @Column(comment = "邮件") + private String email; + + /** + * 账户头像 + */ + @Column(comment = "账户头像") + private String avatar; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + 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 String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + 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 getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountPositionBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountPositionBase.java new file mode 100644 index 0000000..f295c41 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountPositionBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysAccountPositionBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 用户ID + */ + @Column(comment = "用户ID") + private BigInteger accountId; + + /** + * 职位ID + */ + @Column(comment = "职位ID") + private BigInteger positionId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public BigInteger getPositionId() { + return positionId; + } + + public void setPositionId(BigInteger positionId) { + this.positionId = positionId; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountRoleBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountRoleBase.java new file mode 100644 index 0000000..d7c9d7a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysAccountRoleBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysAccountRoleBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 用户ID + */ + @Column(comment = "用户ID") + private BigInteger accountId; + + /** + * 角色ID + */ + @Column(comment = "角色ID") + private BigInteger roleId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public BigInteger getRoleId() { + return roleId; + } + + public void setRoleId(BigInteger roleId) { + this.roleId = roleId; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java new file mode 100644 index 0000000..1ee8658 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java @@ -0,0 +1,127 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class SysApiKeyBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * apiKey + */ + @Column(comment = "apiKey") + private String apiKey; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 部门id + */ + @Column(comment = "部门id") + private BigInteger deptId; + + /** + * 租户id + */ + @Column(tenantId = true, comment = "租户id") + private BigInteger tenantId; + + /** + * 失效时间 + */ + @Column(comment = "失效时间") + private Date expiredAt; + + /** + * 创建人 + */ + @Column(comment = "创建人") + private BigInteger createdBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + 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 Date getExpiredAt() { + return expiredAt; + } + + public void setExpiredAt(Date expiredAt) { + this.expiredAt = expiredAt; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceBase.java new file mode 100644 index 0000000..a6dad3e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysApiKeyResourceBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * 请求接口 + */ + @Column(comment = "请求接口") + private String requestInterface; + + /** + * 标题 + */ + @Column(comment = "标题") + private String title; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getRequestInterface() { + return requestInterface; + } + + public void setRequestInterface(String requestInterface) { + this.requestInterface = requestInterface; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceMappingBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceMappingBase.java new file mode 100644 index 0000000..eba213e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyResourceMappingBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysApiKeyResourceMappingBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") + private BigInteger id; + + /** + * api_key_id + */ + @Column(comment = "api_key_id") + private BigInteger apiKeyId; + + /** + * 请求接口资源访问id + */ + @Column(comment = "请求接口资源访问id") + private BigInteger apiKeyResourceId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getApiKeyId() { + return apiKeyId; + } + + public void setApiKeyId(BigInteger apiKeyId) { + this.apiKeyId = apiKeyId; + } + + public BigInteger getApiKeyResourceId() { + return apiKeyResourceId; + } + + public void setApiKeyResourceId(BigInteger apiKeyResourceId) { + this.apiKeyResourceId = apiKeyResourceId; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDeptBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDeptBase.java new file mode 100644 index 0000000..86f6be0 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDeptBase.java @@ -0,0 +1,198 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateTreeEntity; + + +public class SysDeptBase extends DateTreeEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 父级ID + */ + @Column(comment = "父级ID") + private BigInteger parentId; + + /** + * 父级部门ID集合 + */ + @Column(comment = "父级部门ID集合") + private String ancestors; + + /** + * 部门名称 + */ + @Column(comment = "部门名称") + private String deptName; + + /** + * 部门编码 + */ + @Column(comment = "部门编码") + private String deptCode; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getTenantId() { + return tenantId; + } + + public void setTenantId(BigInteger tenantId) { + this.tenantId = tenantId; + } + + public BigInteger getParentId() { + return parentId; + } + + public void setParentId(BigInteger parentId) { + this.parentId = parentId; + } + + public String getAncestors() { + return ancestors; + } + + public void setAncestors(String ancestors) { + this.ancestors = ancestors; + } + + public String getDeptName() { + return deptName; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptCode() { + return deptCode; + } + + public void setDeptCode(String deptCode) { + this.deptCode = deptCode; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictBase.java new file mode 100644 index 0000000..2e626c2 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictBase.java @@ -0,0 +1,158 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import tech.easyflow.common.entity.DateEntity; + + +public class SysDictBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 数据字典名称 + */ + @Column(comment = "数据字典名称") + private String name; + + /** + * 字典编码 + */ + @Column(comment = "字典编码") + private String code; + + /** + * 字典描述或备注 + */ + @Column(comment = "字典描述或备注") + private String description; + + /** + * 字典类型 1 自定义字典、2 数据表字典、 3 枚举类字典、 4 系统字典(自定义 DictLoader) + */ + @Column(comment = "字典类型 1 自定义字典、2 数据表字典、 3 枚举类字典、 4 系统字典(自定义 DictLoader)") + private Integer dictType; + + /** + * 排序编号 + */ + @Column(comment = "排序编号") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 扩展字典 存放 json + */ + @Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展字典 存放 json") + private Map options; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + 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 Integer getDictType() { + return dictType; + } + + public void setDictType(Integer dictType) { + this.dictType = dictType; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictItemBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictItemBase.java new file mode 100644 index 0000000..1605496 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysDictItemBase.java @@ -0,0 +1,184 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class SysDictItemBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 归属哪个字典 + */ + @Column(comment = "归属哪个字典") + private BigInteger dictId; + + /** + * 名称或内容 + */ + @Column(comment = "名称或内容") + private String text; + + /** + * 值 + */ + @Column(comment = "值") + private String value; + + /** + * 描述 + */ + @Column(comment = "描述") + private String description; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * css样式内容 + */ + @Column(comment = "css样式内容") + private String cssContent; + + /** + * css样式类名 + */ + @Column(comment = "css样式类名") + private String cssClass; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getDictId() { + return dictId; + } + + public void setDictId(BigInteger dictId) { + this.dictId = dictId; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public String getCssContent() { + return cssContent; + } + + public void setCssContent(String cssContent) { + this.cssContent = cssContent; + } + + public String getCssClass() { + return cssClass; + } + + public void setCssClass(String cssClass) { + this.cssClass = cssClass; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysLogBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysLogBase.java new file mode 100644 index 0000000..5d3695d --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysLogBase.java @@ -0,0 +1,183 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; + + +public class SysLogBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "ID") + private BigInteger id; + + /** + * 操作人 + */ + @Column(comment = "操作人") + private BigInteger accountId; + + /** + * 操作名称 + */ + @Column(comment = "操作名称") + private String actionName; + + /** + * 操作的类型 + */ + @Column(comment = "操作的类型") + private String actionType; + + /** + * 操作涉及的类 + */ + @Column(comment = "操作涉及的类") + private String actionClass; + + /** + * 操作涉及的方法 + */ + @Column(comment = "操作涉及的方法") + private String actionMethod; + + /** + * 操作涉及的 URL 地址 + */ + @Column(comment = "操作涉及的 URL 地址") + private String actionUrl; + + /** + * 操作涉及的用户 IP 地址 + */ + @Column(comment = "操作涉及的用户 IP 地址") + private String actionIp; + + /** + * 操作请求参数 + */ + @Column(comment = "操作请求参数") + private String actionParams; + + /** + * 操作请求body + */ + @Column(comment = "操作请求body") + private String actionBody; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 操作时间 + */ + @Column(comment = "操作时间") + private Date created; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getAccountId() { + return accountId; + } + + public void setAccountId(BigInteger accountId) { + this.accountId = accountId; + } + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public String getActionType() { + return actionType; + } + + public void setActionType(String actionType) { + this.actionType = actionType; + } + + public String getActionClass() { + return actionClass; + } + + public void setActionClass(String actionClass) { + this.actionClass = actionClass; + } + + public String getActionMethod() { + return actionMethod; + } + + public void setActionMethod(String actionMethod) { + this.actionMethod = actionMethod; + } + + public String getActionUrl() { + return actionUrl; + } + + public void setActionUrl(String actionUrl) { + this.actionUrl = actionUrl; + } + + public String getActionIp() { + return actionIp; + } + + public void setActionIp(String actionIp) { + this.actionIp = actionIp; + } + + public String getActionParams() { + return actionParams; + } + + public void setActionParams(String actionParams) { + this.actionParams = actionParams; + } + + public String getActionBody() { + return actionBody; + } + + public void setActionBody(String actionBody) { + this.actionBody = actionBody; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysMenuBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysMenuBase.java new file mode 100644 index 0000000..7ba27bd --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysMenuBase.java @@ -0,0 +1,240 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateTreeEntity; + + +public class SysMenuBase extends DateTreeEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 父菜单id + */ + @Column(comment = "父菜单id") + private BigInteger parentId; + + /** + * 菜单类型 + */ + @Column(comment = "菜单类型") + private Integer menuType; + + /** + * 菜单标题 + */ + @Column(comment = "菜单标题") + private String menuTitle; + + /** + * 菜单url + */ + @Column(comment = "菜单url") + private String menuUrl; + + /** + * 组件路径 + */ + @Column(comment = "组件路径") + private String component; + + /** + * 图标/图片地址 + */ + @Column(comment = "图标/图片地址") + private String menuIcon; + + /** + * 是否显示 + */ + @Column(comment = "是否显示") + private Integer isShow; + + /** + * 权限标识 + */ + @Column(comment = "权限标识") + private String permissionTag; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + 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 Integer getMenuType() { + return menuType; + } + + public void setMenuType(Integer menuType) { + this.menuType = menuType; + } + + public String getMenuTitle() { + return menuTitle; + } + + public void setMenuTitle(String menuTitle) { + this.menuTitle = menuTitle; + } + + public String getMenuUrl() { + return menuUrl; + } + + public void setMenuUrl(String menuUrl) { + this.menuUrl = menuUrl; + } + + public String getComponent() { + return component; + } + + public void setComponent(String component) { + this.component = component; + } + + public String getMenuIcon() { + return menuIcon; + } + + public void setMenuIcon(String menuIcon) { + this.menuIcon = menuIcon; + } + + public Integer getIsShow() { + return isShow; + } + + public void setIsShow(Integer isShow) { + this.isShow = isShow; + } + + public String getPermissionTag() { + return permissionTag; + } + + public void setPermissionTag(String permissionTag) { + this.permissionTag = permissionTag; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysOptionBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysOptionBase.java new file mode 100644 index 0000000..92aab9a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysOptionBase.java @@ -0,0 +1,54 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysOptionBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 配置KEY + */ + @Column(comment = "配置KEY") + private String key; + + /** + * 配置内容 + */ + @Column(comment = "配置内容") + private String value; + + public BigInteger getTenantId() { + return tenantId; + } + + public void setTenantId(BigInteger tenantId) { + this.tenantId = tenantId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysPositionBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysPositionBase.java new file mode 100644 index 0000000..78241d3 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysPositionBase.java @@ -0,0 +1,184 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class SysPositionBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 岗位名称 + */ + @Column(comment = "岗位名称") + private String positionName; + + /** + * 岗位编码 + */ + @Column(comment = "岗位编码") + private String positionCode; + + /** + * 排序 + */ + @Column(comment = "排序") + private Integer sortNo; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getTenantId() { + return tenantId; + } + + public void setTenantId(BigInteger tenantId) { + this.tenantId = tenantId; + } + + public BigInteger getDeptId() { + return deptId; + } + + public void setDeptId(BigInteger deptId) { + this.deptId = deptId; + } + + public String getPositionName() { + return positionName; + } + + public void setPositionName(String positionName) { + this.positionName = positionName; + } + + public String getPositionCode() { + return positionCode; + } + + public void setPositionCode(String positionCode) { + this.positionCode = positionCode; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleBase.java new file mode 100644 index 0000000..56068db --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleBase.java @@ -0,0 +1,198 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class SysRoleBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 角色名称 + */ + @Column(comment = "角色名称") + private String roleName; + + /** + * 角色标识 + */ + @Column(comment = "角色标识") + private String roleKey; + + /** + * 数据状态 + */ + @Column(comment = "数据状态") + private Integer status; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建者 + */ + @Column(comment = "创建者") + private BigInteger createdBy; + + /** + * 修改时间 + */ + @Column(comment = "修改时间") + private Date modified; + + /** + * 修改者 + */ + @Column(comment = "修改者") + private BigInteger modifiedBy; + + /** + * 备注 + */ + @Column(comment = "备注") + private String remark; + + /** + * 数据权限(EnumDataScope) + */ + @Column(comment = "数据权限(EnumDataScope)") + private Integer dataScope; + + /** + * 菜单树选择项是否关联显示 + */ + @Column(comment = "菜单树选择项是否关联显示") + private Boolean menuCheckStrictly; + + /** + * 部门树选择项是否关联显示 + */ + @Column(comment = "部门树选择项是否关联显示") + private Boolean deptCheckStrictly; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getTenantId() { + return tenantId; + } + + public void setTenantId(BigInteger tenantId) { + this.tenantId = tenantId; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getRoleKey() { + return roleKey; + } + + public void setRoleKey(String roleKey) { + this.roleKey = roleKey; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public Integer getDataScope() { + return dataScope; + } + + public void setDataScope(Integer dataScope) { + this.dataScope = dataScope; + } + + public Boolean getMenuCheckStrictly() { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(Boolean menuCheckStrictly) { + this.menuCheckStrictly = menuCheckStrictly; + } + + public Boolean getDeptCheckStrictly() { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(Boolean deptCheckStrictly) { + this.deptCheckStrictly = deptCheckStrictly; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleDeptBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleDeptBase.java new file mode 100644 index 0000000..00e0995 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleDeptBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; + +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysRoleDeptBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 角色ID + */ + @Column(comment = "角色ID") + private BigInteger roleId; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getRoleId() { + return roleId; + } + + public void setRoleId(BigInteger roleId) { + this.roleId = roleId; + } + + public BigInteger getDeptId() { + return deptId; + } + + public void setDeptId(BigInteger deptId) { + this.deptId = deptId; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleMenuBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleMenuBase.java new file mode 100644 index 0000000..0431437 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleMenuBase.java @@ -0,0 +1,56 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; + + +public class SysRoleMenuBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键") + private BigInteger id; + + /** + * 角色ID + */ + @Column(comment = "角色ID") + private BigInteger roleId; + + /** + * 菜单ID + */ + @Column(comment = "菜单ID") + private BigInteger menuId; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public BigInteger getRoleId() { + return roleId; + } + + public void setRoleId(BigInteger roleId) { + this.roleId = roleId; + } + + public BigInteger getMenuId() { + return menuId; + } + + public void setMenuId(BigInteger menuId) { + this.menuId = menuId; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysUserFeedbackBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysUserFeedbackBase.java new file mode 100644 index 0000000..0357e59 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysUserFeedbackBase.java @@ -0,0 +1,212 @@ +package tech.easyflow.system.entity.base; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Date; +import tech.easyflow.common.entity.DateEntity; + + +public class SysUserFeedbackBase extends DateEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键id") + private BigInteger id; + + /** + * 问题摘要 + */ + @Column(comment = "问题摘要") + private String feedbackContent; + + /** + * 问题类型(1-功能故障 2-优化建议 3-账号问题 4-其他) + */ + @Column(comment = "问题类型(1-功能故障 2-优化建议 3-账号问题 4-其他)") + private Integer feedbackType; + + /** + * 联系方式【手机号/邮箱】 + */ + @Column(comment = "联系方式【手机号/邮箱】") + private String contactInfo; + + /** + * 附件url + */ + @Column(comment = "附件url") + private String attachmentUrl; + + /** + * 反馈处理状态(0-未查看 1-已查看 2-已处理) + */ + @Column(comment = "反馈处理状态(0-未查看 1-已查看 2-已处理)") + private Integer status; + + /** + * 处理人id + */ + @Column(comment = "处理人id") + private BigInteger handlerId; + + /** + * 处理时间 + */ + @Column(comment = "处理时间") + private Date handleTime; + + /** + * 部门ID + */ + @Column(comment = "部门ID") + private BigInteger deptId; + + /** + * 租户ID + */ + @Column(tenantId = true, comment = "租户ID") + private BigInteger tenantId; + + /** + * 创建时间 + */ + @Column(comment = "创建时间") + private Date created; + + /** + * 创建人 + */ + @Column(comment = "创建人") + private BigInteger createdBy; + + /** + * 最后修改时间 + */ + @Column(comment = "最后修改时间") + private Date modified; + + /** + * 最后修改的人 + */ + @Column(comment = "最后修改的人") + private BigInteger modifiedBy; + + public BigInteger getId() { + return id; + } + + public void setId(BigInteger id) { + this.id = id; + } + + public String getFeedbackContent() { + return feedbackContent; + } + + public void setFeedbackContent(String feedbackContent) { + this.feedbackContent = feedbackContent; + } + + public Integer getFeedbackType() { + return feedbackType; + } + + public void setFeedbackType(Integer feedbackType) { + this.feedbackType = feedbackType; + } + + public String getContactInfo() { + return contactInfo; + } + + public void setContactInfo(String contactInfo) { + this.contactInfo = contactInfo; + } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public BigInteger getHandlerId() { + return handlerId; + } + + public void setHandlerId(BigInteger handlerId) { + this.handlerId = handlerId; + } + + public Date getHandleTime() { + return handleTime; + } + + public void setHandleTime(Date handleTime) { + this.handleTime = handleTime; + } + + 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 Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public BigInteger getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(BigInteger createdBy) { + this.createdBy = createdBy; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public BigInteger getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(BigInteger modifiedBy) { + this.modifiedBy = modifiedBy; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountMapper.java new file mode 100644 index 0000000..a00cf4b --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysAccount; +import com.mybatisflex.core.BaseMapper; + +/** + * 用户表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountPositionMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountPositionMapper.java new file mode 100644 index 0000000..bc920c3 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountPositionMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysAccountPosition; +import com.mybatisflex.core.BaseMapper; + +/** + * 用户-职位表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountPositionMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountRoleMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountRoleMapper.java new file mode 100644 index 0000000..daa3419 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysAccountRoleMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysAccountRole; +import com.mybatisflex.core.BaseMapper; + +/** + * 用户-角色表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountRoleMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyMapper.java new file mode 100644 index 0000000..a3fa90b --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.system.entity.SysApiKey; + +/** + * 映射层。 + * + * @author Administrator + * @since 2025-04-18 + */ +public interface SysApiKeyMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMapper.java new file mode 100644 index 0000000..8b9a553 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.system.entity.SysApiKeyResource; + +/** + * 请求接口表 映射层。 + * + * @author 12076 + * @since 2025-12-01 + */ +public interface SysApiKeyResourceMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMappingMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMappingMapper.java new file mode 100644 index 0000000..dc5c7cc --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysApiKeyResourceMappingMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; + +/** + * apikey-请求接口表 映射层。 + * + * @author 12076 + * @since 2025-12-01 + */ +public interface SysApiKeyResourceMappingMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDeptMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..73c7899 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDeptMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysDept; +import com.mybatisflex.core.BaseMapper; + +/** + * 部门表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysDeptMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictItemMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictItemMapper.java new file mode 100644 index 0000000..b725133 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictItemMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysDictItem; +import com.mybatisflex.core.BaseMapper; + +/** + * 数据字典内容 映射层。 + * + * @author michael + * @since 2024-03-03 + */ +public interface SysDictItemMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictMapper.java new file mode 100644 index 0000000..337e3e4 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysDictMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysDict; +import com.mybatisflex.core.BaseMapper; + +/** + * 系统配置表 映射层。 + * + * @author michael + * @since 2024-03-03 + */ +public interface SysDictMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysLogMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysLogMapper.java new file mode 100644 index 0000000..316f00e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysLogMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysLog; +import com.mybatisflex.core.BaseMapper; + +/** + * 映射层。 + * + * @author michael + * @since 2024-01-28 + */ +public interface SysLogMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysMenuMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..047d382 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysMenuMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysMenu; +import com.mybatisflex.core.BaseMapper; + +/** + * 菜单表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysMenuMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysOptionMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysOptionMapper.java new file mode 100644 index 0000000..ff7ccf8 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysOptionMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysOption; +import com.mybatisflex.core.BaseMapper; + +/** + * 系统配置信息表。 映射层。 + * + * @author michael + * @since 2024-03-13 + */ +public interface SysOptionMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysPositionMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysPositionMapper.java new file mode 100644 index 0000000..19799de --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysPositionMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysPosition; +import com.mybatisflex.core.BaseMapper; + +/** + * 职位表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysPositionMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleDeptMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..76683e9 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.system.entity.SysRoleDept; + +/** + * 角色-部门表 映射层。 + * + * @author ArkLight + * @since 2025-11-19 + */ +public interface SysRoleDeptMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..ce52649 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysRole; +import com.mybatisflex.core.BaseMapper; + +/** + * 系统角色 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysRoleMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMenuMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..78c6fd2 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import tech.easyflow.system.entity.SysRoleMenu; +import com.mybatisflex.core.BaseMapper; + +/** + * 角色-菜单表 映射层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysRoleMenuMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysUserFeedbackMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysUserFeedbackMapper.java new file mode 100644 index 0000000..c293397 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysUserFeedbackMapper.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.mapper; + +import com.mybatisflex.core.BaseMapper; +import tech.easyflow.system.entity.SysUserFeedback; + +/** + * 映射层。 + * + * @author 12076 + * @since 2025-12-30 + */ +public interface SysUserFeedbackMapper extends BaseMapper { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/options/DefaultOptionStore.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/options/DefaultOptionStore.java new file mode 100644 index 0000000..b82c74e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/options/DefaultOptionStore.java @@ -0,0 +1,47 @@ +package tech.easyflow.system.options; + +import com.mybatisflex.core.query.QueryWrapper; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.options.SysOptionStore; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.util.StringUtil; +import tech.easyflow.system.entity.SysOption; +import tech.easyflow.system.service.SysOptionService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +public class DefaultOptionStore implements SysOptionStore { + + @Resource + private SysOptionService optionService; + + + @Override + public void save(String key, Object value) { + if (value == null || !StringUtil.hasText(value.toString())) { + optionService.remove(QueryWrapper.create().eq(SysOption::getKey, key)); + return; + } + + String newValue = value.toString().trim(); + SysOption option = optionService.getByOptionKey(key); + LoginAccount loginAccount = SaTokenUtil.getLoginAccount(); + if (option == null) { + option = new SysOption(key, newValue); + option.setTenantId(loginAccount.getTenantId()); + optionService.save(option); + } else { + option.setValue(newValue); + QueryWrapper queryWrapper = QueryWrapper.create().eq(SysOption::getKey, key); + optionService.update(option, queryWrapper); + } + } + + @Override + public String get(String key) { + SysOption option = optionService.getById(key); + return option != null ? option.getValue() : null; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountPositionService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountPositionService.java new file mode 100644 index 0000000..6f3f8c4 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountPositionService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysAccountPosition; +import com.mybatisflex.core.service.IService; + +/** + * 用户-职位表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountPositionService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountRoleService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountRoleService.java new file mode 100644 index 0000000..eed9c6f --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountRoleService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysAccountRole; +import com.mybatisflex.core.service.IService; + +/** + * 用户-角色表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountRoleService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountService.java new file mode 100644 index 0000000..6c064df --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysAccountService.java @@ -0,0 +1,17 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysAccount; + +/** + * 用户表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysAccountService extends IService { + + void syncRelations(SysAccount entity); + + SysAccount getByUsername(String userKey); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceMappingService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceMappingService.java new file mode 100644 index 0000000..7207f7a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceMappingService.java @@ -0,0 +1,16 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysApiKey; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; + +/** + * apikey-请求接口表 服务层。 + * + * @author 12076 + * @since 2025-12-01 + */ +public interface SysApiKeyResourceMappingService extends IService { + + void authInterface(SysApiKey entity); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceService.java new file mode 100644 index 0000000..2505c5a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyResourceService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysApiKeyResource; + +/** + * 请求接口表 服务层。 + * + * @author 12076 + * @since 2025-12-01 + */ +public interface SysApiKeyResourceService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyService.java new file mode 100644 index 0000000..d64dc39 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysApiKeyService.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysApiKey; + +/** + * 服务层。 + * + * @author Administrator + * @since 2025-04-18 + */ +public interface SysApiKeyService extends IService { + + void checkApikeyPermission(String apiKey, String requestURI); + + SysApiKey getSysApiKey(String apiKey); + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDeptService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDeptService.java new file mode 100644 index 0000000..cd85bf1 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDeptService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysDept; +import com.mybatisflex.core.service.IService; + +/** + * 部门表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysDeptService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictItemService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictItemService.java new file mode 100644 index 0000000..a5eca98 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictItemService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysDictItem; +import com.mybatisflex.core.service.IService; + +/** + * 数据字典内容 服务层。 + * + * @author michael + * @since 2024-03-03 + */ +public interface SysDictItemService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictService.java new file mode 100644 index 0000000..438d423 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysDictService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysDict; +import com.mybatisflex.core.service.IService; + +/** + * 系统配置表 服务层。 + * + * @author michael + * @since 2024-03-03 + */ +public interface SysDictService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysLogService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysLogService.java new file mode 100644 index 0000000..0986ccb --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysLogService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysLog; +import com.mybatisflex.core.service.IService; + +/** + * 操作日志表 服务层。 + * + * @author michael + * @since 2024-01-28 + */ +public interface SysLogService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysMenuService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysMenuService.java new file mode 100644 index 0000000..675e8e8 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysMenuService.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysMenu; +import com.mybatisflex.core.service.IService; + +import java.math.BigInteger; +import java.util.List; + +/** + * 菜单表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysMenuService extends IService { + + List getMenusByAccountId(SysMenu entity, BigInteger accountId); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysOptionService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysOptionService.java new file mode 100644 index 0000000..c357890 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysOptionService.java @@ -0,0 +1,15 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysOption; +import com.mybatisflex.core.service.IService; + +/** + * 系统配置信息表。 服务层。 + * + * @author michael + * @since 2024-03-13 + */ +public interface SysOptionService extends IService { + + SysOption getByOptionKey(String key); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysPositionService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysPositionService.java new file mode 100644 index 0000000..e420ab0 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysPositionService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysPosition; +import com.mybatisflex.core.service.IService; + +/** + * 职位表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysPositionService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleDeptService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleDeptService.java new file mode 100644 index 0000000..f5dd28f --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleDeptService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysRoleDept; + +/** + * 角色-部门表 服务层。 + * + * @author ArkLight + * @since 2025-11-19 + */ +public interface SysRoleDeptService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleMenuService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleMenuService.java new file mode 100644 index 0000000..e759bef --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleMenuService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysRoleMenu; +import com.mybatisflex.core.service.IService; + +/** + * 角色-菜单表 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysRoleMenuService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleService.java new file mode 100644 index 0000000..f674f55 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleService.java @@ -0,0 +1,22 @@ +package tech.easyflow.system.service; + +import tech.easyflow.system.entity.SysRole; +import com.mybatisflex.core.service.IService; + +import java.math.BigInteger; +import java.util.List; + +/** + * 系统角色 服务层。 + * + * @author ArkLight + * @since 2025-03-14 + */ +public interface SysRoleService extends IService { + + void saveRoleMenu(BigInteger roleId, List keys); + + List getRolesByAccountId(BigInteger accountId); + + void saveRole(SysRole sysRole); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysUserFeedbackService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysUserFeedbackService.java new file mode 100644 index 0000000..4d49e27 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysUserFeedbackService.java @@ -0,0 +1,14 @@ +package tech.easyflow.system.service; + +import com.mybatisflex.core.service.IService; +import tech.easyflow.system.entity.SysUserFeedback; + +/** + * 服务层。 + * + * @author 12076 + * @since 2025-12-30 + */ +public interface SysUserFeedbackService extends IService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountPositionServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountPositionServiceImpl.java new file mode 100644 index 0000000..3e15e08 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountPositionServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysAccountPosition; +import tech.easyflow.system.mapper.SysAccountPositionMapper; +import tech.easyflow.system.service.SysAccountPositionService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 用户-职位表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysAccountPositionServiceImpl extends ServiceImpl implements SysAccountPositionService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountRoleServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountRoleServiceImpl.java new file mode 100644 index 0000000..89b9599 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountRoleServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysAccountRole; +import tech.easyflow.system.mapper.SysAccountRoleMapper; +import tech.easyflow.system.service.SysAccountRoleService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 用户-角色表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysAccountRoleServiceImpl extends ServiceImpl implements SysAccountRoleService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountServiceImpl.java new file mode 100644 index 0000000..c5e27b7 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysAccountServiceImpl.java @@ -0,0 +1,84 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.system.entity.SysAccount; +import tech.easyflow.system.entity.SysAccountPosition; +import tech.easyflow.system.entity.SysAccountRole; +import tech.easyflow.system.mapper.SysAccountMapper; +import tech.easyflow.system.mapper.SysAccountPositionMapper; +import tech.easyflow.system.mapper.SysAccountRoleMapper; +import tech.easyflow.system.mapper.SysRoleMapper; +import tech.easyflow.system.service.SysAccountService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * 用户表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysAccountServiceImpl extends ServiceImpl implements SysAccountService { + + @Resource + private SysAccountRoleMapper sysAccountRoleMapper; + @Resource + private SysAccountPositionMapper sysAccountPositionMapper; + @Resource + private SysRoleMapper sysRoleMapper; + + @Override + public void syncRelations(SysAccount entity) { + if (entity == null || entity.getId() == null) { + return; + } + //sync roleIds + List roleIds = entity.getRoleIds(); + if (roleIds != null) { + QueryWrapper delW = QueryWrapper.create(); + delW.eq(SysAccountRole::getAccountId, entity.getId()); + sysAccountRoleMapper.deleteByQuery(delW); + if (!roleIds.isEmpty()) { + List rows = new ArrayList<>(roleIds.size()); + roleIds.forEach(roleId -> { + SysAccountRole row = new SysAccountRole(); + row.setAccountId(entity.getId()); + row.setRoleId(roleId); + rows.add(row); + }); + sysAccountRoleMapper.insertBatch(rows); + } + } + + //sync positionIds + List positionIds = entity.getPositionIds(); + if (positionIds != null) { + QueryWrapper delW = QueryWrapper.create(); + delW.eq(SysAccountPosition::getAccountId, entity.getId()); + sysAccountPositionMapper.deleteByQuery(delW); + if (!positionIds.isEmpty()) { + List rows = new ArrayList<>(positionIds.size()); + positionIds.forEach(positionId -> { + SysAccountPosition row = new SysAccountPosition(); + row.setAccountId(entity.getId()); + row.setPositionId(positionId); + rows.add(row); + }); + sysAccountPositionMapper.insertBatch(rows); + } + } + } + + @Override + public SysAccount getByUsername(String userKey) { + QueryWrapper w = QueryWrapper.create(); + w.eq(SysAccount::getLoginName, userKey); + return getOne(w); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceMappingServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceMappingServiceImpl.java new file mode 100644 index 0000000..48bb5a0 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceMappingServiceImpl.java @@ -0,0 +1,41 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.system.entity.SysApiKey; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; +import tech.easyflow.system.mapper.SysApiKeyResourceMappingMapper; +import tech.easyflow.system.service.SysApiKeyResourceMappingService; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * apikey-请求接口表 服务层实现。 + * + * @author 12076 + * @since 2025-12-01 + */ +@Service +public class SysApiKeyResourceMappingServiceImpl extends ServiceImpl implements SysApiKeyResourceMappingService { + + /** + * 批量授权apiKey接口 + * @param entity + */ + @Override + public void authInterface(SysApiKey entity) { + this.remove(QueryWrapper.create().eq(SysApiKeyResourceMapping::getApiKeyId, entity.getId())); + List rows = new ArrayList<>(entity.getPermissionIds().size()); + BigInteger apiKeyId = entity.getId(); + for (BigInteger resourceId : entity.getPermissionIds()) { + SysApiKeyResourceMapping sysApiKeyResourcePermissionRelationship = new SysApiKeyResourceMapping(); + sysApiKeyResourcePermissionRelationship.setApiKeyId(apiKeyId); + sysApiKeyResourcePermissionRelationship.setApiKeyResourceId(resourceId); + rows.add(sysApiKeyResourcePermissionRelationship); + } + this.saveBatch(rows); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceServiceImpl.java new file mode 100644 index 0000000..593d64e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyResourceServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.system.entity.SysApiKeyResource; +import tech.easyflow.system.mapper.SysApiKeyResourceMapper; +import tech.easyflow.system.service.SysApiKeyResourceService; + +/** + * 请求接口表 服务层实现。 + * + * @author 12076 + * @since 2025-12-01 + */ +@Service +public class SysApiKeyResourceServiceImpl extends ServiceImpl implements SysApiKeyResourceService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyServiceImpl.java new file mode 100644 index 0000000..56334df --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysApiKeyServiceImpl.java @@ -0,0 +1,66 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.system.entity.SysApiKey; +import tech.easyflow.system.entity.SysApiKeyResource; +import tech.easyflow.system.entity.SysApiKeyResourceMapping; +import tech.easyflow.system.mapper.SysApiKeyMapper; +import tech.easyflow.system.service.SysApiKeyResourceMappingService; +import tech.easyflow.system.service.SysApiKeyResourceService; +import tech.easyflow.system.service.SysApiKeyService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.Date; +import java.util.List; + +/** + * 服务层实现。 + * + * @author Administrator + * @since 2025-04-18 + */ +@Service +public class SysApiKeyServiceImpl extends ServiceImpl implements SysApiKeyService { + + @Resource + private SysApiKeyResourceMappingService mappingService; + @Resource + private SysApiKeyResourceService resourceService; + + @Override + public void checkApikeyPermission(String apiKey, String requestURI) { + SysApiKey sysApiKey = getSysApiKey(apiKey); + QueryWrapper w = QueryWrapper.create(); + w.eq(SysApiKeyResource::getRequestInterface, requestURI); + SysApiKeyResource resource = resourceService.getOne(w); + if (resource == null) { + throw new BusinessException("该接口不存在"); + } + QueryWrapper wm = QueryWrapper.create(); + wm.eq(SysApiKeyResourceMapping::getApiKeyId, sysApiKey.getId()); + wm.eq(SysApiKeyResourceMapping::getApiKeyResourceId, resource.getId()); + long count = mappingService.count(wm); + if (count == 0) { + throw new BusinessException("该apiKey无权限访问该接口"); + } + } + + @Override + public SysApiKey getSysApiKey(String apiKey) { + QueryWrapper w = QueryWrapper.create(); + w.eq(SysApiKey::getApiKey, apiKey); + SysApiKey one = getOne(w); + if (one == null || one.getStatus() == 0) { + throw new BusinessException("apiKey 不存在或已禁用"); + } + if (one.getExpiredAt() != null && one.getExpiredAt().getTime() < new Date().getTime()) { + throw new BusinessException("apiKey 已过期"); + } + return one; + } + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDeptServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..7bdba9c --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysDept; +import tech.easyflow.system.mapper.SysDeptMapper; +import tech.easyflow.system.service.SysDeptService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 部门表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysDeptServiceImpl extends ServiceImpl implements SysDeptService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictItemServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictItemServiceImpl.java new file mode 100644 index 0000000..ec2628b --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictItemServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysDictItem; +import tech.easyflow.system.mapper.SysDictItemMapper; +import tech.easyflow.system.service.SysDictItemService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 数据字典内容 服务层实现。 + * + * @author michael + * @since 2024-03-03 + */ +@Service +public class SysDictItemServiceImpl extends ServiceImpl implements SysDictItemService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictServiceImpl.java new file mode 100644 index 0000000..cfece0a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysDictServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysDict; +import tech.easyflow.system.mapper.SysDictMapper; +import tech.easyflow.system.service.SysDictService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 系统配置表 服务层实现。 + * + * @author michael + * @since 2024-03-03 + */ +@Service +public class SysDictServiceImpl extends ServiceImpl implements SysDictService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysLogServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysLogServiceImpl.java new file mode 100644 index 0000000..e1668d0 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysLogServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysLog; +import tech.easyflow.system.mapper.SysLogMapper; +import tech.easyflow.system.service.SysLogService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 操作日志表 服务层实现。 + * + * @author michael + * @since 2024-01-28 + */ +@Service +public class SysLogServiceImpl extends ServiceImpl implements SysLogService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysMenuServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..4d12d2a --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,58 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.common.util.SqlOperatorsUtil; +import tech.easyflow.system.entity.*; +import tech.easyflow.system.mapper.SysMenuMapper; +import tech.easyflow.system.service.SysAccountRoleService; +import tech.easyflow.system.service.SysMenuService; +import tech.easyflow.system.service.SysRoleMenuService; +import cn.hutool.core.collection.CollectionUtil; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.query.SqlOperators; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 菜单表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService { + + @Resource + private SysRoleMenuService sysRoleMenuService; + @Resource + private SysAccountRoleService sysAccountRoleService; + + @Override + public List getMenusByAccountId(SysMenu entity, BigInteger accountId) { + // 查询用户对应角色id集合 + QueryWrapper am = QueryWrapper.create(); + am.eq(SysAccountRole::getAccountId, accountId); + List roleIds = sysAccountRoleService.list(am).stream().map(SysAccountRole::getRoleId).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(roleIds)) { + return new ArrayList<>(); + } + // 查询角色对应的菜单id集合 + QueryWrapper rm = QueryWrapper.create(); + rm.in(SysRoleMenu::getRoleId, roleIds); + List menuIds = sysRoleMenuService.list(rm).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(menuIds)) { + return new ArrayList<>(); + } + // 查询当前用户拥有的菜单 + SqlOperators ops = SqlOperatorsUtil.build(SysMenu.class); + QueryWrapper queryWrapper = QueryWrapper.create(entity, ops); + queryWrapper.in(SysMenu::getId, menuIds); + queryWrapper.orderBy("sort_no asc"); + return list(queryWrapper); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysOptionServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysOptionServiceImpl.java new file mode 100644 index 0000000..4be4819 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysOptionServiceImpl.java @@ -0,0 +1,25 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import tech.easyflow.system.entity.SysOption; +import tech.easyflow.system.mapper.SysOptionMapper; +import tech.easyflow.system.service.SysOptionService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 系统配置信息表。 服务层实现。 + * + * @author michael + * @since 2024-03-13 + */ +@Service +public class SysOptionServiceImpl extends ServiceImpl implements SysOptionService { + + @Override + public SysOption getByOptionKey(String key) { + QueryWrapper w = QueryWrapper.create(); + w.eq(SysOption::getKey, key); + return getOne(w); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysPositionServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysPositionServiceImpl.java new file mode 100644 index 0000000..3a17682 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysPositionServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysPosition; +import tech.easyflow.system.mapper.SysPositionMapper; +import tech.easyflow.system.service.SysPositionService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 职位表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysPositionServiceImpl extends ServiceImpl implements SysPositionService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleDeptServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleDeptServiceImpl.java new file mode 100644 index 0000000..4ce8e38 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleDeptServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import tech.easyflow.system.entity.SysRoleDept; +import tech.easyflow.system.mapper.SysRoleDeptMapper; +import tech.easyflow.system.service.SysRoleDeptService; + +/** + * 角色-部门表 服务层实现。 + * + * @author ArkLight + * @since 2025-11-19 + */ +@Service +public class SysRoleDeptServiceImpl extends ServiceImpl implements SysRoleDeptService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleMenuServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..9655211 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import tech.easyflow.system.entity.SysRoleMenu; +import tech.easyflow.system.mapper.SysRoleMenuMapper; +import tech.easyflow.system.service.SysRoleMenuService; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 角色-菜单表 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysRoleMenuServiceImpl extends ServiceImpl implements SysRoleMenuService { + +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..197022e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,110 @@ +package tech.easyflow.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.easyflow.common.constant.enums.EnumDataScope; +import tech.easyflow.system.entity.SysAccountRole; +import tech.easyflow.system.entity.SysRole; +import tech.easyflow.system.entity.SysRoleDept; +import tech.easyflow.system.entity.SysRoleMenu; +import tech.easyflow.system.mapper.SysRoleDeptMapper; +import tech.easyflow.system.mapper.SysRoleMapper; +import tech.easyflow.system.mapper.SysRoleMenuMapper; +import tech.easyflow.system.service.SysAccountRoleService; +import tech.easyflow.system.service.SysRoleService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统角色 服务层实现。 + * + * @author ArkLight + * @since 2025-03-14 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { + + @Resource + private SysAccountRoleService sysAccountRoleService; + @Resource + private SysRoleMenuMapper sysRoleMenuMapper; + @Resource + private SysRoleDeptMapper sysRoleDeptMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRoleMenu(BigInteger roleId, List keys) { + + QueryWrapper delW = QueryWrapper.create(); + delW.eq(SysRoleMenu::getRoleId, roleId); + sysRoleMenuMapper.deleteByQuery(delW); + + List rows = new ArrayList<>(keys.size()); + keys.forEach(string -> { + SysRoleMenu row = new SysRoleMenu(); + row.setRoleId(roleId); + row.setMenuId(new BigInteger(string)); + rows.add(row); + }); + sysRoleMenuMapper.insertBatch(rows); + } + + @Override + public List getRolesByAccountId(BigInteger accountId) { + // 查询用户对应角色id集合 + QueryWrapper am = QueryWrapper.create(); + am.eq(SysAccountRole::getAccountId, accountId); + List roleIds = sysAccountRoleService.list(am).stream().map(SysAccountRole::getRoleId).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(roleIds)) { + return new ArrayList<>(); + } + return listByIds(roleIds); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRole(SysRole sysRole) { + + saveOrUpdate(sysRole); + + // 非自定义数据权限,则部门id集合为空 + if (!EnumDataScope.CUSTOM.getCode().equals(sysRole.getDataScope())) { + sysRole.setDeptIds(new ArrayList<>()); + } + + List menuIds = sysRole.getMenuIds(); + List deptIds = sysRole.getDeptIds(); + + QueryWrapper wrm = QueryWrapper.create(); + wrm.eq(SysRoleMenu::getRoleId, sysRole.getId()); + sysRoleMenuMapper.deleteByQuery(wrm); + QueryWrapper wrd = QueryWrapper.create(); + wrd.eq(SysRoleDept::getRoleId, sysRole.getId()); + sysRoleDeptMapper.deleteByQuery(wrd); + + if (CollectionUtil.isNotEmpty(menuIds)) { + for (BigInteger menuId : menuIds) { + SysRoleMenu roleMenu = new SysRoleMenu(); + roleMenu.setRoleId(sysRole.getId()); + roleMenu.setMenuId(menuId); + sysRoleMenuMapper.insert(roleMenu); + } + } + + if (CollectionUtil.isNotEmpty(deptIds)) { + for (BigInteger deptId : deptIds) { + SysRoleDept roleDept = new SysRoleDept(); + roleDept.setRoleId(sysRole.getId()); + roleDept.setDeptId(deptId); + sysRoleDeptMapper.insert(roleDept); + } + } + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysUserFeedbackServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysUserFeedbackServiceImpl.java new file mode 100644 index 0000000..356dd9e --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysUserFeedbackServiceImpl.java @@ -0,0 +1,18 @@ +package tech.easyflow.system.service.impl; + +import com.mybatisflex.spring.service.impl.ServiceImpl; +import tech.easyflow.system.entity.SysUserFeedback; +import tech.easyflow.system.mapper.SysUserFeedbackMapper; +import tech.easyflow.system.service.SysUserFeedbackService; +import org.springframework.stereotype.Service; + +/** + * 服务层实现。 + * + * @author 12076 + * @since 2025-12-30 + */ +@Service +public class SysUserFeedbackServiceImpl extends ServiceImpl implements SysUserFeedbackService{ + +} diff --git a/easyflow-modules/pom.xml b/easyflow-modules/pom.xml new file mode 100644 index 0000000..1eeb0f4 --- /dev/null +++ b/easyflow-modules/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + tech.easyflow + easyflow + ${revision} + + + easyflow-modules + pom + + + easyflow-module-system + easyflow-module-log + easyflow-module-auth + easyflow-module-autoconfig + easyflow-module-ai + easyflow-module-job + easyflow-module-datacenter + + + diff --git a/easyflow-starter/easyflow-starter-admin/pom.xml b/easyflow-starter/easyflow-starter-admin/pom.xml new file mode 100644 index 0000000..f1c7fe0 --- /dev/null +++ b/easyflow-starter/easyflow-starter-admin/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + tech.easyflow + easyflow-starter + ${revision} + + + easyflow-starter-admin + + + + tech.easyflow + easyflow-api-admin + + + + \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-all/pom.xml b/easyflow-starter/easyflow-starter-all/pom.xml new file mode 100644 index 0000000..1e85104 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + tech.easyflow + easyflow-starter + ${revision} + + + + easyflow-starter + easyflow-starter-all + jar + + + + tech.easyflow + easyflow-api-admin + + + tech.easyflow + easyflow-api-usercenter + + + tech.easyflow + easyflow-api-public + + + tech.easyflow + easyflow-module-job + + + tech.easyflow + easyflow-module-ai + + + slf4j-simple + org.slf4j + + + log4j-slf4j-impl + org.apache.logging.log4j + + + slf4j-reload4j + org.slf4j + + + + + tech.easyflow + easyflow-module-auth + + + tech.easyflow + easyflow-module-autoconfig + + + com.zaxxer + HikariCP + + + com.mysql + mysql-connector-j + + + org.yaml + snakeyaml + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + true + + + + + repackage + + + + + + + + diff --git a/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MainApplication.java b/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MainApplication.java new file mode 100644 index 0000000..91c6f14 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MainApplication.java @@ -0,0 +1,15 @@ +package tech.easyflow.starter; + +import org.dromara.x.file.storage.spring.EnableFileStorage; +import tech.easyflow.common.spring.BaseApp; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableFileStorage +public class MainApplication extends BaseApp { + + public static void main(String[] args) { + run(MainApplication.class, args); + } + +} diff --git a/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MybatisConfig.java b/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MybatisConfig.java new file mode 100644 index 0000000..1474856 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/java/tech/easyflow/starter/MybatisConfig.java @@ -0,0 +1,26 @@ +package tech.easyflow.starter; + +import com.mybatisflex.core.FlexGlobalConfig; +import com.mybatisflex.core.audit.AuditManager; +import com.mybatisflex.core.audit.ConsoleMessageCollector; +import com.mybatisflex.core.audit.MessageCollector; +import com.mybatisflex.spring.boot.MyBatisFlexCustomizer; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MybatisConfig implements MyBatisFlexCustomizer { + + + @Override + public void customize(FlexGlobalConfig flexGlobalConfig) { + //开启审计功能 + AuditManager.setAuditEnable(true); + + //取消控制台的 Banner 打印 + flexGlobalConfig.setPrintBanner(false); + + //设置 SQL 审计收集器 + MessageCollector collector = new ConsoleMessageCollector(); + AuditManager.setMessageCollector(collector); + } +} diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/ai-brands.json b/easyflow-starter/easyflow-starter-all/src/main/resources/ai-brands.json new file mode 100644 index 0000000..24d721b --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/ai-brands.json @@ -0,0 +1,577 @@ +[ + { + "title": "DeepSeek", + "key": "deepseek", + "options":{ + "llmEndpoint":"https://api.deepseek.com", + "chatPath":"/chat/completions", + "modelList":[ + { + "llmModel":"deepseek-reasoner", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1-0528", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果" + }, + { + "llmModel":"deepseek-chat", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-V3-0324", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + } + ] + }, + "icon": "\n Deep Seek\n \n \n \n \n \n \n \n \n \n \n" + + }, + { + "title": "Open AI", + "key": "openai", + "options":{ + "llmEndpoint":"https://api.openai.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + + "modelList":[ + { + "llmModel":"o4-mini", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"o4-mini", + "description":"O4-mini 是OpenAi最新的小型 O 系列型号。它针对快速、有效的推理进行了优化,在编码和可视化任务中具有非常高效的性能。" + }, + { + "llmModel":"o3", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"o3", + "description":"O3 是一个全面而强大的跨领域模型。它为数学、科学、编码和视觉推理任务设定了新标准。它还擅长技术写作和指导遵循。使用它来思考涉及跨文本、代码和图像分析的多步骤问题。" + }, + { + "llmModel":"o3-pro", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"o3-pro", + "description":"o 系列模型经过强化学习训练,在回答和执行复杂推理之前先思考。o3-pro 模型使用更多的计算来更深入地思考并始终提供更好的答案。" + }, + { + "llmModel":"o3-mini", + "supportChat":true, + "supportFunctionCalling":true, + "title":"o3-mini", + "description":"O3-mini 是OpenAi最新的小型推理模型,以与 O1-mini 相同的成本和延迟目标提供高智能。o3-mini 支持关键的开发人员功能,如结构化输出、函数调用和批处理 API。" + }, + { + "llmModel":"GPT-4.1", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"GPT-4.1", + "description":"GPT-4.1 是OpenAi用于复杂任务的旗舰模型。它非常适合跨领域解决问题。" + }, + { + "llmModel":"GPT-4o", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"GPT-4o", + "description":"GPT-4o(“o”代表“omni”)是OpenAi多功能、高智能的旗舰模型。它接受文本和图像输入,并生成文本输出(包括结构化输出)。它是大多数任务的最佳模型,也是 o 系列模型之外功能最强大的模型" + }, + { + "llmModel":"text-embedding-3-small", + "supportEmbed":true, + "title":"text-embedding-3-small", + "description":"text-embedding-3-small 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用" + }, + { + "llmModel":"text-embedding-3-large", + "supportEmbed":true, + "title":"text-embedding-3-large", + "description":"text-embedding-3-large 是OpenAi最强大的嵌入模型,适用于英语和非英语任务。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + }, + { + "llmModel":"text-embedding-ada-002", + "supportEmbed":true, + "title":"text-embedding-ada-002", + "description":"text-embedding-ada-002 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + } + + ] + }, + "icon": "\n Open AI\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "阿里百炼", + "key": "aliyun", + "options":{ + "llmEndpoint":"https://dashscope.aliyuncs.com", + "chatPath":"/compatible-mode/v1/chat/completions", + "embedPath":"/compatible-mode/v1/embeddings", + "rerankPath":"/api/v1/services/rerank/text-rerank/text-rerank", + "modelList":[ + { + "llmModel":"qwen-plus", + "supportChat":true, + "supportFunctionCalling":true, + "title":"通义千问-Plus", + "description":"Qwen3系列Plus模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力显著超过QwQ、通用能力显著超过Qwen2.5-Plus,达到同规模业界SOTA水平。" + + }, + { + "llmModel":"qwen-turbo", + "supportChat":true, + "supportFunctionCalling":true, + "title":"通义千问-Turbo", + "description":"Qwen3系列Turbo模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力以更小参数规模比肩QwQ-32B、通用能力显著超过Qwen2.5-Turbo,达到同规模业界SOTA水平。" + }, + { + "llmModel":"qwen-max", + "supportChat":true, + "supportFunctionCalling":true, + "title":"通义千问-Max", + "description":"通义千问2.5系列千亿级别超大规模语言模型,支持中文、英文等不同语言输入。随着模型的升级,qwen-max将滚动更新升级。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-7b", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1-Distill-Qwen-7B", + "description":"DeepSeek-R1-Distill-Qwen-7B是一个基于Qwen2.5-Math-7B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-14b", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1-Distill-Qwen-14B", + "description":"DeepSeek-R1-Distill-Qwen-14B是一个基于Qwen2.5-14B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-32b", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1-Distill-Qwen-32B", + "description":"DeepSeek-R1-Distill-Qwen-32B是一个基于Qwen2.5-32B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"qwen-vl-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"通义千问VL-Max", + "description":"通义千问VL-Max(qwen-vl-max),即通义千问超大规模视觉语言模型。相比增强版,再次提升视觉推理能力和指令遵循能力,提供更高的视觉感知和认知水平。在更多复杂任务上提供最佳的性能。" + }, + { + "llmModel":"qwen-vl-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"通义千问VL-Plus", + "description":"通义千问VL-Plus(qwen-vl-plus),即通义千问大规模视觉语言模型增强版。大幅提升细节识别能力和文字识别能力,支持超百万像素分辨率和任意长宽比规格的图像。在广泛的视觉任务上提供卓越的性能。" + }, + { + "llmModel":"qvq-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"通义千问-QVQ-Max", + "description":"通义千问QVQ视觉推理模型,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"qvq-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"通义千问-QVQ-Plus", + "description":"通义千问QVQ视觉推理模型增强版,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"text-embedding-v4", + "supportEmbed":true, + "title":"通用文本向量-v4", + "description":"通义实验室基于Qwen3训练的多语言文本统一向量模型,相较V3版本在文本检索、聚类、分类性能大幅提升;在MTEB多语言、中英、Code检索等评测任务上效果提升15%~40%;支持64~2048维用户自定义向量维度。" + }, + { + "llmModel":"text-embedding-v3", + "supportEmbed":true, + "title":"通用文本向量-v3", + "description":"通用文本向量,是通义实验室基于LLM底座的多语言文本统一向量模型,面向全球多个主流语种,提供高水准的向量服务,帮助开发者将文本数据快速转换为高质量的向量数据。" + } + ] + }, + "icon": "\n 阿里云\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "火山引擎", + "key": "volcengine", + "options":{ + "llmEndpoint":"https://ark.cn-beijing.volces.com", + "chatPath":"/api/v3/chat/completions", + "embedPath":"/api/v3/embeddings", + "modelList":[ + { + "llmModel":"doubao-seed-1-6-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"doubao-seed-1.6", + "description":"全新多模态深度思考模型,同时支持 thinking、non-thinking、auto三种思考模式。其中 non-thinking 模型对比 doubao-1-5-pro-32k-250115 模型大幅提升。" + + }, + { + "llmModel":"doubao-seed-1-6-flash-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"doubao-seed-1.6-flash", + "description":"有极致推理速度的多模态深度思考模型;同时支持文本和视觉理解。文本理解能力超过上一代 Lite 系列模型,视觉理解比肩友商 Pro 系列模型。" + }, + { + "llmModel":"doubao-seed-1-6-thinking-250715", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"doubao-seed-1.6-thinking", + "description":"在思考能力上进行了大幅强化, 对比 doubao 1.5 代深度理解模型,在编程、数学、逻辑推理等基础能力上进一步提升, 支持视觉理解。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "title":"deepseek-r1", + "description":"deepseek-r1 在后训练阶段大规模使用了强化学习技术,在数学、代码、自然语言推理等任务上,能力比肩 OpenAI o1 正式版。" + } + ] + }, + "icon": "\n 火山引擎\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "百度千帆", + "key": "baidu", + "options":{ + "llmEndpoint":"https://qianfan.baidubce.com", + "chatPath":"/v2/chat/completions", + "embedPath":"/v2/embeddings", + "rerankPath":"/v2/rerank", + "modelList":[ + { + "llmModel":"ernie-x1-turbo-32k", + "supportChat":true, + "supportFunctionCalling":true, + "title":"ERNIE X1 Turbo", + "description":"核心定位:深度思考模型,具备更强的理解、规划、反思、进化能力。适用场景: 在中文知识问答、文学创作、文稿写作、日常对话、逻辑推理、复杂计算及工具调用等方面表现尤为出色。" + + }, + { + "llmModel":"ernie-4.5-turbo-128k", + "supportChat":true, + "supportFunctionCalling":true, + "title":"ERNIE 4.5 Turbo", + "description":"​核心定位:更好的满足多轮长历史对话处理、长文档理解问答任务。适用场景:​1)复杂语义理解:支持中文知识问答、文学创作,尤其擅长文档理解(如DocVQA任务)。 ​2)数学推理:在中文数学问题(CMath基准)表现突出。" + }, + { + "llmModel":"ernie-4.5-turbo-vl-32k", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"ERNIE 4.5 Turbo VL", + "description":"​核心定位:多模态基础模型,支持文本、图像跨模态输入与生成。​适用场景:结合图文生成营销文案、视频脚本设计等。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1", + "description":"核心定位:专业优化推理模型,聚焦数学与逻辑任务。 ​适用场景: ​复杂数学问题:如高等数学题求解、科学计算模拟。 ​逻辑拆解与规划:业务流程自动化、学术研究中的假设验证。 ​STEM领域应用:物理建模、金融量化分析等需高精度推理的场景。" + } + ] + }, + "icon": "\n 百度\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "星火大模型", + "key": "spark", + "options":{ + "llmEndpoint":"https://spark-api-open.xf-yun.com", + "chatPath":"/v1/chat/completions", + "modelList":[ + { + "llmModel":"generalv3.5", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Spark Max", + "description":"旗舰级大语言模型,具有千亿级参数,核心能力全面升级,具备更强的数学、中文、代码和多模态能力。适用数理计算、逻辑推理等对效果有更高要求的业务场景。" + }, + { + "llmModel":"4.0Ultra", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Spark4.0 Ultra", + "description":"最强大的大语言模型版本,文本生成、语言理解、知识问答、逻辑推理、数学能力等方面实现超越GPT4 Turbo,优化联网搜索链路,提供更精准回答。" + } + ] + }, + "icon": "\n 星火大模型\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "Gitee", + "key": "gitee", + "options":{ + "llmEndpoint":"https://ai.gitee.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"kimi-k2-instruct", + "supportChat":true, + "supportFunctionCalling":true, + "title":"kimi-k2-instruct", + "description":"Kimi K2 是一个最先进的混合专家 (MoE) 语言模型,激活参数为 320 亿,总参数为 1 万亿。通过 Muon 优化器进行训练,Kimi K2 在前沿知识、推理和编码任务上表现出色,同时在智能体能力方面进行了精心优化" + + }, + { + "llmModel":"ERNIE-4.5-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "title":"ERNIE-4.5-Turbo", + "description":"文心4.5 Turbo在去幻觉、逻辑推理和代码能力等方面也有着明显增强。对比文心4.5,速度更快、价格更低。" + + }, + { + "llmModel":"ERNIE-X1-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "title":"ERNIE-X1-Turbo", + "description":"文心ERNIE X1 Turbo具备更长的思维链,更强的深度思考能力,进一步增强了多模态和工具调用能力,擅长文学创作、逻辑推理等" + + }, + { + "llmModel":"DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1", + "description":"DeepSeek-R1 是一款采用强化学习技术的推理模型,凭借少量标注数据大幅提升推理能力,性能媲美 OpenAI o1。" + + }, + { + "llmModel":"DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-V3", + "description":"DeepSeek-V3 是 685B 参数的高效 MoE 语言模型,性能优越,训练稳定,超越开源模型,并接近顶级闭源模型。" + + }, + { + "llmModel":"Qwen3-235B-A22B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Qwen3-235B-A22B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Qwen3-30B-A3B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Qwen3-32B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"ERNIE-4.5-Turbo-VL", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"ERNIE-4.5-Turbo-VL", + "description":"文心一言大模型全新版本,图片理解、创作、翻译、代码等能力显著提升,首次支持32K上下文长度,首Token时延显著降低。" + + }, + { + "llmModel":"Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是一款拥有 320 亿参数、支持多图输入与复杂图文推理的大规模多模态指令微调模型。" + + }, + { + "llmModel":"InternVL3-78B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"InternVL3-78B", + "description":"InternVL3-78B 是一款支持中英文、多图多轮对话的大规模多模态模型,具备超强图文理解、推理与生成能力,广泛适用于复杂 AI 应用场景。" + + }, + { + "llmModel":"InternVL3-38B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"InternVL3-38B", + "description":"InternVL3-38B 是一款支持中英双语、多模态对话与图像理解的大规模视觉语言模型,具备强大的跨模态推理与视觉问答能力。" + }, + { + "llmModel":"Qwen3-Embedding-8B", + "supportEmbed":true, + "title":"Qwen3-Embedding-8B", + "description":"Qwen3‑Embedding‑8B 是 Qwen 系列推出的大规模嵌入模型,专注于生成高质量、多语言及代码向量,支持多种下游任务中的语义匹配与信息检索需求。" + }, + { + "llmModel":"Qwen3-Embedding-4B", + "supportEmbed":true, + "title":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是由 Qwen 团队开发的一款高性能文本和代码嵌入模型,专为多语言、多模态任务设计,能够将文本和代码内容转换为语义丰富的向量表示。它广泛适用于语义搜索、跨语言检索、信息匹配、文本相似度分析等多种自然语言处理和代码理解场景。" + } + ] + }, + "icon": "\n Gitee\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "硅基流动", + "key": "siliconlow", + "options":{ + "llmEndpoint":"https://api.siliconflow.cn", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"deepseek-ai/DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-R1", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果。" + + }, + { + "llmModel":"deepseek-ai/DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "title":"DeepSeek-V3", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + + }, + { + "llmModel":"moonshotai/Kimi-K2-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Kimi-K2-Instruct", + "description":"Kimi K2 是一款具备超强代码和 Agent 能力的 MoE 架构基础模型,总参数 1T,激活参数 32B。在通用知识推理、编程、数学、Agent 等主要类别的基准性能测试中,K2 模型的性能超过其他主流开源模型" + + }, + { + "llmModel":"Tongyi-Zhiwen/QwenLong-L1-32B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"QwenLong-L1-32B", + "description":"QwenLong-L1-32B 是首个使用强化学习训练的长上下文大型推理模型(LRM),专门针对长文本推理任务进行优化。该模型通过渐进式上下文扩展的强化学习框架,实现了从短上下文到长上下文的稳定迁移。在七个长上下文文档问答基准测试中,QwenLong-L1-32B 超越了 OpenAI-o3-mini 和 Qwen3-235B-A22B 等旗舰模型,性能可媲美 Claude-3.7-Sonnet-Thinking。该模型特别擅长数学推理、逻辑推理和多跳推理等复杂任务" + + }, + { + "llmModel":"Qwen/Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Qwen3-30B-A3B", + "description":"Qwen3-30B-A3B 是通义千问系列的最新大语言模型,采用混合专家(MoE)架构,拥有 30.5B 总参数量和 3.3B 激活参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"Qwen/Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "title":"Qwen3-32B", + "description":"Qwen3-32B 是通义千问系列的最新大语言模型,拥有 32.8B 参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"MiniMaxAI/MiniMax-M1-80k", + "supportChat":true, + "supportFunctionCalling":true, + "title":"MiniMax-M1-80k", + "description":"MiniMax-M1 是开源权重的大规模混合注意力推理模型,拥有 4560 亿参数,每个 Token 可激活约 459 亿参数。模型原生支持 100 万 Token 的超长上下文,并通过闪电注意力机制,在 10 万 Token 的生成任务中相比 DeepSeek R1 节省 75% 的浮点运算量。同时,MiniMax-M1 采用 MoE(混合专家)架构,结合 CISPO 算法与混合注意力设计的高效强化学习训练,在长输入推理与真实软件工程场景中实现了业界领先的性能。" + + }, + { + "llmModel":"THUDM/GLM-4.1V-9B-Thinking", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"GLM-4.1V-9B-Thinking", + "description":"GLM-4.1V-9B-Thinking 是由智谱 AI 和清华大学 KEG 实验室联合发布的一款开源视觉语言模型(VLM),专为处理复杂的多模态认知任务而设计。该模型基于 GLM-4-9B-0414 基础模型,通过引入“思维链”(Chain-of-Thought)推理机制和采用强化学习策略,显著提升了其跨模态的推理能力和稳定性。作为一个 9B 参数规模的轻量级模型,它在部署效率和性能之间取得了平衡,在 28 项权威评测基准中,有 18 项的表现持平甚至超越了 72B 参数规模的 Qwen-2.5-VL-72B。该模型不仅在图文理解、数学科学推理、视频理解等任务上表现卓越,还支持高达 4K 分辨率的图像和任意宽高比输入" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是通义千问团队推出的多模态大模型,是 Qwen2.5-VL 系列的一部分。该模型不仅精通识别常见物体,还能分析图像中的文本、图表、图标、图形和布局。它可作为视觉智能体,能够推理并动态操控工具,具备使用电脑和手机的能力。此外,这个模型可以精确定位图像中的对象,并为发票、表格等生成结构化输出。相比前代模型 Qwen2-VL,该版本在数学和问题解决能力方面通过强化学习得到了进一步提升,响应风格也更符合人类偏好" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-72B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"Qwen2.5-VL-72B-Instruct", + "description":"Qwen2.5-VL 是 Qwen2.5 系列中的视觉语言模型。该模型在多方面有显著提升:具备更强的视觉理解能力,能够识别常见物体、分析文本、图表和布局;作为视觉代理能够推理并动态指导工具使用;支持理解超过 1 小时的长视频并捕捉关键事件;能够通过生成边界框或点准确定位图像中的物体;支持生成结构化输出,尤其适用于发票、表格等扫描数据。模型在多项基准测试中表现出色,包括图像、视频和代理任务评测" + }, + { + "llmModel":"deepseek-ai/deepseek-vl2", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "title":"deepseek-vl2", + "description":"DeepSeek-VL2 是一个基于 DeepSeekMoE-27B 开发的混合专家(MoE)视觉语言模型,采用稀疏激活的 MoE 架构,在仅激活 4.5B 参数的情况下实现了卓越性能。该模型在视觉问答、光学字符识别、文档/表格/图表理解和视觉定位等多个任务中表现优异,与现有的开源稠密模型和基于 MoE 的模型相比,在使用相同或更少的激活参数的情况下,实现了具有竞争力的或最先进的性能表现" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-8B", + "supportEmbed":true, + "title":"Qwen3-Embedding-8B", + "description":"Qwen3-Embedding-8B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 80 亿参数规模,支持长达 32K 的上下文长度,可生成最高 4096 维的嵌入向量。该模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上排名第一(截至 2025 年 6 月 5 日,得分 70.58),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 4096)和指令感知功能,可根据特定任务、语言或场景进行优化" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-4B", + "supportEmbed":true, + "title":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 40 亿参数规模,支持长达 32K 的上下文长度,可生成最高 2560 维的嵌入向量。模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上表现卓越(得分 69.45),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 2560)和指令感知功能,可根据特定任务、语言或场景进行优化,在效率和效果之间达到良好平衡" + }, + { + "llmModel":"BAAI/bge-m3", + "supportEmbed":true, + "title":"bge-m3", + "description":"BGE-M3 是一个多功能、多语言、多粒度的文本嵌入模型。它支持三种常见的检索功能:密集检索、多向量检索和稀疏检索。该模型可以处理超过100种语言,并且能够处理从短句到长达8192个词元的长文档等不同粒度的输入。BGE-M3在多语言和跨语言检索任务中表现出色,在 MIRACL 和 MKQA 等基准测试中取得了领先结果。它还具有处理长文档检索的能力,在 MLDR 和 NarritiveQA 等数据集上展现了优秀性能" + }, + { + "llmModel":"netease-youdao/bce-embedding-base_v1", + "supportEmbed":true, + "title":"bce-embedding-base_v1", + "description":"bce-embedding-base_v1 是由网易有道开发的双语和跨语言嵌入模型。该模型在中英文语义表示和检索任务中表现出色,尤其擅长跨语言场景。它是为检索增强生成(RAG)系统优化的,可以直接应用于教育、医疗、法律等多个领域。该模型不需要特定指令即可使用,能够高效地生成语义向量,为语义搜索和问答系统提供关键支持" + } + + ] + }, + "icon": "\n 硅基流动\n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "title": "Ollama", + "key": "ollama", + "options":{ + + }, + "icon": "\n 编组 4备份 9\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + } +] \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/application-prod.yml b/easyflow-starter/easyflow-starter-all/src/main/resources/application-prod.yml new file mode 100644 index 0000000..9ea78cb --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/application-prod.yml @@ -0,0 +1,8 @@ +spring: + config: + activate: + on-profile: prod + datasource: + url: jdbc:mysql://127.0.0.1:3306/easyflow?useInformationSchema=true&characterEncoding=utf-8 + username: easyflow + password: 123456 \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml b/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml new file mode 100644 index 0000000..77f3e24 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml @@ -0,0 +1,146 @@ +server: + port: 8111 + address: 0.0.0.0 + shutdown: graceful + # 启用HTTP响应压缩 + compression: + enabled: true + servlet: + encoding: + enabled: true + charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题 + force: true +spring: + profiles: + active: dev + datasource: + # !!! 注意:useInformationSchema=true 是必须的,用于支持 MyBatis-Flex 正确读取表注释。 + url: jdbc:mysql://127.0.0.1:23306/easyflow?useInformationSchema=true&characterEncoding=utf-8 + username: root + password: root + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + web: + resources: + # 示例:windows【file: C:\easyflow\attachment】 linux【file: /www/easyflow/attachment】 + static-locations: file:/Users/slience/postgraduate/easyflow/attachment + mvc: + pathmatch: + matching-strategy: ant_path_matcher + # 静态资源路径,用于访问本地文件 + # !!! 注意,这里要和下面的 easyflow.storage.local.prefix 后面的路径 /attachment 保持一致! + static-path-pattern: /attachment/** + # quartz 相关配置 + quartz: + startup-delay: 1 + job-store-type: jdbc + jdbc: + platform: mysql + initialize-schema: never + properties: + org: + quartz: + jobStore: + misfireThreshold: 1000 + # 如果数据库大小写敏感,可将ddl里的相关表名改为大写 + tablePrefix: TB_QRTZ_ + threadPool: + threadCount: 20 + threadPriority: 5 + threads: + virtual: + enabled: true +easyflow: + # 语音播放、识别服务(阿里云) + audio: + type: aliAudioService + ali: + access-key-id: xxx + access-key-secret: xxx + app-key: xxx + voice: siyue + login: + # 放行接口路径 + excludes: /api/v1/auth/**, /static/**, /userCenter/auth/**, /userCenter/public/** + storage: + type: local # xFileStorage / local + # 本地文件存储配置 + local: + # 示例:windows【C:\easyflow\attachment】 linux【/www/easyflow/attachment】 + root: /Users/slience/postgraduate/easyflow/attachment + # 后端接口地址,用于拼接完整 url + prefix: http://localhost:8080/attachment +# xFileStorage存储文件配置 +dromara: + x-file-storage: #文件存储配置 + default-platform: aliyun-oss-1 #默认使用的存储平台 + aliyun-oss: + - platform: aliyun-oss-1 # 存储平台标识 + enable-storage: true # 启用存储 + access-key: yourAccessKeyId + secret-key: yourAccessKeySecret + end-point: yourEndpoint # 示例:https://oss-cn-beijing.aliyuncs.com + bucket-name: yourBucketName + domain: yourDomain # 访问域名,注意“/”结尾,例如:https://bucketname.oss-cn-shanghai.aliyuncs.com/ + base-path: attachment # 基础路径 +# 自定义节点相关配置 +node: + # 文件内容提取节点,默认使用简单文档读取器,可自行实现 ReadDocService + reader: 'defaultReader' + # gitee 文档读取的实现,需要配置gitee的appKey + gitee: + appKey: 'xxx' + # 搜索引擎节点 - 目前只支持博查搜索 + bochaai: + apiKey: 'xxx' +jetcache: + # 缓存类型,可选值:local/remote/both CacheConfig 类初始化 + cacheType: local + statIntervalMinutes: 15 + areaInCacheName: false + local: + default: + type: linkedhashmap + keyConvertor: fastjson + remote: + default: + type: redis + keyConvertor: fastjson2 + broadcastChannel: projectA + valueEncoder: java + valueDecoder: java + poolConfig: + minIdle: 5 + maxIdle: 20 + maxTotal: 50 + host: 127.0.0.1 + port: 6379 + password: pwd + database: 0 +# 多路召回搜索引擎配置 +rag: + searcher: + # 搜索方式 默认lucene + lucene: + indexDirPath: ./luceneKnowledge + elastic: + host: https://127.0.0.1:9200 + userName: elastic + password: elastic + indexName: easyflow +logging: + file: + path: .logs/ + name: easyflow.log + level: + root: info + tech.easyflow.ai: debug +# 行为验证码配置 +captcha: + # 初始化默认资源 + init-default-resource: true + # 开启二次验证 + secondary: + enabled: true \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/1.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/1.jpg new file mode 100644 index 0000000..0322666 Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/1.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/10.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/10.jpg new file mode 100644 index 0000000..52ea6f0 Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/10.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/2.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/2.jpg new file mode 100644 index 0000000..38ceb29 Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/2.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/3.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/3.jpg new file mode 100644 index 0000000..609570b Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/3.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/4.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/4.jpg new file mode 100644 index 0000000..4f59d4a Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/4.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/5.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/5.jpg new file mode 100644 index 0000000..a47292e Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/5.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/6.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/6.jpg new file mode 100644 index 0000000..8dd857c Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/6.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/7.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/7.jpg new file mode 100644 index 0000000..859c3fd Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/7.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/8.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/8.jpg new file mode 100644 index 0000000..828c484 Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/8.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/9.jpg b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/9.jpg new file mode 100644 index 0000000..8cfde91 Binary files /dev/null and b/easyflow-starter/easyflow-starter-all/src/main/resources/captcha-images/9.jpg differ diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/logback-spring.xml b/easyflow-starter/easyflow-starter-all/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b3720f1 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/logback-spring.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + UTF-8 + + + + + + ${LOG_PATH}/${LOG_FILE} + + ${LOG_PATH}/%d{yyyy-MM-dd}/${LOG_FILE}-%i + 50MB + 30 + + + %d{MM-dd HH:mm:ss.SSS} |-%-5level %logger{36}:%L - %m%n + + + + + + + + + + + + \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-codegen/pom.xml b/easyflow-starter/easyflow-starter-codegen/pom.xml new file mode 100644 index 0000000..0d0e774 --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + tech.easyflow + easyflow-starter + ${revision} + + + easyflow-starter-codegen + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + + + com.mybatis-flex + mybatis-flex-codegen + + + tech.easyflow + easyflow-common-base + + + com.mysql + mysql-connector-j + runtime + + + com.zaxxer + HikariCP + + + + \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AIModuleGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AIModuleGen.java new file mode 100644 index 0000000..dca9c51 --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AIModuleGen.java @@ -0,0 +1,69 @@ +package tech.easyflow.codegen; + +import com.mybatisflex.codegen.Generator; +import com.mybatisflex.codegen.config.ColumnConfig; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.mybatisflex.codegen.dialect.JdbcTypeMapping; +import com.mybatisflex.core.handler.CommaSplitTypeHandler; +import com.zaxxer.hikari.HikariDataSource; + +public class AIModuleGen { + public static void main(String[] args) { + //配置数据源 + HikariDataSource dataSource = new HikariDataSource(); + + //注意:url 需添加上 useInformationSchema=true 才能正常获取表的注释 + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.10:3306/easyflow-v2?useInformationSchema=true&characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + + // 设置时间类型为 Date + JdbcTypeMapping.registerDateTypes(); + + + //生成 framework-modules/easyflow-module-ai 下的代码 + GlobalConfig globalConfig = createGlobalConfig(); + Generator moduleGenerator = new Generator(dataSource, globalConfig); + moduleGenerator.generate(); + } + + + public static GlobalConfig createGlobalConfig() { + + String optionsColumns = "options,vector_store_options,model_options"; + + //创建配置内容 + GlobalConfig globalConfig = Util.createBaseConfig(optionsColumns); + globalConfig.setBasePackage("tech.easyflow.ai"); + + globalConfig.setGenerateTable("tb_bot" + , "tb_bot_api_key", "tb_bot_category" + , "tb_bot_conversation", "tb_bot_document_collection", "tb_bot_message" + , "tb_bot_model", "tb_bot_plugin", "tb_bot_recently_used", "tb_bot_workflow" + , "tb_document", "tb_document_chunk", "tb_document_collection", "tb_document_collection_category", "tb_document_history" + , "tb_model", "tb_model_provider" + , "tb_plugin", "tb_plugin_category", "tb_plugin_category_mapping", "tb_plugin_item" + , "tb_resource", "tb_resource_category" + , "tb_workflow", "tb_workflow_category", "tb_workflow_exec_result", "tb_workflow_exec_step", "tb_mcp", "tb_bot_mcp" + ); + + String sourceDir = System.getProperty("user.dir") + "/easyflow-modules/easyflow-module-ai/src/main/java"; + globalConfig.setSourceDir(sourceDir); + + + ColumnConfig tablesColumnConfig = new ColumnConfig(); + tablesColumnConfig.setPropertyType("java.util.List"); + tablesColumnConfig.setTypeHandler(CommaSplitTypeHandler.class); + tablesColumnConfig.setColumnName("tables"); + globalConfig.setColumnConfig("tb_dev_module", tablesColumnConfig); + + ColumnConfig validRolesColumnConfig = new ColumnConfig(); + validRolesColumnConfig.setPropertyType("java.util.List"); + validRolesColumnConfig.setTypeHandler(CommaSplitTypeHandler.class); + validRolesColumnConfig.setColumnName("valid_roles"); + globalConfig.setColumnConfig("tb_dev_table_field", validRolesColumnConfig); + + return globalConfig; + } + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AllModuleGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AllModuleGen.java new file mode 100644 index 0000000..6b9969e --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/AllModuleGen.java @@ -0,0 +1,11 @@ +package tech.easyflow.codegen; + +public class AllModuleGen { + public static void main(String[] args) { + AIModuleGen.main(args); + DatacenterModuleGen.main(args); + JobModuleGen.main(args); + SystemModuleGen.main(args); + } + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/DatacenterModuleGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/DatacenterModuleGen.java new file mode 100644 index 0000000..119cdbc --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/DatacenterModuleGen.java @@ -0,0 +1,50 @@ +package tech.easyflow.codegen; + +import com.mybatisflex.codegen.Generator; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.mybatisflex.codegen.dialect.JdbcTypeMapping; +import com.zaxxer.hikari.HikariDataSource; + +public class DatacenterModuleGen { + public static void main(String[] args) { + //配置数据源 + HikariDataSource dataSource = new HikariDataSource(); + + //注意:url 需添加上 useInformationSchema=true 才能正常获取表的注释 + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.10:3306/easyflow-v2?useInformationSchema=true&characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + + // 设置时间类型为 Date + JdbcTypeMapping.registerDateTypes(); + + + //生成 framework-modules/easyflow-module-datacenter 下的代码 + GlobalConfig globalConfig = createGlobalConfig(); + Generator moduleGenerator = new Generator(dataSource, globalConfig); + moduleGenerator.generate(); + + } + + + + public static GlobalConfig createGlobalConfig() { + + String optionsColumns = "options,vector_store_options,llm_options"; + + //创建配置内容 + GlobalConfig globalConfig = Util.createBaseConfig(optionsColumns); + globalConfig.setBasePackage("tech.easyflow.datacenter"); + + + globalConfig.setGenerateTable("tb_datacenter_table", "tb_datacenter_table_field"); + String sourceDir = System.getProperty("user.dir") + "/easyflow-modules/easyflow-module-datacenter/src/main/java"; + globalConfig.setSourceDir(sourceDir); + + return globalConfig; + } + + + + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/JobModuleGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/JobModuleGen.java new file mode 100644 index 0000000..e3d7045 --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/JobModuleGen.java @@ -0,0 +1,50 @@ +package tech.easyflow.codegen; + +import com.mybatisflex.codegen.Generator; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.mybatisflex.codegen.dialect.JdbcTypeMapping; +import com.zaxxer.hikari.HikariDataSource; + +public class JobModuleGen { + public static void main(String[] args) { + //配置数据源 + HikariDataSource dataSource = new HikariDataSource(); + + //注意:url 需添加上 useInformationSchema=true 才能正常获取表的注释 + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.10:3306/easyflow-v2?useInformationSchema=true&characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + + // 设置时间类型为 Date + JdbcTypeMapping.registerDateTypes(); + + + //生成 framework-modules/easyflow-module-job 下的代码 + GlobalConfig globalConfig = createGlobalConfig(); + Generator moduleGenerator = new Generator(dataSource, globalConfig); + moduleGenerator.generate(); + + } + + + + + public static GlobalConfig createGlobalConfig() { + + String optionsColumns = "options,job_params"; + + //创建配置内容 + GlobalConfig globalConfig = Util.createBaseConfig(optionsColumns); + globalConfig.setBasePackage("tech.easyflow.job"); + + + + globalConfig.setGenerateTable("tb_sys_job", "tb_sys_job_log"); + String sourceDir = System.getProperty("user.dir") + "/easyflow-modules/easyflow-module-job/src/main/java"; + globalConfig.setSourceDir(sourceDir); + + return globalConfig; + } + + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/PasswordGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/PasswordGen.java new file mode 100644 index 0000000..179479b --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/PasswordGen.java @@ -0,0 +1,17 @@ +package tech.easyflow.codegen; + +import tech.easyflow.common.util.HashUtil; + +public class PasswordGen { + + public static void main(String[] args) { + String password = "123456"; + String salt = HashUtil.generateSalt(24); + String hashPassword = HashUtil.sha256(salt + password); + System.out.println("salt: " + salt); + System.out.println("password: " + password); + System.out.println("hashPassword: " + hashPassword); + } + + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/SystemModuleGen.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/SystemModuleGen.java new file mode 100644 index 0000000..b6ac6b6 --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/SystemModuleGen.java @@ -0,0 +1,53 @@ +package tech.easyflow.codegen; + +import com.mybatisflex.codegen.Generator; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.mybatisflex.codegen.dialect.JdbcTypeMapping; +import com.zaxxer.hikari.HikariDataSource; + +public class SystemModuleGen { + public static void main(String[] args) { + //配置数据源 + HikariDataSource dataSource = new HikariDataSource(); + + //注意:url 需添加上 useInformationSchema=true 才能正常获取表的注释 + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.10:3306/easyflow-v2?useInformationSchema=true&characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + + // 设置时间类型为 Date + JdbcTypeMapping.registerDateTypes(); + + + //生成 framework-modules/easyflow-module-system 下的代码 + GlobalConfig globalConfig = createGlobalConfig(); + Generator moduleGenerator = new Generator(dataSource, globalConfig); + moduleGenerator.generate(); + } + + + + + + + public static GlobalConfig createGlobalConfig() { + + String optionsColumns = "options,vector_store_options,llm_options"; + + //创建配置内容 + GlobalConfig globalConfig = Util.createBaseConfig(optionsColumns); + globalConfig.setBasePackage("tech.easyflow.system"); + + + globalConfig.setGenerateTable("tb_sys_account", "tb_sys_account_position", "tb_sys_account_role" + , "tb_sys_api_key", "tb_sys_api_key_resource", "tb_sys_api_key_resource_mapping" + , "tb_sys_dept", "tb_sys_dict", "tb_sys_dict_item", "tb_sys_log", "tb_sys_menu", "tb_sys_option" + , "tb_sys_position", "tb_sys_role", "tb_sys_role_menu", "tb_sys_user_feedback" + ); + String sourceDir = System.getProperty("user.dir") + "/easyflow-modules/easyflow-module-system/src/main/java"; + globalConfig.setSourceDir(sourceDir); + + return globalConfig; + } + +} diff --git a/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/Util.java b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/Util.java new file mode 100644 index 0000000..b57f576 --- /dev/null +++ b/easyflow-starter/easyflow-starter-codegen/src/main/java/tech/easyflow/codegen/Util.java @@ -0,0 +1,102 @@ +package tech.easyflow.codegen; + +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.codegen.config.ColumnConfig; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.mybatisflex.core.handler.FastjsonTypeHandler; +import com.mybatisflex.core.keygen.KeyGenerators; +import tech.easyflow.common.entity.DateEntity; +import tech.easyflow.common.entity.DateTreeEntity; +import tech.easyflow.common.entity.TreeEntity; +import tech.easyflow.common.util.StringUtil; + +public class Util { + + + public static GlobalConfig createBaseConfig() { + return createBaseConfig("options"); + } + + + public static GlobalConfig createBaseConfig(String optionsColumns) { + + //创建配置内容 + GlobalConfig globalConfig = new GlobalConfig(); + + //设置表前缀和只生成哪些表 + globalConfig.setTablePrefix("tb_"); + + //设置生成 entity + globalConfig.setEntityGenerateEnable(true); + + globalConfig.getEntityConfig().setWithBaseClassEnable(true); + globalConfig.getEntityConfig().setBaseOverwriteEnable(true); + globalConfig.getEntityConfig().setColumnCommentEnable(true); + + + globalConfig.setMapperGenerateEnable(true); + globalConfig.setServiceGenerateEnable(true); + globalConfig.setServiceImplGenerateEnable(true); + + + // 设置 entity 父类 + globalConfig.setEntitySuperClassFactory(table -> { + if (table.containsColumn("id", "pid") || table.containsColumn("id","parent_id")) { + if (table.containsColumn("created", "modified")) { + return DateTreeEntity.class; + } else { + return TreeEntity.class; + } + } else if (table.containsColumn("created", "modified")) { + return DateEntity.class; + } + return null; + }); + + // 不生成 Controller + globalConfig.setControllerGenerateEnable(false); +// globalConfig.setControllerOverwriteEnable(false); +// try { +// ClassPathResource cpr = new ClassPathResource("templates/controller.tpl"); +// globalConfig.setControllerTemplatePath(cpr.getFile().getAbsolutePath()); +// } catch (IOException e) { +// throw new RuntimeException(e); +// } + + ColumnConfig pkColumnConfig = new ColumnConfig(); + pkColumnConfig.setPrimaryKey(true); + pkColumnConfig.setKeyType(KeyType.Generator); + pkColumnConfig.setKeyValue(KeyGenerators.snowFlakeId); + pkColumnConfig.setColumnName("id"); + + + ColumnConfig tenantColumnConfig = new ColumnConfig(); + tenantColumnConfig.setTenantId(true); + tenantColumnConfig.setColumnName("tenant_id"); + + ColumnConfig logicDeleteColumnConfig = new ColumnConfig(); + logicDeleteColumnConfig.setLogicDelete(true); + logicDeleteColumnConfig.setColumnName("is_deleted"); + + globalConfig.setColumnConfig(pkColumnConfig); + globalConfig.setColumnConfig(tenantColumnConfig); + globalConfig.setColumnConfig(logicDeleteColumnConfig); + + + String[] optionsColumnArray = optionsColumns.split(","); + for (String optionsColumn : optionsColumnArray) { + if (StringUtil.hasText(optionsColumn)) { + ColumnConfig optionsColumnConfig = new ColumnConfig(); + optionsColumnConfig.setPropertyType("java.util.Map"); + optionsColumnConfig.setTypeHandler(FastjsonTypeHandler.class); + optionsColumnConfig.setColumnName(optionsColumn.trim()); + globalConfig.setColumnConfig(optionsColumnConfig); + } + } + + + return globalConfig; + } + + +} diff --git a/easyflow-starter/easyflow-starter-public/pom.xml b/easyflow-starter/easyflow-starter-public/pom.xml new file mode 100644 index 0000000..e3cd3db --- /dev/null +++ b/easyflow-starter/easyflow-starter-public/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + tech.easyflow + easyflow-starter + ${revision} + + + easyflow-starter-public + + \ No newline at end of file diff --git a/easyflow-starter/easyflow-starter-usercenter/pom.xml b/easyflow-starter/easyflow-starter-usercenter/pom.xml new file mode 100644 index 0000000..1bd4f20 --- /dev/null +++ b/easyflow-starter/easyflow-starter-usercenter/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + tech.easyflow + easyflow-starter + ${revision} + + + easyflow-starter-usercenter + + \ No newline at end of file diff --git a/easyflow-starter/pom.xml b/easyflow-starter/pom.xml new file mode 100644 index 0000000..ef74545 --- /dev/null +++ b/easyflow-starter/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + tech.easyflow + easyflow + ${revision} + + + easyflow-starter + pom + + + easyflow-starter-admin + easyflow-starter-all + easyflow-starter-public + easyflow-starter-usercenter + easyflow-starter-codegen + + + \ No newline at end of file diff --git a/easyflow-ui-admin/.browserslistrc b/easyflow-ui-admin/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/easyflow-ui-admin/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/easyflow-ui-admin/.changeset/README.md b/easyflow-ui-admin/.changeset/README.md new file mode 100644 index 0000000..5654e89 --- /dev/null +++ b/easyflow-ui-admin/.changeset/README.md @@ -0,0 +1,5 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/easyflow-ui-admin/.changeset/config.json b/easyflow-ui-admin/.changeset/config.json new file mode 100644 index 0000000..97e251b --- /dev/null +++ b/easyflow-ui-admin/.changeset/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "easyflow/easyflow" } + ], + "commit": false, + "fixed": [["@easyflow-core/*", "@easyflow/*"]], + "snapshot": { + "prereleaseTemplate": "{tag}-{datetime}" + }, + "privatePackages": { "version": true, "tag": true }, + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/easyflow-ui-admin/.commitlintrc.js b/easyflow-ui-admin/.commitlintrc.js new file mode 100644 index 0000000..dda66a2 --- /dev/null +++ b/easyflow-ui-admin/.commitlintrc.js @@ -0,0 +1 @@ +export { default } from '@easyflow/commitlint-config'; diff --git a/easyflow-ui-admin/.dockerignore b/easyflow-ui-admin/.dockerignore new file mode 100644 index 0000000..52b833a --- /dev/null +++ b/easyflow-ui-admin/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +.gitignore +*.md +dist +.turbo +dist.zip diff --git a/easyflow-ui-admin/.editorconfig b/easyflow-ui-admin/.editorconfig new file mode 100644 index 0000000..179aec6 --- /dev/null +++ b/easyflow-ui-admin/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=2 +max_line_length = 100 +trim_trailing_whitespace = true +quote_type = single + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/easyflow-ui-admin/.gitattributes b/easyflow-ui-admin/.gitattributes new file mode 100644 index 0000000..d4e5bd3 --- /dev/null +++ b/easyflow-ui-admin/.gitattributes @@ -0,0 +1,11 @@ +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Automatically normalize line endings (to LF) for all text-based files. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file diff --git a/easyflow-ui-admin/.gitconfig b/easyflow-ui-admin/.gitconfig new file mode 100644 index 0000000..4b28a69 --- /dev/null +++ b/easyflow-ui-admin/.gitconfig @@ -0,0 +1,2 @@ +[core] + ignorecase = false diff --git a/easyflow-ui-admin/.gitignore b/easyflow-ui-admin/.gitignore new file mode 100644 index 0000000..3399f39 --- /dev/null +++ b/easyflow-ui-admin/.gitignore @@ -0,0 +1,52 @@ +node_modules +.DS_Store +dist +dist-ssr +dist.zip +dist.tar +dist.war +.nitro +.output +*-dist.zip +*-dist.tar +*-dist.war +coverage +*.local +**/.vitepress/cache +.cache +.turbo +.temp +dev-dist +.stylelintcache +yarn.lock +package-lock.json +.VSCodeCounter +**/backend-mock/data + +# local env files +.env.local +.env.*.local +.eslintcache + +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +vite.config.mts.* +vite.config.mjs.* +vite.config.js.* +vite.config.ts.* + +# Editor directories and files +.idea +# .vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.history +.cursor diff --git a/easyflow-ui-admin/.gitpod.yml b/easyflow-ui-admin/.gitpod.yml new file mode 100644 index 0000000..5fda2cf --- /dev/null +++ b/easyflow-ui-admin/.gitpod.yml @@ -0,0 +1,6 @@ +ports: + - port: 5555 + onOpen: open-preview +tasks: + - init: npm i -g corepack && pnpm install + command: pnpm run dev:play diff --git a/easyflow-ui-admin/.node-version b/easyflow-ui-admin/.node-version new file mode 100644 index 0000000..ee5c244 --- /dev/null +++ b/easyflow-ui-admin/.node-version @@ -0,0 +1 @@ +22.1.0 diff --git a/easyflow-ui-admin/.npmrc b/easyflow-ui-admin/.npmrc new file mode 100644 index 0000000..aeac1ae --- /dev/null +++ b/easyflow-ui-admin/.npmrc @@ -0,0 +1,13 @@ +registry=https://registry.npmmirror.com +public-hoist-pattern[]=lefthook +public-hoist-pattern[]=eslint +public-hoist-pattern[]=prettier +public-hoist-pattern[]=prettier-plugin-tailwindcss +public-hoist-pattern[]=stylelint +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=@commitlint/* +public-hoist-pattern[]=czg + +strict-peer-dependencies=false +auto-install-peers=true +dedupe-peer-dependents=true diff --git a/easyflow-ui-admin/.prettierignore b/easyflow-ui-admin/.prettierignore new file mode 100644 index 0000000..d0b0ca1 --- /dev/null +++ b/easyflow-ui-admin/.prettierignore @@ -0,0 +1,18 @@ +dist +dev-dist +.local +.output.js +node_modules +.nvmrc +coverage +CODEOWNERS +.nitro +.output + + +**/*.svg +**/*.sh + +public +.npmrc +*-lock.yaml diff --git a/easyflow-ui-admin/.prettierrc.mjs b/easyflow-ui-admin/.prettierrc.mjs new file mode 100644 index 0000000..efde5ec --- /dev/null +++ b/easyflow-ui-admin/.prettierrc.mjs @@ -0,0 +1 @@ +export { default } from '@easyflow/prettier-config'; diff --git a/easyflow-ui-admin/.stylelintignore b/easyflow-ui-admin/.stylelintignore new file mode 100644 index 0000000..f4b2db2 --- /dev/null +++ b/easyflow-ui-admin/.stylelintignore @@ -0,0 +1,4 @@ +dist +public +__tests__ +coverage diff --git a/easyflow-ui-admin/app/.env b/easyflow-ui-admin/app/.env new file mode 100644 index 0000000..3961cfb --- /dev/null +++ b/easyflow-ui-admin/app/.env @@ -0,0 +1,8 @@ +# 应用标题 +VITE_APP_TITLE=EasyFlow + +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 +VITE_APP_NAMESPACE=easyflow-web + +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/easyflow-ui-admin/app/.env.analyze b/easyflow-ui-admin/app/.env.analyze new file mode 100644 index 0000000..ffafa8d --- /dev/null +++ b/easyflow-ui-admin/app/.env.analyze @@ -0,0 +1,7 @@ +# public path +VITE_BASE=/ + +# Basic interface address SPA +VITE_GLOB_API_URL=/api + +VITE_VISUALIZER=true diff --git a/easyflow-ui-admin/app/.env.development b/easyflow-ui-admin/app/.env.development new file mode 100644 index 0000000..54e5744 --- /dev/null +++ b/easyflow-ui-admin/app/.env.development @@ -0,0 +1,13 @@ +# 端口号 +VITE_PORT=5090 + +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL=http://127.0.0.1:8111 + +# 是否打开 devtools,true 为打开,false 为关闭 +VITE_DEVTOOLS=false + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true diff --git a/easyflow-ui-admin/app/.env.production b/easyflow-ui-admin/app/.env.production new file mode 100644 index 0000000..f939413 --- /dev/null +++ b/easyflow-ui-admin/app/.env.production @@ -0,0 +1,19 @@ +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL= + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=none + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=hash + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/easyflow-ui-admin/app/index.html b/easyflow-ui-admin/app/index.html new file mode 100644 index 0000000..e622043 --- /dev/null +++ b/easyflow-ui-admin/app/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + <%= VITE_APP_TITLE %> + + + + +
+ + + + + diff --git a/easyflow-ui-admin/app/package.json b/easyflow-ui-admin/app/package.json new file mode 100644 index 0000000..007cdce --- /dev/null +++ b/easyflow-ui-admin/app/package.json @@ -0,0 +1,52 @@ +{ + "name": "@easyflow/app", + "version": "1.0.0", + "type": "module", + "scripts": { + "build": "pnpm vite build --mode production", + "build:analyze": "pnpm vite build --mode analyze", + "dev": "pnpm vite --mode development", + "preview": "vite preview", + "typecheck": "vue-tsc --noEmit --skipLibCheck" + }, + "imports": { + "#/*": "./src/*" + }, + "dependencies": { + "@easyflow-core/shadcn-ui": "workspace:*", + "@easyflow/access": "workspace:*", + "@easyflow/common-ui": "workspace:*", + "@easyflow/constants": "workspace:*", + "@easyflow/hooks": "workspace:*", + "@easyflow/icons": "workspace:*", + "@easyflow/layouts": "workspace:*", + "@easyflow/locales": "workspace:*", + "@easyflow/plugins": "workspace:*", + "@easyflow/preferences": "workspace:*", + "@easyflow/request": "workspace:*", + "@easyflow/stores": "workspace:*", + "@easyflow/styles": "workspace:*", + "@easyflow/types": "workspace:*", + "@easyflow/utils": "workspace:*", + "@element-plus/icons-vue": "^2.3.2", + "@tinyflow-ai/vue": "^1.2.2", + "@vueuse/core": "catalog:", + "dayjs": "catalog:", + "dompurify": "^3.3.1", + "element-plus": "catalog:", + "fetch-event-stream": "^0.1.6", + "highlight.js": "^11.11.1", + "markdown-it": "^14.1.0", + "pinia": "catalog:", + "radash": "^12.1.1", + "vue": "catalog:", + "vue-cropper": "^1.1.4", + "vue-element-plus-x": "catalog:", + "vue-router": "catalog:", + "vue3-json-viewer": "^2.4.1" + }, + "devDependencies": { + "cssnano": "catalog:", + "unplugin-element-plus": "catalog:" + } +} diff --git a/easyflow-ui-admin/app/postcss.config.mjs b/easyflow-ui-admin/app/postcss.config.mjs new file mode 100644 index 0000000..75827b2 --- /dev/null +++ b/easyflow-ui-admin/app/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@easyflow/tailwind-config/postcss'; diff --git a/easyflow-ui-admin/app/public/empty-dark.png b/easyflow-ui-admin/app/public/empty-dark.png new file mode 100644 index 0000000..d1537a4 Binary files /dev/null and b/easyflow-ui-admin/app/public/empty-dark.png differ diff --git a/easyflow-ui-admin/app/public/empty.png b/easyflow-ui-admin/app/public/empty.png new file mode 100644 index 0000000..41429c1 Binary files /dev/null and b/easyflow-ui-admin/app/public/empty.png differ diff --git a/easyflow-ui-admin/app/public/favicon.ico b/easyflow-ui-admin/app/public/favicon.ico new file mode 100644 index 0000000..3de8f5a Binary files /dev/null and b/easyflow-ui-admin/app/public/favicon.ico differ diff --git a/easyflow-ui-admin/app/public/favicon.svg b/easyflow-ui-admin/app/public/favicon.svg new file mode 100644 index 0000000..5e74e86 --- /dev/null +++ b/easyflow-ui-admin/app/public/favicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/easyflow-ui-admin/app/public/load.min.js b/easyflow-ui-admin/app/public/load.min.js new file mode 100644 index 0000000..f482e16 --- /dev/null +++ b/easyflow-ui-admin/app/public/load.min.js @@ -0,0 +1 @@ +const Math=window.Math,head=document.getElementsByTagName("head")[0],TIMEOUT=1e4,TAC_LOADING_DIV='
请稍等...
';function showLoading(e){var t=document.querySelector(e);t&&(t.innerHTML=TAC_LOADING_DIV)}function hideLoading(e){let t=document.querySelector(e);t&&(t.innerHTML="")}function loadCaptchaScript(e,t,n,r,o){const i=e.scriptUrls,c=e.cssUrls,l=e.timeout||TIMEOUT;let s=i.length+c.length;function d(e,i){if(s--,e&&0===s){if(hideLoading(t.bindEl),!window.TAC)throw new Error("TAC未加载,请检查地址是否正确");r(new TAC(t,n))}else e||(hideLoading(t.bindEl),o(i))}setTimeout(()=>{0!==s&&showLoading(t.bindEl)},10),i.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"script",l)}),c.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"link",l)})}function loadResource(e,t,n="script",r){if(document.querySelector(`${n}[${"script"===n?"src":"href"}="${e.url}"]`))return void t(!0,e);let o=!1;const i=document.createElement(n);"link"===n?i.rel="stylesheet":i.async=!0,i["script"===n?"src":"href"]=e.url;let c;i.onload=i.onreadystatechange=(()=>{o||i.readyState&&"loaded"!==i.readyState&&"complete"!==i.readyState||function t(n){e.checkOnReady?c=setTimeout(()=>{e.checkOnReady()?n():t(n)},10):n()}(()=>{o=!0,setTimeout(()=>t(o,e),0)})}),i.onerror=(()=>{t(o=!1,e)}),head.appendChild(i),setTimeout(()=>{o||(c&&clearTimeout(c),i.onload=i.onerror=null,i.remove&&i.remove(),t(o,e))},r||TIMEOUT)}function loadTAC(e,t,n){return new Promise((r,o)=>{let i={..."string"==typeof e?{url:e}:e};i.url&&(i.url.endsWith("/")||(i.url+="/"),i.scriptUrls||(i.scriptUrls=[i.url+"js/tac.min.js"]),i.cssUrls||(i.cssUrls=[i.url+"css/tac.css"])),i.scriptUrls&&i.cssUrls?loadCaptchaScript(i,t,n,r,o):o("请按照文档配置tac")})}setTimeout(()=>{let e=document.scripts,t=null;for(let n=0;n1||e[n].src.indexOf("load.min.js")>1){t=e[n].src.substring(e[n].src.indexOf("/"),e[n].src.lastIndexOf("/"));break}},100),window.loadCaptchaScript=loadCaptchaScript,window.loadTAC=loadTAC,window.initTAC=loadTAC; diff --git a/easyflow-ui-admin/app/public/logo.svg b/easyflow-ui-admin/app/public/logo.svg new file mode 100644 index 0000000..14c65bc --- /dev/null +++ b/easyflow-ui-admin/app/public/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + EasyFlow + diff --git a/easyflow-ui-admin/app/public/logoDark.svg b/easyflow-ui-admin/app/public/logoDark.svg new file mode 100644 index 0000000..2c0db80 --- /dev/null +++ b/easyflow-ui-admin/app/public/logoDark.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + EasyFlow + diff --git a/easyflow-ui-admin/app/public/logoMini.svg b/easyflow-ui-admin/app/public/logoMini.svg new file mode 100644 index 0000000..fe66fb4 --- /dev/null +++ b/easyflow-ui-admin/app/public/logoMini.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/easyflow-ui-admin/app/public/slogan.svg b/easyflow-ui-admin/app/public/slogan.svg new file mode 100644 index 0000000..4772c9e --- /dev/null +++ b/easyflow-ui-admin/app/public/slogan.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knowledge + + + + + Workflow + + + + + + + + EasyFlow + Unified AI workspace for agents, workflows and knowledge + diff --git a/easyflow-ui-admin/app/public/tac-btn.png b/easyflow-ui-admin/app/public/tac-btn.png new file mode 100644 index 0000000..d5611f4 Binary files /dev/null and b/easyflow-ui-admin/app/public/tac-btn.png differ diff --git a/easyflow-ui-admin/app/public/tac/css/tac.css b/easyflow-ui-admin/app/public/tac/css/tac.css new file mode 100644 index 0000000..9ea6011 --- /dev/null +++ b/easyflow-ui-admin/app/public/tac/css/tac.css @@ -0,0 +1,7 @@ +#tianai-captcha-parent{box-shadow:0 0 11px 0 #999;width:318px;height:318px;overflow:hidden;position:relative;z-index:997;box-sizing:border-box;border-radius:5px;padding:8px}#tianai-captcha-parent #tianai-captcha-box{height:260px;width:100%;position:relative;overflow:hidden}#tianai-captcha-parent #tianai-captcha-box .loading{width:120px;height:20px;-webkit-mask:linear-gradient(90deg, #000 70%, rgba(0, 0, 0, 0) 0) 0/20%;background:linear-gradient(#f7b645 0 0) 0/0% no-repeat rgba(221,221,221,.4196078431);animation:cartoon 1s infinite steps(6);margin:120px auto}@keyframes cartoon{100%{background-size:120%}}#tianai-captcha-parent #tianai-captcha-box #tianai-captcha{transform-style:preserve-3d;will-change:transform;transition-duration:.45s;transform:translateX(-300px)}#tianai-captcha-parent #tianai-captcha-bg-img{background-color:#fff;background-position:top;background-size:cover;z-index:-1;width:100%;height:100%;top:0;left:0;position:absolute;border-radius:6px}#tianai-captcha-parent .slider-bottom{height:19px;width:100%}#tianai-captcha-parent .slider-bottom .close-btn{width:20px;height:20px;background-image:url(../images/icon.png);background-repeat:no-repeat;background-position:0 -14px;float:right;margin-right:2px;cursor:pointer}#tianai-captcha-parent .slider-bottom .refresh-btn{width:20px;height:20px;background-image:url(../images/icon.png);background-position:0 -167px;background-repeat:no-repeat;float:right;margin-right:10px;cursor:pointer}#tianai-captcha-parent .slider-bottom .logo{height:30px;float:left}#tianai-captcha-parent .slider-move-shadow{animation:myanimation 2s infinite;height:100%;width:5px;background-color:#fff;position:absolute;top:0;left:0;filter:opacity(0.5);box-shadow:1px 1px 1px #fff;border-radius:50%}#tianai-captcha-parent #tianai-captcha-slider-move-track-mask{border-width:1px;border-style:solid;border-color:#00f4ab;width:0;height:32px;background-color:#a9ffe5;opacity:.5;position:absolute;top:-1px;left:-1px;border-radius:5px} +#tianai-captcha{text-align:left;box-sizing:content-box;width:300px;height:260px;z-index:999}#tianai-captcha .slider-bottom .logo{height:30px}#tianai-captcha .slider-bottom{height:19px;width:100%}#tianai-captcha .content .tianai-captcha-tips{height:25px;width:100%;position:absolute;bottom:-25px;left:0;z-index:999;font-size:15px;line-height:25px;color:#fff;text-align:center;transition:bottom .3s ease-in-out}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-error{background-color:#ff5d39}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-success{background-color:#39c522}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-on{bottom:0}#tianai-captcha .content #tianai-captcha-loading{z-index:9999;background-color:#f5f5f5;text-align:center;height:100%;overflow:hidden;position:relative;display:flex;justify-content:center;align-items:center}#tianai-captcha .content #tianai-captcha-loading img{display:block;width:45px;height:45px}#tianai-captcha #tianai-captcha-slider-bg-canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div .tianai-captcha-slider-bg-div-slice{position:absolute}@keyframes myanimation{from{left:0}to{left:289px}} +#tianai-captcha.tianai-captcha-slider{z-index:999;position:absolute;left:0;top:0;user-select:none}#tianai-captcha.tianai-captcha-slider .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-slider .bg-img-div{width:100%;height:100%;position:absolute;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .bg-img-div img{height:100%;width:100%;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-img-div{height:100%;position:absolute;left:0;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .slider-img-div #tianai-captcha-slider-move-img{height:100%}#tianai-captcha.tianai-captcha-slider .slider-move{height:34px;width:100%;margin:11px 0;position:relative}#tianai-captcha.tianai-captcha-slider .slider-move-track{position:relative;height:32px;line-height:32px;text-align:center;background:#f5f5f5;color:#999;transition:0s;font-size:14px;box-sizing:content-box;border:1px solid #f5f5f5;border-radius:4px}#tianai-captcha.tianai-captcha-slider .refresh-btn,#tianai-captcha.tianai-captcha-slider .close-btn{display:inline-block}#tianai-captcha.tianai-captcha-slider .slider-move{line-height:38px;font-size:14px;text-align:center;white-space:nowrap;color:#88949d;-moz-user-select:none;-webkit-user-select:none;user-select:none;filter:opacity(0.8)}#tianai-captcha.tianai-captcha-slider .slider-move .slider-move-btn{transform:translate(0px, 0px);position:absolute;top:0;left:0;width:55px;height:100%;background-repeat:no-repeat;background-size:cover;background-position: center;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-tip{margin-bottom:5px;font-weight:bold;font-size:15px;line-height:normal;color:#000}#tianai-captcha.tianai-captcha-slider .slider-move-btn:hover{cursor:move} +#tianai-captcha.tianai-captcha-rotate .rotate-img-div{height:100%;text-align:center}#tianai-captcha.tianai-captcha-rotate .rotate-img-div img{height:100%;transform:rotate(0deg);display:inline-block} +#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-img-div{background-size:100% 180px;position:absolute;transform:translate(0px, 0px);z-index:1;width:100%}#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-bg-img{width:100%;height:100%;position:absolute;transform:translate(0px, 0px);background-size:100% 180px} +#tianai-captcha.tianai-captcha-disable{z-index:999;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-disable .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div{background-image:url(../images/dun.jpeg);width:100%;height:100%;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div #content-span{color:#fff;overflow:hidden;margin-top:132px;display:block;text-align:center} +#tianai-captcha.tianai-captcha-word-click{box-sizing:border-box}#tianai-captcha.tianai-captcha-word-click .click-tip{position:relative;height:40px;width:100%}#tianai-captcha.tianai-captcha-word-click .click-tip .tip-img{height:35px;position:absolute;right:15px}#tianai-captcha.tianai-captcha-word-click .click-tip #tianai-captcha-click-track-font{font-size:18px;display:inline-block;height:40px;line-height:40px;position:absolute}#tianai-captcha.tianai-captcha-word-click .slider-bottom{position:relative;top:6px}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask{width:100%;height:100%;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask .click-span{position:absolute;left:0;top:0;border-radius:50px;background-color:#409eff;width:20px;height:20px;text-align:center;line-height:20px;color:#fff;border:2px solid #fff;box-sizing:content-box}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn{width:100%;height:35px;border-radius:4px;background-image:linear-gradient(173deg, hsl(38.09, 91%, 57.89%) 0%, hsl(38.09, 89.38%, 71.74%) 100%);font-size:15px;text-align:center;box-sizing:border-box;line-height:35px;color:#fff;margin-top:3px}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn:hover{cursor:pointer} diff --git a/easyflow-ui-admin/app/public/tac/images/dun.jpeg b/easyflow-ui-admin/app/public/tac/images/dun.jpeg new file mode 100644 index 0000000..347a371 Binary files /dev/null and b/easyflow-ui-admin/app/public/tac/images/dun.jpeg differ diff --git a/easyflow-ui-admin/app/public/tac/images/icon.png b/easyflow-ui-admin/app/public/tac/images/icon.png new file mode 100644 index 0000000..586a123 Binary files /dev/null and b/easyflow-ui-admin/app/public/tac/images/icon.png differ diff --git a/easyflow-ui-admin/app/public/tac/js/tac.min.js b/easyflow-ui-admin/app/public/tac/js/tac.min.js new file mode 100644 index 0000000..6a4e2f4 --- /dev/null +++ b/easyflow-ui-admin/app/public/tac/js/tac.min.js @@ -0,0 +1 @@ +(()=>{"use strict";var t,e,a={783:(t,e,a)=>{var i=a(618),n=Object.create(null),r="undefined"==typeof document,s=Array.prototype.forEach;function c(){}function o(t,e){if(!e){if(!t.href)return;e=t.href.split("?")[0]}if(l(e)&&!1!==t.isLoaded&&e&&e.indexOf(".css")>-1){t.visited=!0;var a=t.cloneNode();a.isLoaded=!1,a.addEventListener("load",(function(){a.isLoaded||(a.isLoaded=!0,t.parentNode.removeChild(t))})),a.addEventListener("error",(function(){a.isLoaded||(a.isLoaded=!0,t.parentNode.removeChild(t))})),a.href="".concat(e,"?").concat(Date.now()),t.nextSibling?t.parentNode.insertBefore(a,t.nextSibling):t.parentNode.appendChild(a)}}function d(t){if(!t)return!1;var e=document.querySelectorAll("link"),a=!1;return s.call(e,(function(e){if(e.href){var n=function(t,e){var a;return t=i(t),e.some((function(i){t.indexOf(e)>-1&&(a=i)})),a}(e.href,t);l(n)&&!0!==e.visited&&n&&(o(e,n),a=!0)}})),a}function h(){var t=document.querySelectorAll("link");s.call(t,(function(t){!0!==t.visited&&o(t)}))}function l(t){return!!/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(t)}t.exports=function(t,e){if(r)return c;var a,s,o,l=function(t){var e=n[t];if(!e){if(document.currentScript)e=document.currentScript.src;else{var a=document.getElementsByTagName("script"),r=a[a.length-1];r&&(e=r.src)}n[t]=e}return function(t){if(!e)return null;var a=e.split(/([^\\/]+)\.js$/),n=a&&a[1];return n&&t?t.split(",").map((function(t){var a=new RegExp("".concat(n,"\\.js$"),"g");return i(e.replace(a,"".concat(t.replace(/{fileName}/g,n),".css")))})):[e.replace(".js",".css")]}}(t);return a=function(){var t=d(l(e.filename));e.locals?h():t||h()},s=50,o=0,function(){var t=this,e=arguments;clearTimeout(o),o=setTimeout((function(){return a.apply(t,e)}),s)}}},618:t=>{t.exports=function(t){if(t=t.trim(),/^data:/i.test(t))return t;var e=-1!==t.indexOf("//")?t.split("//")[0]+"//":"",a=t.replace(new RegExp(e,"i"),"").split("/"),i=a[0].toLowerCase().replace(/\.$/,"");return a[0]="",e+i+a.reduce((function(t,e){switch(e){case"..":t.pop();break;case".":break;default:t.push(e)}return t}),[]).join("/")}},488:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},523:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},991:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},2:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},492:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},305:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},444:(t,e,a)=>{var i=a(783)(t.id,{locals:!1});t.hot.dispose(i),t.hot.accept(void 0,i)},687:(t,e,a)=>{a(488),a(523),a(444);function i(t){t.preventDefault&&t.preventDefault()}function n(t){f(t).each((t=>{t.addEventListener("touchmove",i,{passive:!1}),t.addEventListener("mousemove",i,{passive:!1})}))}function r(t){if(null!==t.pageX&&void 0!==t.pageX)return{x:Math.round(t.pageX),y:Math.round(t.pageY)};let e;return t.changedTouches?e=t.changedTouches:t.targetTouches?e=t.targetTouches:t.originalEvent&&t.originalEvent.targetTouches&&(e=t.originalEvent.targetTouches),null!==e[0].pageX&&void 0!==e[0].pageX?{x:Math.round(e[0].pageX),y:Math.round(e[0].pageY)}:{x:Math.round(e[0].clientX),y:Math.round(e[0].clientY)}}function s(t,e){const a=r(e);let i=a.x,n=a.y;t.currentCaptchaData.startX=i,t.currentCaptchaData.startY=n;const s=t.currentCaptchaData.trackList;t.currentCaptchaData.startTime=new Date;const o=t.currentCaptchaData.startTime;s.push({x:a.x,y:a.y,type:"down",t:(new Date).getTime()-o.getTime()}),t.__m__=c.bind(null,t),t.__u__=d.bind(null,t),window.addEventListener("mousemove",t.__m__),window.addEventListener("mouseup",t.__u__),window.addEventListener("touchmove",t.__m__,!1),window.addEventListener("touchend",t.__u__,!1),t&&t.doDown&&t.doDown(e,t)}function c(t,e){e.touches&&e.touches.length>0&&(e=e.touches[0]);const a=r(e);let i=a.x,n=a.y;const s=t.currentCaptchaData.startX,c=t.currentCaptchaData.startY,o=t.currentCaptchaData.startTime,d=t.currentCaptchaData.end,h=(t.currentCaptchaData.bgImageWidth,t.currentCaptchaData.trackList);let l=i-s,p=n-c;const u={x:a.x,y:a.y,type:"move",t:(new Date).getTime()-o.getTime()};h.push(u),l<0?l=0:l>d&&(l=d),t.currentCaptchaData.moveX=l,t.currentCaptchaData.moveY=p,t.doMove&&t.doMove(e,t)}function o(t){t&&(t.__m__&&(window.removeEventListener("mousemove",t.__m__),window.removeEventListener("touchmove",t.__m__)),t.__u__&&(window.removeEventListener("mouseup",t.__u__),window.removeEventListener("touchend",t.__u__)))}function d(t,e){o(t);const a=r(e);t.currentCaptchaData.stopTime=new Date;const i=t.currentCaptchaData.startTime,n=t.currentCaptchaData.trackList,s={x:a.x,y:a.y,type:"up",t:(new Date).getTime()-i.getTime()};n.push(s),t.doUp&&t.doUp(e,t),t.endCallback(t.currentCaptchaData,t)}function h(t,e,a,i,n){const r={startTime:new Date,trackList:[],movePercent:0,clickCount:0,bgImageWidth:Math.round(t),bgImageHeight:Math.round(e),templateImageWidth:Math.round(a),templateImageHeight:Math.round(i),end:n};return r}function l(t,e){f(t).find("#tianai-captcha-tips").removeClass("tianai-captcha-tips-on"),e&&setTimeout(e,.35)}function p(t,e,a,i){const n=f(t).find("#tianai-captcha-tips");n.text(e),1===a?(n.removeClass("tianai-captcha-tips-error"),n.addClass("tianai-captcha-tips-success")):(n.removeClass("tianai-captcha-tips-success"),n.addClass("tianai-captcha-tips-error")),n.addClass("tianai-captcha-tips-on"),setTimeout(i,1e3)}class u{showTips(t,e,a){p(this.el,t,e,a)}closeTips(t,e){l(this.el,t)}}function f(t,e){return new m(t,e)}class m{constructor(t,e){if(e&&"object"==typeof e&&void 0!==e.nodeType)return this.dom=e,void(this.domStr=t);if(t instanceof m)this.dom=t.dom,this.domStr=t.domStr;else if("string"==typeof t)this.dom=document.querySelector(t),this.domStr=t;else{if("object"!=typeof document||void 0===document.nodeType)throw new Error("不支持的类型");this.dom=t,this.domStr=t.nodeName}}each(t){this.getTarget().querySelectorAll("*").forEach(t)}removeClass(t){let e=this.getTarget();if(e.classList)e.classList.remove(t);else{const a=e.className,i=new RegExp("(?:^|\\s)"+t+"(?!\\S)","g");e.className=a.replace(i,"")}return this}addClass(t){const e=this.getTarget();if(e.classList)e.classList.add(t);else{let a=e.className;-1===a.indexOf(t)&&(e.className=a+" "+t)}return this}find(t){const e=this.getTarget().querySelector(t);return e?new m(t,e):null}children(t){const e=this.getTarget().childNodes;for(let a=0;a\n
\n ${t.i18n.slider_title}\n
\n
\n
\n \n \n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n\n\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}showTips(t,e,a){p(this.el,t,e,a)}closeTips(t){l(this.el,t)}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-img-div").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-slider-move-img");a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage),a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}))}};a(305);const g=class extends u{constructor(t,e){super(),this.boxEl=t,this.styleConfig=e,this.type="ROTATE",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.rotate_title}\n
\n
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-move-img").css("transform","rotate("+t/(this.currentCaptchaData.end/360)+"deg)"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-slider-move-img");a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage),a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}))}};a(991);const b=class extends u{constructor(t,e){super(),this.boxEl=f(t),this.styleConfig=e,this.type="CONCAT",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append((this.styleConfig,'\n
\n
\n 拖动滑块完成拼图\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n ')),this.el=this.boxEl.find("#tianai-captcha"),this.loadStyle(),this.el.find("#tianai-captcha-slider-move-btn").mousedown(s.bind(null,this)),this.el.find("#tianai-captcha-slider-move-btn").touchstart(s.bind(null,this)),n(this.el),window.currentCaptcha=this,this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){o();const t=this.boxEl.children("#tianai-captcha");t&&t.remove()}doMove(){const t=this.currentCaptchaData.moveX;this.el.find("#tianai-captcha-slider-move-btn").css("transform","translate("+t+"px, 0px)"),this.el.find("#tianai-captcha-slider-concat-img-div").css("background-position-x",t+"px"),this.el.find("#tianai-captcha-slider-move-track-mask").css("width",t+"px")}loadStyle(){let t="",e="#00f4ab",a="#a9ffe5";const i=this.styleConfig;i&&(t=i.btnUrl,a=i.moveTrackMaskBgColor,e=i.moveTrackMaskBorderColor),this.el.find(".slider-move .slider-move-btn").css("background-image","url("+t+")"),this.el.find("#tianai-captcha-slider-move-track-mask").css("border-color",e),this.el.find("#tianai-captcha-slider-move-track-mask").css("background-color",a)}loadCaptchaForData(t,e){const a=t.el.find(".tianai-captcha-slider-concat-bg-img"),i=t.el.find("#tianai-captcha-slider-concat-img-div");a.css("background-image","url("+e.data.backgroundImage+")"),i.css("background-image","url("+e.data.backgroundImage+")"),i.css("background-position","0px 0px");var n=e.data.backgroundImageHeight,r=(n-e.data.data.randomY)/n*180;i.css("height",r+"px"),t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height(),242),t.currentCaptchaData.currentCaptchaId=e.data.id}};a(2);const C=class{constructor(t,e){this.boxEl=t,this.styleConfig=e,this.type="DISABLE",this.currentCaptchaData={}}init(t,e,a){return this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.disable_title}\n
\n
\n
\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n\x3c!-- --\x3e\n \n
\n
\n
\n `}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadCaptchaForData(this,t),this.endCallback=e,a&&a(this),this}destroy(){const t=this.boxEl.find("#tianai-captcha");t&&t.remove()}loadCaptchaForData(t,e){const a=e.msg||e.message||"接口异常";t.el.find("#content-span").text(a)}};a(492);const y=class extends u{constructor(t,e){super(),this.boxEl=t,this.styleConfig=e,this.type="IMAGE_CLICK",this.currentCaptchaData={}}init(t,e,a){this.destroy(),this.boxEl.append(function(t){return`\n
\n
\n ${t.i18n.image_click_title}\n \n
\n
\n
\n \n \n
\n
\n
\n
\n
确定
\n
\n`}(this.styleConfig)),this.el=this.boxEl.find("#tianai-captcha"),this.loadCaptchaForData(this,t),this.endCallback=e;const i=c.bind(null,this);return this.el.find("#bg-img-click-mask").click((t=>{if("click-span"===t.target.className)return;this.currentCaptchaData.clickCount++;const e=this.currentCaptchaData.trackList;1===this.currentCaptchaData.clickCount&&(this.currentCaptchaData.startTime=new Date,window.addEventListener("mousemove",i),this.currentCaptchaData.startX=t.offsetX,this.currentCaptchaData.startY=t.offsetY);const a=this.currentCaptchaData.startTime;e.push({x:Math.round(t.offsetX),y:Math.round(t.offsetY),type:"click",t:(new Date).getTime()-a.getTime()});const n=t.offsetX-10,r=t.offsetY-10;this.el.find("#bg-img-click-mask").append(""+this.currentCaptchaData.clickCount+"")})),this.el.find(".click-confirm-btn").click((()=>{this.currentCaptchaData.clickCount>0&&(this.currentCaptchaData.stopTime=new Date,window.removeEventListener("mousemove",i),this.endCallback(this.currentCaptchaData,this))})),a&&a(this),this}destroy(){const t=this.boxEl.children("#tianai-captcha");t&&t.remove(),o()}loadCaptchaForData(t,e){const a=t.el.find("#tianai-captcha-slider-bg-img"),i=t.el.find("#tianai-captcha-tip-img");a.on("load",(()=>{t.currentCaptchaData=h(a.width(),a.height(),i.width(),i.height()),t.currentCaptchaData.currentCaptchaId=e.data.id})),a.attr("src",e.data.backgroundImage),i.attr("src",e.data.templateImage)}};const w=class extends y{constructor(t,e){super(t,e),this.type="WORD_IMAGE_CLICK"}},k={btnUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAABkCAYAAABU19jRAAAJcUlEQVR4nO2d63MT1xmHf9rV6mr5fgNMuSW+ENsY8N0EE2BMhinJNB8y/dD2Qz/0v+gMf0w/JHTKNJAhICwbsA02TpNAHEMgQIwNBSEb8F2rvXTeY1kjYyA+TmVJmfeZ8YiRWa9299E57/mdI63Dtm3E+RjAKTDMaj4F8AU9uyzMCQBn+EQxb+EjAF+RMH8AcJrPFLMGvCSMzWeKWSN/I2GiAFx8xpi1oPBZYiTQWRhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGChaGkYKFYaRgYRgpWBhGCiefrtShGwZiup74+4qqwu12Z/W7lIVJEfN6FDfv3sPXfYOIRRfpm1UQKC7EkQ+PYFtRcdZKw8KkiLsPJ/CfgSFcH7yOxWhU7MSluYQoR44fxdaCoqyUhoVJEfZ8FN99c1N0Sx6PR+zEMAz0XAgBNtB14hi25OXDkWXHxUVvinA4ln6ScTqdsGwbvRd7EPwyiEcvXyDbvpyHhUkRaq4fe/c3wEWSWFZiJySNYZroCYYQPHsBY1OTWSWNevLkyb/TYwa8lt8UAb8ftluDW9UwPj4hDs0Rb3JUVRXd09j9nwELKKgoR4HXlw2Hb3INkyK8mob9NdUwLROq4sCVKwMrdqRpGkzTFN0TaWR2HcKu0rKMr2lYmBTi1jS01dUt7UBx4PKlfvHP5JaGuqseIY0DjmOHsKukNKOPiYVJMU5VRXt9PSwboO+fvHJ5QEiiKEvlIz3S86HuHiiqAhw9iJ0lpRnb0rAwG4CqKHh/Tz0UhwOWaWGg/5oofEkmJLU4wfPdQia765CQJhNHJCzMBkEtSVtdLRw2YNo2hgaGEDMMMWpahrwJBUMUCkM9djgjE2EWZgOhFqW5rlbMKdm2heHBYUT1mCiAEW9pKKfpPh8Sj5mYCLMwG4zLqWJfTZWQgL5S++uhYURjBrR4S0MtUSYnwixMGvBoGvZUV4quh0S4Pjgsaho1XtOIcM8wxJCb+qmu33dljDS/CWEeTb/E/Pw89EUdebkBVBQWrnnbWVjQoMAtsT9asGDQhf8VUbnX5UJ9VaVoZahVuXZ1cMXoiaSJxWIiEab/dPj4UXFczjRrk/VJ70/hp/jhuxF89o9TGP1+FH6fD9OxGHw5Pnicb34/PJ2dweitu7hwLojvb47A9rhQmJeXGLm8iQeP/4uRH27h88/+iZhhYs40UFZQsK7XrqkqigvyYbk18VrHH74+EX74YAzRqI66mupE15UmzKwW5kEkgtFvRxA8ex7hJ2HMzczgzu0f8fjxExRt2YzcgB9udfUJjuo6Tv/7HE6f+pe4GHd//AkwLRhuDeXFRW+U5v7EI4yMjKI3GMLt0Tt4cO8BAoEcWJoTZYXrl6asqBC6U0GOy42HY+MrZi1JmoWFRZQW5sNyuVBeUpxOabJ7aiASjiB4/iKmnj+H5loaacwvLOL2jRF4AjnY8dc/I/DKbTdoSHvr8SO8DD/DzPSMWHrg1JwYvHZdpK2NVZWU26/aF3VDTyLP0N/bh4mJR3C7XZiZnRVdht/nx7u7tsOzzg5qORFWHAocigO9vX2Jronwej24cXMEbq8XrfW169rH/4usnq02o1FEo9FEE47luN22sTAzC0OPrd7ItnHn9h0MDg3D6/WKbZdHJqYRg26ar92XDgvD39zA2Ng4VKdTbEf7mpmeRX/fAPRfeRch+luNNTXICeSu+h3ti7okUzdgp3luO6uFUTUN9lLmnniOCkdKVnML8uB0r76rD72Di4qL4NI0IUnydpZlw/WmGsY00bRvDzZvKhfFKLAU9VOG8v7BdijW+i8kLX649yyMz0+fwVQksur3NILyejzw5efCoaT3kmW1MN68AMq2bBIXXtd18WMZBt6r242DBzvgda3uWhQ4xNzOkeNdohZYXFjA4vwCfD4/Sio2i9bjdeSoGirKylFYXirykehiFHpUR2FJCbZu+x1yXlMrrQWSZWwygv6Ll3DxXBCX+66u6I7o2DRFRWtbM1o62xNdb7rI7lGSqqBs+zZMTj4XLYY/x49t7+zABx8eReWO7ciLL41ctZmqoqRiE/x+P6amp5FbkI9jx7tw+GgncqmbesPuPAEfduzcgenZOTg0FaWby/GXP/0RdZXvrOvlkyzjzyfR81UIoQs9IpRJniqglszt0tDc1oS9bc2o37lTLMhKI2bW35HtRXQRs3MLmH/xUrzzVb8HJQUFyHX/crJCQ+JwOALFqaKspGjNRWtkbg5zc7PQXC5szl/f6Ig6MFqiSavuqHCmumuFLIYBt+ZEY0sTGtua0VBTJQK/NKPzLfzSQEKWL4NiiG5a1gpZzPhMdnNrE/a3N2NPVaUI+jIAnacGNhiShdbx9pzrFgunSA4tqeUQRbuqoLW9BQ0tjSINzhBZBCzMBvPzVAS950KiG6KWJVkWGnXRELrjQBtqG/eioTqzZAELs3FQy3Iv/BR9wUtiUtGOr+tNhoptGt1V7atD4+4aEehlGizMBnH/WRj9wcuiG7LjI7Vllm8d3nnoAKoaakXq+0tzWumChUkxdlyWge4rYt0uzRMpSck01SzUDR3s7MC7e2pFRqSmOZx7GyxMCrESLcsldAd7oCgrEx6xrldRRM1SvbceHfV1K0K7TISFSREx28L41KRIcGmdruOVz82KBFd1oqWjBe/tb0ArLd3McFnAwqSOiclJ9JwP4fLFXtEtJXdDywluU2uTGDpTgZupNcur8GerU8R0eBJDV6+LRVbJLYdIcF2aSHD3tzaL9b20zjdbYGFShB0z4HY6V9QtFNLRXFATxf2U4FZXZkLcLwULkyJoaUXMNMV6HbyS4O6jicQMS3DXCguTInJKC9HU0YoPOg8k1uy0t7eivnmfSHB9WSgLwZOPKcKwLcT0GL69cxe3b46KoK6+ZS92V2zNyAR3jfBsdaox6LPSpiVyf/rEo/rq11JlFzxbnWoomEMW5CtrhWsYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgoWhpGChWGkYGEYKVgYRgr6qGx6b4/BZBXUwnzCl4xZI5844g3MCQBn+Kwxb+EjAGcdST3SxwBO8RljXsOnAL4AgP8BXnVIgIvemwsAAAAASUVORK5CYII=",moveTrackMaskBgColor:"#89d2ff",moveTrackMaskBorderColor:"#0298f8",i18n:{tips_success:"验证成功,耗时%s秒",tips_error:"验证失败,请重新尝试!",slider_title:"拖动滑块完成拼图",concat_title:"拖动滑块完成拼图",image_click_title:"请依次点击:",rotate_title:"拖动滑块完成拼图",slider_title_size:"15px",concat_title_size:"15px",image_click_title_size:"20px",rotate_title_size:"15px"}};class E{constructor(t){if(!t.bindEl)throw new Error("[TAC] 必须配置 [bindEl]用于将验证码绑定到该元素上");if(!t.requestCaptchaDataUrl)throw new Error("[TAC] 必须配置 [requestCaptchaDataUrl]请求验证码接口");if(!t.validCaptchaUrl)throw new Error("[TAC] 必须配置 [validCaptchaUrl]验证验证码接口");this.bindEl=t.bindEl,this.domBindEl=f(t.bindEl),this.requestCaptchaDataUrl=t.requestCaptchaDataUrl,this.validCaptchaUrl=t.validCaptchaUrl,t.validSuccess&&(this.validSuccess=t.validSuccess),t.validFail&&(this.validFail=t.validFail),t.requestHeaders?this.requestHeaders=t.requestHeaders:this.requestHeaders={},t.btnCloseFun&&(this.btnCloseFun=t.btnCloseFun),t.btnRefreshFun&&(this.btnRefreshFun=t.btnRefreshFun),this.requestChain=[],this.timeToTimestamp=t.timeToTimestamp||!0,this.insertRequestChain(0,{preRequest(t,e,a,i){if(this.timeToTimestamp&&e.data)for(let t in e.data)e.data[t]instanceof Date&&(e.data[t]=e.data[t].getTime());return!0}})}addRequestChain(t){this.requestChain.push(t)}insertRequestChain(t,e){this.requestChain.splice(t,0,e)}removeRequestChain(t){this.requestChain.splice(t,1)}requestCaptchaData(){const t={};t.headers=this.requestHeaders||{},t.data={},t.headers["Content-Type"]="application/json;charset=UTF-8",t.method="POST",t.url=this.requestCaptchaDataUrl,this._preRequest("requestCaptchaData",t);return this.doSendRequest(t).then((e=>(this._postRequest("requestCaptchaData",t,e),e)))}doSendRequest(t){if(t.headers)for(const e in t.headers)if(t.headers[e].indexOf("application/json")>-1){"string"!=typeof t.data&&(t.data=JSON.stringify(t.data));break}return(e=t,new Promise((function(t,a){var i=new XMLHttpRequest;if(i.open(e.method||"GET",e.url),e.headers)for(const t in e.headers)e.headers.hasOwnProperty(t)&&i.setRequestHeader(t,e.headers[t]);i.onreadystatechange=function(){if(i.readyState===XMLHttpRequest.DONE)if(i.status>=200&&i.status<=500){const e=i.getResponseHeader("Content-Type");e&&-1!==e.indexOf("application/json")?t(JSON.parse(i.responseText)):t(i.responseText)}else a(new Error("Request failed with status: "+i.status))},i.onerror=function(){a(new Error("Network Error"))},i.send(e.data)}))).then((t=>{try{return JSON.parse(t)}catch(e){return t}}));var e}_preRequest(t,e,a,i){for(let n=0;n(this._postRequest("validCaptcha",r,t,a,i),t))).then((t=>{if(200==t.code){const n=(e.stopTime-e.startTime)/1e3;a.showTips(`验证成功,耗时${n}秒`,1,(()=>this.validSuccess(t,a,i)))}else{let e="验证失败,请重新尝试!";t.code&&4001!=t.code&&(e="验证码被黑洞吸走了!"),a.showTips(e,0,(()=>this.validFail(t,a,i)))}})).catch((t=>{let e=a.styleConfig.i18n.tips_error;t.code&&200!=t.code&&(4001!=res.code&&(e=a.styleConfig.i18n.tips_4001),a.showTips(e,0,(()=>this.validFail(res,a,i))))}))}validSuccess(t,e,a){window.currentCaptchaRes=t,a.destroyWindow()}validFail(t,e,a){a.reloadCaptcha()}}window.TAC=class{constructor(t,e){this.config=function(t){return t instanceof E?t:new E(t)}(t),this.config.btnRefreshFun&&(this.btnRefreshFun=this.config.btnRefreshFun),this.config.btnCloseFun&&(this.btnCloseFun=this.config.btnCloseFun),this.style=function(t){let e={...k,...t};return e.i18n={...k.i18n,...t?.i18n},e}(e)}init(){return this.destroyWindow(),this.config.domBindEl.append('\n
\n
\n
\n
\n
\n \x3c!-- 底部 --\x3e\n
\n \n
\n
\n
\n
\n '),this.domTemplate=this.config.domBindEl.find("#tianai-captcha-parent"),n(this.domTemplate),this.loadStyle(),this.config.domBindEl.find("#tianai-captcha-slider-refresh-btn").click((t=>{this.btnRefreshFun(t,this)})),this.config.domBindEl.find("#tianai-captcha-slider-close-btn").click((t=>{this.btnCloseFun(t,this)})),this.reloadCaptcha(),this}btnRefreshFun(t,e){e.reloadCaptcha()}btnCloseFun(t,e){e.destroyWindow()}reloadCaptcha(){this.showLoading(),this.destroyCaptcha((()=>{this.createCaptcha()}))}showLoading(){this.config.domBindEl.find("#tianai-captcha-loading").css("display","block")}closeLoading(){this.config.domBindEl.find("#tianai-captcha-loading").css("display","none")}loadStyle(){const t=this.style.bgUrl,e=this.style.logoUrl;t&&this.config.domBindEl.find("#tianai-captcha-bg-img").css("background-image","url("+t+")"),e&&""!==e?this.config.domBindEl.find("#tianai-captcha-logo").attr("src",e):null===e&&this.config.domBindEl.find("#tianai-captcha-logo").css("display","none")}destroyWindow(){this.C&&(this.C.destroy(),this.C=void 0),this.domTemplate&&this.domTemplate.remove()}openCaptcha(){setTimeout((()=>{this.C.el.css("transform","translateX(0)")}),10)}createCaptcha(){this.config.requestCaptchaData().then((t=>{if(this.closeLoading(),!t.code)throw new Error("[TAC] 后台验证码接口数据错误!!!");let e=200===t.code?t.data?.type:"DISABLED";const a=function(t,e){const a=e.config.domBindEl.find("#tianai-captcha-box"),i=e.style;switch(t){case"SLIDER":return new v(a,i);case"ROTATE":return new g(a,i);case"CONCAT":return new b(a,i);case"WORD_IMAGE_CLICK":return new w(a,i);case"DISABLED":return new C(a,i);default:return null}}(e,this);if(null==a)throw new Error("[TAC] 未知的验证码类型["+e+"]");a.init(t,((t,e)=>{const a=e.currentCaptchaData,i={bgImageWidth:a.bgImageWidth,bgImageHeight:a.bgImageHeight,templateImageWidth:a.templateImageWidth,templateImageHeight:a.templateImageHeight,startTime:a.startTime.getTime(),stopTime:a.stopTime.getTime(),trackList:a.trackList};"ROTATE_DEGREE"!==e.type&&"ROTATE"!==e.type||(i.bgImageWidth=e.currentCaptchaData.end),a.data&&(i.data=a.data);const n=e.currentCaptchaData.currentCaptchaId;e.currentCaptchaData=void 0,this.config.validCaptcha(n,i,e,this)})),this.C=a,this.openCaptcha()}))}destroyCaptcha(t){this.C?(this.C.el.css("transform","translateX(300px)"),setTimeout((()=>{this.C.destroy(),t&&t()}),500)):t()}},window.CaptchaConfig=E}},i={};function n(t){var e=i[t];if(void 0!==e){if(void 0!==e.error)throw e.error;return e.exports}var r=i[t]={id:t,exports:{}};try{var s={id:t,module:r,factory:a[t],require:n};n.i.forEach((function(t){t(s)})),r=s.module,s.factory.call(r.exports,r,r.exports,s.require)}catch(t){throw r.error=t,t}return r.exports}n.m=a,n.c=i,n.i=[],n.hu=t=>t+"."+n.h()+".hot-update.js",n.miniCssF=t=>{},n.hmrF=()=>"main."+n.h()+".hot-update.json",n.h=()=>"7ca661d8917e051f770d",n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t={},e="webpack-demo:",n.l=(a,i,r,s)=>{if(t[a])t[a].push(i);else{var c,o;if(void 0!==r)for(var d=document.getElementsByTagName("script"),h=0;h{c.onerror=c.onload=null,clearTimeout(u);var n=t[a];if(delete t[a],c.parentNode&&c.parentNode.removeChild(c),n&&n.forEach((t=>t(i))),e)return e(i)},u=setTimeout(p.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=p.bind(null,c.onerror),c.onload=p.bind(null,c.onload),o&&document.head.appendChild(c)}},(()=>{var t,e,a,i={},r=n.c,s=[],c=[],o="idle",d=0,h=[];function l(t){o=t;for(var e=[],a=0;a0)return l("abort").then((function(){throw n[0]}));var r=l("dispose");i.forEach((function(t){t.dispose&&t.dispose()}));var s,c=l("apply"),o=function(t){s||(s=t)},d=[];return i.forEach((function(t){if(t.apply){var e=t.apply(o);if(e)for(var a=0;a=0&&b._disposeHandlers.splice(e,1)},invalidate:function(){switch(this._selfInvalidated=!0,o){case"idle":e=[],Object.keys(n.hmrI).forEach((function(t){n.hmrI[t](m,e)})),l("ready");break;case"ready":Object.keys(n.hmrI).forEach((function(t){n.hmrI[t](m,e)}));break;case"prepare":case"check":case"dispose":case"apply":(a=a||[]).push(m)}},check:u,apply:f,status:function(t){if(!t)return o;c.push(t)},addStatusHandler:function(t){c.push(t)},removeStatusHandler:function(t){var e=c.indexOf(t);e>=0&&c.splice(e,1)},data:i[m]},t=void 0,b),C.parents=s,C.children=[],s=[],h.require=y})),n.hmrC={},n.hmrI={}})(),(()=>{var t;n.g.importScripts&&(t=n.g.location+"");var e=n.g.document;if(!t&&e&&(e.currentScript&&(t=e.currentScript.src),!t)){var a=e.getElementsByTagName("script");if(a.length)for(var i=a.length-1;i>-1&&!t;)t=a[i--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=t+"../../"})(),(()=>{if("undefined"!=typeof document){var t=(t,e,a,i,n)=>{var r=document.createElement("link");r.rel="stylesheet",r.type="text/css";return r.onerror=r.onload=a=>{if(r.onerror=r.onload=null,"load"===a.type)i();else{var s=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.href||e,o=new Error("Loading CSS chunk "+t+" failed.\n("+c+")");o.code="CSS_CHUNK_LOAD_FAILED",o.type=s,o.request=c,r.parentNode&&r.parentNode.removeChild(r),n(o)}},r.href=e,a?a.parentNode.insertBefore(r,a.nextSibling):document.head.appendChild(r),r},e=(t,e)=>{for(var a=document.getElementsByTagName("link"),i=0;i({dispose:()=>{for(var t=0;t{for(var t=0;t{h.push(r),s.forEach((r=>{var s=n.miniCssF(r),c=n.p+s,o=e(s,c);o&&d.push(new Promise(((e,n)=>{var s=t(r,c,o,(()=>{s.as="style",s.rel="preload",e()}),n);a.push(o),i.push(s)})))}))}}})(),(()=>{var t,e,a,i,r,s=n.hmrS_jsonp=n.hmrS_jsonp||{179:0},c={};function o(e,a){return t=a,new Promise(((t,a)=>{c[e]=t;var i=n.p+n.hu(e),r=new Error;n.l(i,(t=>{if(c[e]){c[e]=void 0;var i=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;r.message="Loading hot update chunk "+e+" failed.\n("+i+": "+n+")",r.name="ChunkLoadError",r.type=i,r.request=n,a(r)}}))}))}function d(t){function c(t){for(var e=[t],a={},i=e.map((function(t){return{chain:[t],id:t}}));i.length>0;){var r=i.pop(),s=r.id,c=r.chain,d=n.c[s];if(d&&(!d.hot._selfAccepted||d.hot._selfInvalidated)){if(d.hot._selfDeclined)return{type:"self-declined",chain:c,moduleId:s};if(d.hot._main)return{type:"unaccepted",chain:c,moduleId:s};for(var h=0;h ")),f.type){case"self-declined":t.onDeclined&&t.onDeclined(f),t.ignoreDeclined||(v=new Error("Aborted because of self decline: "+f.moduleId+C));break;case"declined":t.onDeclined&&t.onDeclined(f),t.ignoreDeclined||(v=new Error("Aborted because of declined dependency: "+f.moduleId+" in "+f.parentId+C));break;case"unaccepted":t.onUnaccepted&&t.onUnaccepted(f),t.ignoreUnaccepted||(v=new Error("Aborted because "+u+" is not accepted"+C));break;case"accepted":t.onAccepted&&t.onAccepted(f),g=!0;break;case"disposed":t.onDisposed&&t.onDisposed(f),b=!0;break;default:throw new Error("Unexception type "+f.type)}if(v)return{error:v};if(g)for(u in l[u]=m,o(h,f.outdatedModules),f.outdatedDependencies)n.o(f.outdatedDependencies,u)&&(d[u]||(d[u]=[]),o(d[u],f.outdatedDependencies[u]));b&&(o(h,[f.moduleId]),l[u]=p)}a=void 0;for(var y,w=[],k=0;k0;){var r=a.pop(),c=n.c[r];if(c){var o={},l=c.hot._disposeHandlers;for(k=0;k=0&&p.parents.splice(t,1))}}}for(var u in d)if(n.o(d,u)&&(c=n.c[u]))for(y=d[u],k=0;k=0&&c.children.splice(t,1)},apply:function(e){for(var a in l)n.o(l,a)&&(n.m[a]=l[a]);for(var i=0;i{for(var o in i)n.o(i,o)&&(a[o]=i[o],t&&t.push(o));s&&r.push(s),c[e]&&(c[e](),c[e]=void 0)},n.hmrI.jsonp=function(t,e){a||(a={},r=[],i=[],e.push(d)),n.o(a,t)||(a[t]=n.m[t])},n.hmrC.jsonp=function(t,c,h,l,p,u){p.push(d),e={},i=c,a=h.reduce((function(t,e){return t[e]=!1,t}),{}),r=[],t.forEach((function(t){n.o(s,t)&&void 0!==s[t]?(l.push(o(t,u)),e[t]=!0):e[t]=!1})),n.f&&(n.f.jsonpHmr=function(t,a){e&&n.o(e,t)&&!e[t]&&(a.push(o(t)),e[t]=!0)})},n.hmrM=()=>{if("undefined"==typeof fetch)throw new Error("No browser support: need fetch API");return fetch(n.p+n.hmrF()).then((t=>{if(404!==t.status){if(!t.ok)throw new Error("Failed to fetch update manifest "+t.statusText);return t.json()}}))}})();n(687)})(); diff --git a/easyflow-ui-admin/app/src/adapter/component/index.ts b/easyflow-ui-admin/app/src/adapter/component/index.ts new file mode 100644 index 0000000..d8d83aa --- /dev/null +++ b/easyflow-ui-admin/app/src/adapter/component/index.ts @@ -0,0 +1,331 @@ +/** + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 + * 可用于 easyflow-form、easyflow-modal、easyflow-drawer 等组件使用, + */ + +import type { Component } from 'vue'; + +import type { BaseFormComponentType } from '@easyflow/common-ui'; +import type { Recordable } from '@easyflow/types'; + +import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; + +import { ApiComponent, globalShareState, IconPicker } from '@easyflow/common-ui'; +import { $t } from '@easyflow/locales'; + +import { ElNotification } from 'element-plus'; + +const ElButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/button/index'), + import('element-plus/es/components/button/style/css'), + ]).then(([res]) => res.ElButton), +); +const ElCheckbox = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox/style/css'), + ]).then(([res]) => res.ElCheckbox), +); +const ElCheckboxButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox-button/style/css'), + ]).then(([res]) => res.ElCheckboxButton), +); +const ElCheckboxGroup = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/checkbox/index'), + import('element-plus/es/components/checkbox-group/style/css'), + ]).then(([res]) => res.ElCheckboxGroup), +); +const ElDatePicker = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/date-picker/index'), + import('element-plus/es/components/date-picker/style/css'), + ]).then(([res]) => res.ElDatePicker), +); +const ElDivider = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/divider/index'), + import('element-plus/es/components/divider/style/css'), + ]).then(([res]) => res.ElDivider), +); +const ElInput = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/input/index'), + import('element-plus/es/components/input/style/css'), + ]).then(([res]) => res.ElInput), +); +const ElInputNumber = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/input-number/index'), + import('element-plus/es/components/input-number/style/css'), + ]).then(([res]) => res.ElInputNumber), +); +const ElRadio = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio/style/css'), + ]).then(([res]) => res.ElRadio), +); +const ElRadioButton = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio-button/style/css'), + ]).then(([res]) => res.ElRadioButton), +); +const ElRadioGroup = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/radio/index'), + import('element-plus/es/components/radio-group/style/css'), + ]).then(([res]) => res.ElRadioGroup), +); +const ElSelectV2 = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/select-v2/index'), + import('element-plus/es/components/select-v2/style/css'), + ]).then(([res]) => res.ElSelectV2), +); +const ElSpace = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/space/index'), + import('element-plus/es/components/space/style/css'), + ]).then(([res]) => res.ElSpace), +); +const ElSwitch = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/switch/index'), + import('element-plus/es/components/switch/style/css'), + ]).then(([res]) => res.ElSwitch), +); +const ElTimePicker = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/time-picker/index'), + import('element-plus/es/components/time-picker/style/css'), + ]).then(([res]) => res.ElTimePicker), +); +const ElTreeSelect = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/tree-select/index'), + import('element-plus/es/components/tree-select/style/css'), + ]).then(([res]) => res.ElTreeSelect), +); +const ElUpload = defineAsyncComponent(() => + Promise.all([ + import('element-plus/es/components/upload/index'), + import('element-plus/es/components/upload/style/css'), + ]).then(([res]) => res.ElUpload), +); + +const withDefaultPlaceholder = ( + component: T, + type: 'input' | 'select', + componentProps: Recordable = {}, +) => { + return defineComponent({ + name: component.name, + inheritAttrs: false, + setup: (props: any, { attrs, expose, slots }) => { + const placeholder = + props?.placeholder || + attrs?.placeholder || + $t(`ui.placeholder.${type}`); + // 透传组件暴露的方法 + const innerRef = ref(); + expose( + new Proxy( + {}, + { + get: (_target, key) => innerRef.value?.[key], + has: (_target, key) => key in (innerRef.value || {}), + }, + ), + ); + return () => + h( + component, + { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, + slots, + ); + }, + }); +}; + +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 +export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' + | 'Checkbox' + | 'CheckboxGroup' + | 'DatePicker' + | 'Divider' + | 'IconPicker' + | 'Input' + | 'InputNumber' + | 'RadioGroup' + | 'Select' + | 'Space' + | 'Switch' + | 'TimePicker' + | 'TreeSelect' + | 'Upload' + | BaseFormComponentType; + +async function initComponentAdapter() { + const components: Partial> = { + // 如果你的组件体积比较大,可以使用异步加载 + // Button: () => + // import('xxx').then((res) => res.Button), + ApiSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiSelect', + }, + 'select', + { + component: ElSelectV2, + loadingSlot: 'loading', + visibleEvent: 'onVisibleChange', + }, + ), + ApiTreeSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiTreeSelect', + }, + 'select', + { + component: ElTreeSelect, + props: { label: 'label', children: 'children' }, + nodeKey: 'value', + loadingSlot: 'loading', + optionsPropName: 'data', + visibleEvent: 'onVisibleChange', + }, + ), + Checkbox: ElCheckbox, + CheckboxGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options, isButton } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(isButton ? ElCheckboxButton : ElCheckbox, option), + ); + } + } + return h( + ElCheckboxGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, + // 自定义默认按钮 + DefaultButton: (props, { attrs, slots }) => { + return h(ElButton, { ...props, attrs, type: 'info' }, slots); + }, + // 自定义主要按钮 + PrimaryButton: (props, { attrs, slots }) => { + return h(ElButton, { ...props, attrs, type: 'primary' }, slots); + }, + Divider: ElDivider, + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { + iconSlot: 'append', + modelValueProp: 'model-value', + inputComponent: ElInput, + }), + Input: withDefaultPlaceholder(ElInput, 'input'), + InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), + RadioGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(attrs.isButton ? ElRadioButton : ElRadio, option), + ); + } + } + return h( + ElRadioGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, + Select: (props, { attrs, slots }) => { + return h(ElSelectV2, { ...props, attrs }, slots); + }, + Space: ElSpace, + Switch: ElSwitch, + TimePicker: (props, { attrs, slots }) => { + const { name, id, isRange } = props; + const extraProps: Recordable = {}; + if (isRange) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElTimePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + DatePicker: (props, { attrs, slots }) => { + const { name, id, type } = props; + const extraProps: Recordable = {}; + if (type && type.includes('range')) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElDatePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), + Upload: ElUpload, + }; + + // 将组件注册到全局共享状态中 + globalShareState.setComponents(components); + + // 定义全局共享状态中的消息提示 + globalShareState.defineMessage({ + // 复制成功消息提示 + copyPreferencesSuccess: (title, content) => { + ElNotification({ + title, + message: content, + position: 'bottom-right', + duration: 0, + type: 'success', + }); + }, + }); +} + +export { initComponentAdapter }; diff --git a/easyflow-ui-admin/app/src/adapter/form.ts b/easyflow-ui-admin/app/src/adapter/form.ts new file mode 100644 index 0000000..f4068ce --- /dev/null +++ b/easyflow-ui-admin/app/src/adapter/form.ts @@ -0,0 +1,41 @@ +import type { + EasyFlowFormSchema as FormSchema, + EasyFlowFormProps, +} from '@easyflow/common-ui'; + +import type { ComponentType } from './component'; + +import { setupEasyFlowForm, useEasyFlowForm as useForm, z } from '@easyflow/common-ui'; +import { $t } from '@easyflow/locales'; + +async function initSetupEasyFlowForm() { + setupEasyFlowForm({ + config: { + modelPropNameMap: { + Upload: 'fileList', + CheckboxGroup: 'model-value', + }, + }, + defineRules: { + required: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return $t('ui.formRules.required', [ctx.label]); + } + return true; + }, + selectRequired: (value, _params, ctx) => { + if (value === undefined || value === null) { + return $t('ui.formRules.selectRequired', [ctx.label]); + } + return true; + }, + }, + }); +} + +const useEasyFlowForm = useForm; + +export { initSetupEasyFlowForm, useEasyFlowForm, z }; + +export type EasyFlowFormSchema = FormSchema; +export type { EasyFlowFormProps }; diff --git a/easyflow-ui-admin/app/src/adapter/vxe-table.ts b/easyflow-ui-admin/app/src/adapter/vxe-table.ts new file mode 100644 index 0000000..1b73c06 --- /dev/null +++ b/easyflow-ui-admin/app/src/adapter/vxe-table.ts @@ -0,0 +1,70 @@ +import type { VxeTableGridOptions } from '@easyflow/plugins/vxe-table'; + +import { h } from 'vue'; + +import { setupEasyFlowVxeTable, useEasyFlowVxeGrid } from '@easyflow/plugins/vxe-table'; + +import { ElButton, ElImage } from 'element-plus'; + +import { useEasyFlowForm } from './form'; + +setupEasyFlowVxeTable({ + configVxeTable: (vxeUI) => { + vxeUI.setConfig({ + grid: { + align: 'center', + border: false, + columnConfig: { + resizable: true, + }, + minHeight: 180, + formConfig: { + // 全局禁用vxe-table的表单配置,使用formOptions + enabled: false, + }, + proxyConfig: { + autoLoad: true, + response: { + result: 'items', + total: 'total', + list: 'items', + }, + showActiveMsg: true, + showResponseMsg: false, + }, + round: true, + showOverflow: true, + size: 'small', + } as VxeTableGridOptions, + }); + + // 表格配置项可以用 cellRender: { name: 'CellImage' }, + vxeUI.renderer.add('CellImage', { + renderTableDefault(_renderOpts, params) { + const { column, row } = params; + const src = row[column.field]; + return h(ElImage, { src, previewSrcList: [src] }); + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellLink' }, + vxeUI.renderer.add('CellLink', { + renderTableDefault(renderOpts) { + const { props } = renderOpts; + return h( + ElButton, + { size: 'small', link: true }, + { default: () => props?.text }, + ); + }, + }); + + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 + // vxeUI.formats.add + }, + useEasyFlowForm, +}); + +export { useEasyFlowVxeGrid }; + +export type * from '@easyflow/plugins/vxe-table'; diff --git a/easyflow-ui-admin/app/src/api/ai/bot.ts b/easyflow-ui-admin/app/src/api/ai/bot.ts new file mode 100644 index 0000000..4b674bf --- /dev/null +++ b/easyflow-ui-admin/app/src/api/ai/bot.ts @@ -0,0 +1,138 @@ +import type { + AiLlm, + BotInfo, + ChatMessage, + RequestResult, + Session, +} from '@easyflow/types'; + +import { api } from '#/api/request.js'; + +/** 获取bot详情 */ +export const getBotDetails = (id: string) => { + return api.get>('/api/v1/bot/getDetail', { + params: { id }, + }); +}; + +export interface GetSessionListParams { + botId: string; + tempUserId: string; +} +/** 获取bot对话列表 */ +export const getSessionList = (params: GetSessionListParams) => { + return api.get>( + '/api/v1/conversation/externalList', + { params }, + ); +}; + +export interface SaveBotParams { + icon: string; + title: string; + alias: string; + description: string; + categoryId: any; + status: number; +} +/** 创建Bot */ +export const saveBot = (params: SaveBotParams) => { + return api.post('/api/v1/bot/save', { ...params }); +}; + +export interface UpdateBotParams extends SaveBotParams { + id: string; +} +/** 修改Bot */ +export const updateBotApi = (params: UpdateBotParams) => { + return api.post('/api/v1/bot/update', { ...params }); +}; + +/** 删除Bot */ +export const removeBotFromId = (id: string) => { + return api.post('/api/v1/bot/remove', { id }); +}; + +export interface GetMessageListParams { + conversationId: string; + botId: string; + tempUserId: string; +} +/** 获取单个对话的信息列表 */ +export const getMessageList = (params: GetMessageListParams) => { + return api.get>( + '/api/v1/botMessage/messageList', + { + params, + }, + ); +}; + +/** 更新Bot的LLM配置 */ +export interface UpdateLlmOptionsParams { + id: string; + llmOptions: { + [key: string]: any; + }; +} +export interface UpdateBotOptionsParams { + id: string; + options: { + [key: string]: any; + }; +} + +export const updateLlmOptions = (params: UpdateLlmOptionsParams) => { + return api.post('/api/v1/bot/updateLlmOptions', { + ...params, + }); +}; + +export const updateBotOptions = (params: UpdateBotOptionsParams) => { + return api.post('/api/v1/bot/updateOptions', { + ...params, + }); +}; + +/** 更新Bot的LLM配置 */ +export interface GetAiLlmListParams { + [key: string]: any; +} +export const getAiLlmList = (params: GetAiLlmListParams) => { + return api.get>('/api/v1/model/list', { + params, + }); +}; + +/** 更新modelId */ +export interface UpdateLlmIdParams { + id: string; + modelId: string; +} +export const updateLlmId = (params: UpdateLlmIdParams) => { + return api.post('/api/v1/bot/updateLlmId', { + ...params, + }); +}; + +export const doPostBotPluginTools = (botId: string) => { + return api.post>('/api/v1/pluginItem/tool/list', { + id: botId, + }); +}; + +export const getPerQuestions = (presetQuestions: any[]) => { + if (!presetQuestions) { + return []; + } + return presetQuestions + .filter((item: any) => { + return ( + typeof item.description === 'string' && item.description.trim() !== '' + ); + }) + .map((item: any) => ({ + key: item.key, + description: item.description, + })); +}; diff --git a/easyflow-ui-admin/app/src/api/ai/index.ts b/easyflow-ui-admin/app/src/api/ai/index.ts new file mode 100644 index 0000000..9db119a --- /dev/null +++ b/easyflow-ui-admin/app/src/api/ai/index.ts @@ -0,0 +1,2 @@ +export * from './bot'; +export * from './llm'; diff --git a/easyflow-ui-admin/app/src/api/ai/knowledge.ts b/easyflow-ui-admin/app/src/api/ai/knowledge.ts new file mode 100644 index 0000000..e69de29 diff --git a/easyflow-ui-admin/app/src/api/ai/llm.ts b/easyflow-ui-admin/app/src/api/ai/llm.ts new file mode 100644 index 0000000..76cb2b3 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/ai/llm.ts @@ -0,0 +1,44 @@ +import { api } from '#/api/request.js'; + +// 获取LLM供应商 +export async function getLlmProviderList() { + return api.get('/api/v1/modelProvider/list'); +} + +// 保存LLM +export async function saveLlm(data: string) { + return api.post('/api/v1/model/save', data); +} + +// 删除LLM +export async function deleteLlm(data: any) { + return api.post(`/api/v1/model/remove`, data); +} + +// 修改LLM +export async function updateLlm(data: any) { + return api.post(`/api/v1/model/update`, data); +} + +// 一键添加LLM +export async function quickAddLlm(data: any) { + return api.post(`/api/v1/model/quickAdd`, data); +} + +export interface llmType { + id: string; + title: string; + modelProvider: { + icon: string; + providerName: string; + providerType: string; + }; + withUsed: boolean; + llmModel: string; + icon: string; + description: string; + modelType: string; + groupName: string; + added: boolean; + aiLlmProvider: any; +} diff --git a/easyflow-ui-admin/app/src/api/common/file.ts b/easyflow-ui-admin/app/src/api/common/file.ts new file mode 100644 index 0000000..525beb2 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/common/file.ts @@ -0,0 +1,27 @@ +/** + * 格式化文件大小(字节转 B/KB/MB/GB/TB) + * @param bytes - 文件大小(单位:字节 Byte) + * @param decimalPlaces - 保留小数位数(默认 2 位) + * @returns 格式化后的大小字符串(如:1.23 MB、456 B、7.8 GB) + */ +export function formatFileSize( + bytes: number, + decimalPlaces: number = 2, +): string { + // 处理特殊情况:bytes 为 0 或非数字 + if (Number.isNaN(bytes) || bytes < 0) return '0 B'; + if (bytes === 0) return '0 B'; + + // 单位数组(从 Byte 到 TB) + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + // 计算合适的单位索引(1 KB = 1024 B,每次除以 1024 切换单位) + const unitIndex = Math.floor(Math.log(bytes) / Math.log(1024)); + // 计算对应单位的大小(保留指定小数位) + const formattedSize = (bytes / 1024 ** unitIndex).toFixed(decimalPlaces); + + // 移除末尾多余的 .00(如 2.00 MB → 2 MB,1.50 KB → 1.5 KB) + const sizeWithoutTrailingZeros = Number.parseFloat(formattedSize).toString(); + + // 返回格式化结果(单位与大小拼接) + return `${sizeWithoutTrailingZeros} ${units[unitIndex]}`; +} diff --git a/easyflow-ui-admin/app/src/api/common/hasPermission.ts b/easyflow-ui-admin/app/src/api/common/hasPermission.ts new file mode 100644 index 0000000..92ed4e5 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/common/hasPermission.ts @@ -0,0 +1,9 @@ +import { useAccessStore } from '@easyflow/stores'; + +export function hasPermission(codes: string[]) { + const accessStore = useAccessStore(); + const userCodesSet = new Set(accessStore.accessCodes); + + const intersection = codes.filter((item) => userCodesSet.has(item)); + return intersection.length > 0; +} diff --git a/easyflow-ui-admin/app/src/api/core/auth.ts b/easyflow-ui-admin/app/src/api/core/auth.ts new file mode 100644 index 0000000..d0e8700 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/core/auth.ts @@ -0,0 +1,52 @@ +import { baseRequestClient, requestClient } from '#/api/request'; + +export namespace AuthApi { + /** 登录接口参数 */ + export interface LoginParams { + password?: string; + username?: string; + } + + /** 登录接口返回值 */ + export interface LoginResult { + accessToken: string; + token: string; + } + + export interface RefreshTokenResult { + data: string; + status: number; + } +} + +/** + * 登录 + */ +export async function loginApi(data: AuthApi.LoginParams) { + return requestClient.post('/api/v1/auth/login', data); +} + +/** + * 刷新accessToken + */ +export async function refreshTokenApi() { + return baseRequestClient.post('/auth/refresh', { + withCredentials: true, + }); +} + +/** + * 退出登录 + */ +export async function logoutApi() { + return requestClient.post('/api/v1/auth/logout', { + withCredentials: true, + }); +} + +/** + * 获取用户权限码 + */ +export async function getAccessCodesApi() { + return requestClient.get('/api/v1/auth/getPermissions'); +} diff --git a/easyflow-ui-admin/app/src/api/core/index.ts b/easyflow-ui-admin/app/src/api/core/index.ts new file mode 100644 index 0000000..28a5aef --- /dev/null +++ b/easyflow-ui-admin/app/src/api/core/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; +export * from './menu'; +export * from './user'; diff --git a/easyflow-ui-admin/app/src/api/core/menu.ts b/easyflow-ui-admin/app/src/api/core/menu.ts new file mode 100644 index 0000000..5f8547e --- /dev/null +++ b/easyflow-ui-admin/app/src/api/core/menu.ts @@ -0,0 +1,12 @@ +import type { RouteRecordStringComponent } from '@easyflow/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户所有菜单 + */ +export async function getAllMenusApi() { + return requestClient.get( + '/api/v1/sysMenu/treeV2', + ); +} diff --git a/easyflow-ui-admin/app/src/api/core/user.ts b/easyflow-ui-admin/app/src/api/core/user.ts new file mode 100644 index 0000000..1181022 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/core/user.ts @@ -0,0 +1,10 @@ +import type { UserInfo } from '@easyflow/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户信息 + */ +export async function getUserInfoApi() { + return requestClient.get('/api/v1/sysAccount/myProfile'); +} diff --git a/easyflow-ui-admin/app/src/api/index.ts b/easyflow-ui-admin/app/src/api/index.ts new file mode 100644 index 0000000..3b785a2 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './ai'; +export * from './core'; diff --git a/easyflow-ui-admin/app/src/api/request.ts b/easyflow-ui-admin/app/src/api/request.ts new file mode 100644 index 0000000..12a67c9 --- /dev/null +++ b/easyflow-ui-admin/app/src/api/request.ts @@ -0,0 +1,221 @@ +import type { ServerSentEventMessage } from 'fetch-event-stream'; + +/** + * 该文件可自行根据业务逻辑进行调整 + */ +import type { RequestClientOptions } from '@easyflow/request'; + +import { useAppConfig } from '@easyflow/hooks'; +import { preferences } from '@easyflow/preferences'; +import { + authenticateResponseInterceptor, + defaultResponseInterceptor, + errorMessageResponseInterceptor, + RequestClient, +} from '@easyflow/request'; +import { useAccessStore } from '@easyflow/stores'; + +import { ElMessage } from 'element-plus'; +import { events } from 'fetch-event-stream'; + +import { useAuthStore } from '#/store'; + +import { refreshTokenApi } from './core'; + +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); + +function createRequestClient(baseURL: string, options?: RequestClientOptions) { + const client = new RequestClient({ + ...options, + baseURL, + }); + + /** + * 重新认证逻辑 + */ + async function doReAuthenticate() { + console.warn('Access token or refresh token is invalid or expired. '); + const accessStore = useAccessStore(); + const authStore = useAuthStore(); + accessStore.setAccessToken(null); + if ( + preferences.app.loginExpiredMode === 'modal' && + accessStore.isAccessChecked + ) { + accessStore.setLoginExpired(true); + } else { + await authStore.logout(); + } + } + + /** + * 刷新token逻辑 + */ + async function doRefreshToken() { + const accessStore = useAccessStore(); + const resp = await refreshTokenApi(); + const newToken = resp.data; + accessStore.setAccessToken(newToken); + return newToken; + } + + function formatToken(token: null | string) { + return token ? `${token}` : null; + } + + // 请求头处理 + client.addRequestInterceptor({ + fulfilled: async (config) => { + const accessStore = useAccessStore(); + + config.headers['easyflow-token'] = formatToken(accessStore.accessToken); + config.headers['Accept-Language'] = preferences.app.locale; + return config; + }, + }); + + // 处理返回的响应数据格式 + client.addResponseInterceptor( + defaultResponseInterceptor({ + codeField: 'errorCode', + dataField: 'data', + showErrorMessage: (message) => { + ElMessage.error(message); + }, + successCode: 0, + }), + ); + + // token过期的处理 + client.addResponseInterceptor( + authenticateResponseInterceptor({ + client, + doReAuthenticate, + doRefreshToken, + enableRefreshToken: preferences.app.enableRefreshToken, + formatToken, + }), + ); + + // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 + client.addResponseInterceptor( + errorMessageResponseInterceptor((msg: string, error) => { + // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg + // 当前mock接口返回的错误字段是 error 或者 message + const responseData = error?.response?.data ?? {}; + const errorMessage = responseData?.error ?? responseData?.message ?? ''; + // 如果没有错误信息,则会根据状态码进行提示 + ElMessage.error(errorMessage || msg); + }), + ); + + return client; +} + +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); + +export const api = createRequestClient(apiURL, { + responseReturn: 'body', +}); + +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); + +export interface SseOptions { + onMessage?: (message: ServerSentEventMessage) => void; + onError?: (err: any) => void; + onFinished?: () => void; +} +export class SseClient { + private controller: AbortController | null = null; + private currentRequestId = 0; + + abort(): void { + if (this.controller) { + this.controller.abort(); + this.controller = null; + } + } + + isActive(): boolean { + return this.controller !== null; + } + + async post(url: string, data?: any, options?: SseOptions): Promise { + // 生成唯一的请求ID + const requestId = ++this.currentRequestId; + const currentRequestId = requestId; + + // 如果已有请求,先取消 + this.abort(); + + // 创建新的控制器 + const controller = new AbortController(); + this.controller = controller; + + // 保存信号的引用到局部变量 + const signal = controller.signal; + + try { + const res = await fetch(apiURL + url, { + method: 'POST', + signal, // 使用局部变量 signal + headers: this.getHeaders(), + body: JSON.stringify(data), + }); + + if (!res.ok) { + const error = new Error(`HTTP ${res.status}: ${res.statusText}`); + options?.onError?.(error); + return; + } + + // 在开始事件流之前检查是否还是同一个请求 + if (this.currentRequestId !== currentRequestId) { + return; + } + + const msgEvents = events(res, signal); + + try { + for await (const event of msgEvents) { + // 每次迭代都检查是否还是同一个请求 + if (this.currentRequestId !== currentRequestId) { + break; + } + options?.onMessage?.(event); + } + } catch (innerError) { + options?.onError?.(innerError); + } + + // 只有在还是同一个请求的情况下才调用 onFinished + if (this.currentRequestId === currentRequestId) { + options?.onFinished?.(); + } + } catch (error) { + if (this.currentRequestId !== currentRequestId) { + return; + } + console.error('SSE错误:', error); + options?.onError?.(error); + } finally { + // 只有当还是当前请求时才清除 controller + if (this.currentRequestId === currentRequestId) { + this.controller = null; + } + } + } + + private getHeaders() { + const accessStore = useAccessStore(); + return { + Accept: 'text/event-stream', + 'Content-Type': 'application/json', + 'easyflow-token': accessStore.accessToken || '', + }; + } +} + +export const sseClient = new SseClient(); diff --git a/easyflow-ui-admin/app/src/app.vue b/easyflow-ui-admin/app/src/app.vue new file mode 100644 index 0000000..00a9c72 --- /dev/null +++ b/easyflow-ui-admin/app/src/app.vue @@ -0,0 +1,17 @@ + + + diff --git a/easyflow-ui-admin/app/src/assets/ai/bot/defaultBotAvatar.png b/easyflow-ui-admin/app/src/assets/ai/bot/defaultBotAvatar.png new file mode 100644 index 0000000..817cc26 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/bot/defaultBotAvatar.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/knowledge/book.png b/easyflow-ui-admin/app/src/assets/ai/knowledge/book.png new file mode 100644 index 0000000..6a2d8f2 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/knowledge/book.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/knowledge/book.svg b/easyflow-ui-admin/app/src/assets/ai/knowledge/book.svg new file mode 100644 index 0000000..840d42e --- /dev/null +++ b/easyflow-ui-admin/app/src/assets/ai/knowledge/book.svg @@ -0,0 +1,17 @@ + + + 编组备份 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyflow-ui-admin/app/src/assets/ai/knowledge/document.svg b/easyflow-ui-admin/app/src/assets/ai/knowledge/document.svg new file mode 100644 index 0000000..53589b8 --- /dev/null +++ b/easyflow-ui-admin/app/src/assets/ai/knowledge/document.svg @@ -0,0 +1,27 @@ + + + 编组 9 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyflow-ui-admin/app/src/assets/ai/plugin/defaultPluginIcon.png b/easyflow-ui-admin/app/src/assets/ai/plugin/defaultPluginIcon.png new file mode 100644 index 0000000..4f1aa84 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/plugin/defaultPluginIcon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/resource/audio-icon.png b/easyflow-ui-admin/app/src/assets/ai/resource/audio-icon.png new file mode 100644 index 0000000..97a01b4 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/resource/audio-icon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/resource/doc-icon.png b/easyflow-ui-admin/app/src/assets/ai/resource/doc-icon.png new file mode 100644 index 0000000..26853bb Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/resource/doc-icon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/resource/other-icon.png b/easyflow-ui-admin/app/src/assets/ai/resource/other-icon.png new file mode 100644 index 0000000..0ae38ae Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/resource/other-icon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/resource/video-icon.png b/easyflow-ui-admin/app/src/assets/ai/resource/video-icon.png new file mode 100644 index 0000000..43598b9 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/resource/video-icon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-file.png b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-file.png new file mode 100644 index 0000000..fa28fb8 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-file.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-icon.png b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-icon.png new file mode 100644 index 0000000..985da1e Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-icon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-other.png b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-other.png new file mode 100644 index 0000000..b3e00c5 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/workflow/confirm-other.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/workflow/fileIcon.png b/easyflow-ui-admin/app/src/assets/ai/workflow/fileIcon.png new file mode 100644 index 0000000..adde504 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/workflow/fileIcon.png differ diff --git a/easyflow-ui-admin/app/src/assets/ai/workflow/workflowIcon.png b/easyflow-ui-admin/app/src/assets/ai/workflow/workflowIcon.png new file mode 100644 index 0000000..69d3c8b Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/ai/workflow/workflowIcon.png differ diff --git a/easyflow-ui-admin/app/src/assets/datacenter/table2x.png b/easyflow-ui-admin/app/src/assets/datacenter/table2x.png new file mode 100644 index 0000000..7e41bbe Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/datacenter/table2x.png differ diff --git a/easyflow-ui-admin/app/src/assets/datacenter/upload.png b/easyflow-ui-admin/app/src/assets/datacenter/upload.png new file mode 100644 index 0000000..2fdfe76 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/datacenter/upload.png differ diff --git a/easyflow-ui-admin/app/src/assets/defaultUserAvatar.png b/easyflow-ui-admin/app/src/assets/defaultUserAvatar.png new file mode 100644 index 0000000..207dc70 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/defaultUserAvatar.png differ diff --git a/easyflow-ui-admin/app/src/assets/login/dingding-60.png b/easyflow-ui-admin/app/src/assets/login/dingding-60.png new file mode 100644 index 0000000..3b86c17 Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/login/dingding-60.png differ diff --git a/easyflow-ui-admin/app/src/assets/login/wx-60.png b/easyflow-ui-admin/app/src/assets/login/wx-60.png new file mode 100644 index 0000000..52383ef Binary files /dev/null and b/easyflow-ui-admin/app/src/assets/login/wx-60.png differ diff --git a/easyflow-ui-admin/app/src/bootstrap.ts b/easyflow-ui-admin/app/src/bootstrap.ts new file mode 100644 index 0000000..5381a14 --- /dev/null +++ b/easyflow-ui-admin/app/src/bootstrap.ts @@ -0,0 +1,92 @@ +import { createApp, watchEffect } from 'vue'; +import { + BubbleList, + Conversations, + Sender, + Thinking, + XMarkdown, +} from 'vue-element-plus-x'; + +import { registerAccessDirective } from '@easyflow/access'; +import { registerLoadingDirective } from '@easyflow/common-ui'; +import { preferences } from '@easyflow/preferences'; +import { initStores } from '@easyflow/stores'; +import '@easyflow/styles'; +import '@easyflow/styles/ele'; + +import { useTitle } from '@vueuse/core'; +import { ElLoading } from 'element-plus'; + +import { $t, setupI18n } from '#/locales'; + +import { initComponentAdapter } from './adapter/component'; +import { initSetupEasyFlowForm } from './adapter/form'; +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string) { + // 初始化组件适配器 + await initComponentAdapter(); + + // 初始化表单组件 + await initSetupEasyFlowForm(); + + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 2000, + // }); + const app = createApp(App); + + // 注册Element Plus提供的v-loading指令 + app.directive('loading', ElLoading.directive); + + app.component('ElBubbleList', BubbleList); + app.component('ElConversations', Conversations); + app.component('ElSender', Sender); + app.component('ElXMarkdown', XMarkdown); + app.component('ElThinking', Thinking); + + // 注册EasyFlow提供的v-loading和v-spinning指令 + registerLoadingDirective(app, { + loading: false, // EasyFlow提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册EasyFlow提供的v-loading指令 + spinning: 'spinning', + }); + + // 国际化 i18n 配置 + await setupI18n(app); + + // 配置 pinia-tore + await initStores(app, { namespace }); + + // 安装权限指令 + registerAccessDirective(app); + + // 初始化 tippy + const { initTippy } = await import('@easyflow/common-ui/es/tippy'); + initTippy(app); + + // 配置路由及路由守卫 + app.use(router); + + // 配置Motion插件 + const { MotionPlugin } = await import('@easyflow/plugins/motion'); + app.use(MotionPlugin); + + // 动态更新标题 + watchEffect(() => { + if (preferences.app.dynamicTitle) { + const routeTitle = router.currentRoute.value.meta?.title; + const pageTitle = + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; + useTitle(pageTitle); + } + }); + + app.mount('#app'); +} + +export { bootstrap }; diff --git a/easyflow-ui-admin/app/src/components/botAvatar/botAvatar.vue b/easyflow-ui-admin/app/src/components/botAvatar/botAvatar.vue new file mode 100644 index 0000000..8b6bc90 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/botAvatar/botAvatar.vue @@ -0,0 +1,14 @@ + + + diff --git a/easyflow-ui-admin/app/src/components/cardPage/CardPage.vue b/easyflow-ui-admin/app/src/components/cardPage/CardPage.vue new file mode 100644 index 0000000..ea276e9 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/cardPage/CardPage.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/categoryPanel/CategoryCrudPanel.vue b/easyflow-ui-admin/app/src/components/categoryPanel/CategoryCrudPanel.vue new file mode 100644 index 0000000..c9c6ded --- /dev/null +++ b/easyflow-ui-admin/app/src/components/categoryPanel/CategoryCrudPanel.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/categoryPanel/CategoryPanel.vue b/easyflow-ui-admin/app/src/components/categoryPanel/CategoryPanel.vue new file mode 100644 index 0000000..4a32d23 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/categoryPanel/CategoryPanel.vue @@ -0,0 +1,370 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/chat/ProblemPresupposition.vue b/easyflow-ui-admin/app/src/components/chat/ProblemPresupposition.vue new file mode 100644 index 0000000..49209b1 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/chat/ProblemPresupposition.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/chat/PublishWxOfficalAccount.vue b/easyflow-ui-admin/app/src/components/chat/PublishWxOfficalAccount.vue new file mode 100644 index 0000000..d6c3eb0 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/chat/PublishWxOfficalAccount.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/chat/SenderHeader.vue b/easyflow-ui-admin/app/src/components/chat/SenderHeader.vue new file mode 100644 index 0000000..ac63180 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/chat/SenderHeader.vue @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyflow-ui-admin/app/src/components/chat/chat.vue b/easyflow-ui-admin/app/src/components/chat/chat.vue new file mode 100644 index 0000000..27039ac --- /dev/null +++ b/easyflow-ui-admin/app/src/components/chat/chat.vue @@ -0,0 +1,700 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/collapse/CustomCoolapse.vue b/easyflow-ui-admin/app/src/components/collapse/CustomCoolapse.vue new file mode 100644 index 0000000..0ff32f2 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/collapse/CustomCoolapse.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/collapseViewItem/CollapseViewItem.vue b/easyflow-ui-admin/app/src/components/collapseViewItem/CollapseViewItem.vue new file mode 100644 index 0000000..b9e03c9 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/collapseViewItem/CollapseViewItem.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/commonSelectModal/CommonSelectDataModal.vue b/easyflow-ui-admin/app/src/components/commonSelectModal/CommonSelectDataModal.vue new file mode 100644 index 0000000..aeee7b8 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/commonSelectModal/CommonSelectDataModal.vue @@ -0,0 +1,536 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/cron/CronGenerator.vue b/easyflow-ui-admin/app/src/components/cron/CronGenerator.vue new file mode 100644 index 0000000..a0951b5 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/cron/CronGenerator.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/cron/CronPicker.vue b/easyflow-ui-admin/app/src/components/cron/CronPicker.vue new file mode 100644 index 0000000..a0dbbc1 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/cron/CronPicker.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/cron/CronTabPane.vue b/easyflow-ui-admin/app/src/components/cron/CronTabPane.vue new file mode 100644 index 0000000..afeb34c --- /dev/null +++ b/easyflow-ui-admin/app/src/components/cron/CronTabPane.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/dict/DictSelect.vue b/easyflow-ui-admin/app/src/components/dict/DictSelect.vue new file mode 100644 index 0000000..4baa82e --- /dev/null +++ b/easyflow-ui-admin/app/src/components/dict/DictSelect.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/headerSearch/HeaderSearch.vue b/easyflow-ui-admin/app/src/components/headerSearch/HeaderSearch.vue new file mode 100644 index 0000000..c4e5e56 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/headerSearch/HeaderSearch.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/CategorizeIcon.vue b/easyflow-ui-admin/app/src/components/icons/CategorizeIcon.vue new file mode 100644 index 0000000..9aeffdd --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/CategorizeIcon.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/DesignIcon.vue b/easyflow-ui-admin/app/src/components/icons/DesignIcon.vue new file mode 100644 index 0000000..c5052ae --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/DesignIcon.vue @@ -0,0 +1,18 @@ + diff --git a/easyflow-ui-admin/app/src/components/icons/EditIcon.vue b/easyflow-ui-admin/app/src/components/icons/EditIcon.vue new file mode 100644 index 0000000..92141a8 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/EditIcon.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/MagicStaffIcon.vue b/easyflow-ui-admin/app/src/components/icons/MagicStaffIcon.vue new file mode 100644 index 0000000..318bf7b --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/MagicStaffIcon.vue @@ -0,0 +1,55 @@ + diff --git a/easyflow-ui-admin/app/src/components/icons/ManageIcon.vue b/easyflow-ui-admin/app/src/components/icons/ManageIcon.vue new file mode 100644 index 0000000..866f6e4 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/ManageIcon.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/PluginIcon.vue b/easyflow-ui-admin/app/src/components/icons/PluginIcon.vue new file mode 100644 index 0000000..0e8497e --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/PluginIcon.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/PluginToolIcon.vue b/easyflow-ui-admin/app/src/components/icons/PluginToolIcon.vue new file mode 100644 index 0000000..dcc0e42 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/PluginToolIcon.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/icons/RecordingIcon.vue b/easyflow-ui-admin/app/src/components/icons/RecordingIcon.vue new file mode 100644 index 0000000..6165ebd --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/RecordingIcon.vue @@ -0,0 +1,129 @@ + diff --git a/easyflow-ui-admin/app/src/components/icons/SendEnableIcon.vue b/easyflow-ui-admin/app/src/components/icons/SendEnableIcon.vue new file mode 100644 index 0000000..bfa1d10 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/SendEnableIcon.vue @@ -0,0 +1,37 @@ + diff --git a/easyflow-ui-admin/app/src/components/icons/SendIcon.vue b/easyflow-ui-admin/app/src/components/icons/SendIcon.vue new file mode 100644 index 0000000..a400927 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/SendIcon.vue @@ -0,0 +1,38 @@ + diff --git a/easyflow-ui-admin/app/src/components/icons/SendingIcon.vue b/easyflow-ui-admin/app/src/components/icons/SendingIcon.vue new file mode 100644 index 0000000..d29473b --- /dev/null +++ b/easyflow-ui-admin/app/src/components/icons/SendingIcon.vue @@ -0,0 +1,47 @@ + diff --git a/easyflow-ui-admin/app/src/components/json/ShowJson.vue b/easyflow-ui-admin/app/src/components/json/ShowJson.vue new file mode 100644 index 0000000..43d7b71 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/json/ShowJson.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/page/CardList.vue b/easyflow-ui-admin/app/src/components/page/CardList.vue new file mode 100644 index 0000000..5fb9003 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/page/CardList.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/page/PageData.vue b/easyflow-ui-admin/app/src/components/page/PageData.vue new file mode 100644 index 0000000..1f07bcc --- /dev/null +++ b/easyflow-ui-admin/app/src/components/page/PageData.vue @@ -0,0 +1,129 @@ + + + diff --git a/easyflow-ui-admin/app/src/components/page/PageSide.vue b/easyflow-ui-admin/app/src/components/page/PageSide.vue new file mode 100644 index 0000000..b7d97c0 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/page/PageSide.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/tag/Tag.vue b/easyflow-ui-admin/app/src/components/tag/Tag.vue new file mode 100644 index 0000000..85a6cf3 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/tag/Tag.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/tree/Tree.vue b/easyflow-ui-admin/app/src/components/tree/Tree.vue new file mode 100644 index 0000000..61556c3 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/tree/Tree.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/upload/ChatFileUploader.vue b/easyflow-ui-admin/app/src/components/upload/ChatFileUploader.vue new file mode 100644 index 0000000..2167283 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/ChatFileUploader.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/upload/Cropper.vue b/easyflow-ui-admin/app/src/components/upload/Cropper.vue new file mode 100644 index 0000000..b57404c --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/Cropper.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/upload/CropperMulti.vue b/easyflow-ui-admin/app/src/components/upload/CropperMulti.vue new file mode 100644 index 0000000..f34faac --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/CropperMulti.vue @@ -0,0 +1,603 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/components/upload/DragFileUpload.vue b/easyflow-ui-admin/app/src/components/upload/DragFileUpload.vue new file mode 100644 index 0000000..2ea0f30 --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/DragFileUpload.vue @@ -0,0 +1,83 @@ + + + diff --git a/easyflow-ui-admin/app/src/components/upload/Upload.vue b/easyflow-ui-admin/app/src/components/upload/Upload.vue new file mode 100644 index 0000000..f9989bc --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/Upload.vue @@ -0,0 +1,72 @@ + + + diff --git a/easyflow-ui-admin/app/src/components/upload/UploadAvatar.vue b/easyflow-ui-admin/app/src/components/upload/UploadAvatar.vue new file mode 100644 index 0000000..fec20ad --- /dev/null +++ b/easyflow-ui-admin/app/src/components/upload/UploadAvatar.vue @@ -0,0 +1,125 @@ + + + + + + + diff --git a/easyflow-ui-admin/app/src/layouts/auth.vue b/easyflow-ui-admin/app/src/layouts/auth.vue new file mode 100644 index 0000000..a99496d --- /dev/null +++ b/easyflow-ui-admin/app/src/layouts/auth.vue @@ -0,0 +1,25 @@ + + + diff --git a/easyflow-ui-admin/app/src/layouts/basic.vue b/easyflow-ui-admin/app/src/layouts/basic.vue new file mode 100644 index 0000000..91316c8 --- /dev/null +++ b/easyflow-ui-admin/app/src/layouts/basic.vue @@ -0,0 +1,197 @@ + + + diff --git a/easyflow-ui-admin/app/src/layouts/index.ts b/easyflow-ui-admin/app/src/layouts/index.ts new file mode 100644 index 0000000..fb5d2fe --- /dev/null +++ b/easyflow-ui-admin/app/src/layouts/index.ts @@ -0,0 +1,6 @@ +const BasicLayout = () => import('./basic.vue'); +const AuthPageLayout = () => import('./auth.vue'); + +const IFrameView = () => import('@easyflow/layouts').then((m) => m.IFrameView); + +export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/easyflow-ui-admin/app/src/locales/README.md b/easyflow-ui-admin/app/src/locales/README.md new file mode 100644 index 0000000..7b45103 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/README.md @@ -0,0 +1,3 @@ +# locale + +每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/easyflow-ui-admin/app/src/locales/index.ts b/easyflow-ui-admin/app/src/locales/index.ts new file mode 100644 index 0000000..d647d1b --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/index.ts @@ -0,0 +1,102 @@ +import type { Language } from 'element-plus/es/locale'; + +import type { App } from 'vue'; + +import type { LocaleSetupOptions, SupportedLanguagesType } from '@easyflow/locales'; + +import { ref } from 'vue'; + +import { + $t, + setupI18n as coreSetup, + loadLocalesMapFromDir, +} from '@easyflow/locales'; +import { preferences } from '@easyflow/preferences'; + +import dayjs from 'dayjs'; +import enLocale from 'element-plus/es/locale/lang/en'; +import defaultLocale from 'element-plus/es/locale/lang/zh-cn'; + +const elementLocale = ref(defaultLocale); + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); +/** + * 加载应用特有的语言包 + * 这里也可以改造为从服务端获取翻译数据 + * @param lang + */ +async function loadMessages(lang: SupportedLanguagesType) { + const [appLocaleMessages] = await Promise.all([ + localesMap[lang]?.(), + loadThirdPartyMessage(lang), + ]); + return appLocaleMessages?.default; +} + +/** + * 加载第三方组件库的语言包 + * @param lang + */ +async function loadThirdPartyMessage(lang: SupportedLanguagesType) { + await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]); +} + +/** + * 加载dayjs的语言包 + * @param lang + */ +async function loadDayjsLocale(lang: SupportedLanguagesType) { + let locale; + switch (lang) { + case 'en-US': { + locale = await import('dayjs/locale/en'); + break; + } + case 'zh-CN': { + locale = await import('dayjs/locale/zh-cn'); + break; + } + // 默认使用英语 + default: { + locale = await import('dayjs/locale/en'); + } + } + if (locale) { + dayjs.locale(locale); + } else { + console.error(`Failed to load dayjs locale for ${lang}`); + } +} + +/** + * 加载element-plus的语言包 + * @param lang + */ +async function loadElementLocale(lang: SupportedLanguagesType) { + switch (lang) { + case 'en-US': { + elementLocale.value = enLocale; + break; + } + case 'zh-CN': { + elementLocale.value = defaultLocale; + break; + } + } +} + +async function setupI18n(app: App, options: LocaleSetupOptions = {}) { + await coreSetup(app, { + defaultLocale: preferences.app.locale, + loadMessages, + missingWarn: !import.meta.env.PROD, + ...options, + }); +} + +export { $t, elementLocale, setupI18n }; diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/aiResource.json b/easyflow-ui-admin/app/src/locales/langs/en-US/aiResource.json new file mode 100644 index 0000000..f18527a --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/aiResource.json @@ -0,0 +1,20 @@ +{ + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "resourceType": "ResourceType", + "resourceName": "ResourceName", + "suffix": "Suffix", + "resourceUrl": "ResourceUrl", + "origin": "Origin", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "isDeleted": "IsDeleted", + "fileSize": "FileSize", + "categoryId": "Category", + "choose": "Choose" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflow.json b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflow.json new file mode 100644 index 0000000..f4e1c69 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflow.json @@ -0,0 +1,70 @@ +{ + "id": "Id", + "alias": "Alias", + "deptId": "DeptId", + "tenantId": "TenantId", + "title": "Title", + "description": "Description", + "icon": "Icon", + "content": "Content", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "englishName": "EnglishName", + "status": "ShowInUserCenter", + "categoryId": "Category", + "params": "Params", + "steps": "Steps", + "result": "Result", + "confirm": "For contents to be confirmed, please confirm first!", + "completed": "Chain has been completed, please start a new one.", + "fileContentExtraction": "FileContentExtraction", + "documentAddress": "DocumentAddress", + "parsedText": "ParsedText", + "resourceSync": "ResourceSync", + "originUrl": "OriginUrl", + "savedUrl": "SavedUrl", + "saveOptions": "SaveOptions", + "image": "Image", + "video": "Video", + "audio": "Audio", + "document": "Document", + "other": "Other", + "fileGeneration": "FileGeneration", + "fileSettings": "FileSettings", + "fileDownloadURL": "FileDownloadURL", + "pluginSelect": "PluginSelect", + "saveData": "SaveData", + "dataToBeSaved": "DataToBeSaved", + "successInsertedRecords": "SuccessInsertedRecords", + "dataTable": "DataTable", + "queryData": "QueryData", + "queryResult": "QueryResult", + "filterConditions": "FilterConditions", + "limit": "Limit", + "sqlQuery": "SQL Query", + "subProcess": "SubProcess", + "workflowSelect": "WorkflowSelect", + "bochaSearch": "BochaSearch", + "descriptions": { + "fileContentExtraction": "Extract text content from PDF or Word documents, etc", + "documentAddress": "Document URL address", + "parsedText": "Parsed text content", + "resourceSync": "Download resource files and save to system resource library", + "originUrl": "File origin URL", + "resourceType": "Please select the type of resource", + "fileGeneration": "Generate Word, PDF, HTML, etc. files for users to download", + "fileType": "Please select the type of file to generate", + "fileDownloadURL": "Generated file URL", + "plugin": "Select a predefined plugin", + "saveData": "Save data to data hub", + "dataToBeSaved": "List of data to be saved", + "dataTable": "Please select a data table", + "queryData": "Query data from the data hub", + "sqlQuery": "Query the database via SQL", + "enterSQL": "Please enter the SQL statement", + "queryResultJson": "Query result (JSON object)", + "subProcess": "Select a predefined process" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowCategory.json b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowCategory.json new file mode 100644 index 0000000..ed18a81 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowCategory.json @@ -0,0 +1,10 @@ +{ + "id": "Id", + "categoryName": "CategoryName", + "sortNo": "SortNo", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "status": "Status" +} \ No newline at end of file diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowExecRecord.json b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowExecRecord.json new file mode 100644 index 0000000..a568b58 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowExecRecord.json @@ -0,0 +1,25 @@ +{ + "id": "Id", + "execKey": "ExecKey", + "workflowId": "WorkflowId", + "title": "Title", + "description": "Description", + "input": "Input", + "output": "Output", + "workflowJson": "WorkflowJson", + "startTime": "StartTime", + "endTime": "EndTime", + "tokens": "Tokens", + "status": "Status", + "createdKey": "CreatedKey", + "createdBy": "CreatedBy", + "errorInfo": "ErrorInfo", + "moduleName": "ExecuteRecords", + "execTime": "TimeConsuming", + "status1": "Running", + "status5": "Suspend", + "status10": "Error", + "status20": "Success", + "status21": "Failed", + "status22": "Cancel" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowRecordStep.json b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowRecordStep.json new file mode 100644 index 0000000..0aa8c11 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/aiWorkflowRecordStep.json @@ -0,0 +1,22 @@ +{ + "id": "Id", + "recordId": "RecordId", + "execKey": "ExecKey", + "nodeId": "NodeId", + "nodeName": "NodeName", + "input": "Input", + "output": "Output", + "nodeData": "NodeData", + "startTime": "StartTime", + "endTime": "EndTime", + "tokens": "Tokens", + "status": "Status", + "errorInfo": "ErrorInfo", + "moduleName": "Steps", + "execTime": "TimeConsuming", + "status1": "Running", + "status6": "Suspend", + "status10": "Error", + "status20": "Success", + "status21": "Failed" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/bot.json b/easyflow-ui-admin/app/src/locales/langs/en-US/bot.json new file mode 100644 index 0000000..36dae8b --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/bot.json @@ -0,0 +1,27 @@ +{ + "problemPresupposition": "ProblemPresupposition", + "noPermission": "NoPermission", + "llm": "LLM", + "temperature": "Temperature", + "maxReplyLength": "MaxReplyLength", + "historyCount": "HistoryCount", + "skill": "Skill", + "conversationSettings": "ConversationSettings", + "welcomeMessage": "WelcomeMessage", + "deepThinking": "DeepThinking", + "enableDeepThinking": "EnableDeepThinking", + "publish": "Publish", + "postToWeChatOfficialAccount": "PostToWeChatOfficialAccount", + "configured": "Configured", + "notConfigured": "NotConfigured", + "placeholder": { + "welcome": "Please enter welcome message", + "prompt": "You are an AI assistant. Please provide clear and accurate answers based on the user's questions.", + "permission": "No permission to configure the bot!" + }, + "systemPrompt": "System Prompt", + "aiOptimization": "AI Optimization", + "weChatOfficialAccountConfiguration": "WeChat Official Account configuration", + "aiOptimizedPrompts": "AI Optimized Prompts", + "chatAssistant": "Chat Assistant" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/button.json b/easyflow-ui-admin/app/src/locales/langs/en-US/button.json new file mode 100644 index 0000000..c39ad7f --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/button.json @@ -0,0 +1,47 @@ +{ + "query": "Query", + "reset": "Reset", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "export": "Export", + "import": "Import", + "save": "Save", + "cancel": "Cancel", + "confirm": "Confirm", + "addLlm": "New large model added", + "oneClickAdd": "One-click add", + "newConversation": "New Conversation", + "start": "Start", + "stop": "Stop", + "log": "Log", + "back": "Back", + "importFile": "ImportFile", + "view": "View", + "download": "Download", + "upload": "Upload", + "preview": "Preview", + "nextStep": "NextStep", + "previousStep": "PreviousStep", + "addLine": "AddRecord", + "batchImport": "BatchImport", + "startImport": "StartImport", + "design": "Design", + "run": "Run", + "runTest": "RunTest", + "copy": "Copy", + "selectAll": "Select All", + "choose": "Select", + "setting": "Setting", + "create": "Create", + "update": "Update", + "oneClickOptimization": "One-click optimization", + "replace": "Replace", + "markAsRead": "MarkAsRead", + "markAsResolved": "MarkAsResolved", + "optimizing": "Optimizing", + "regenerate": "Regenerate", + "hide": "Hide", + "more": "Mode", + "viewSegmentation": "ViewSegmentation" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/common.json b/easyflow-ui-admin/app/src/locales/langs/en-US/common.json new file mode 100644 index 0000000..5cbb07c --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/common.json @@ -0,0 +1,25 @@ +{ + "handle": "Operate", + "searchPlaceholder": "Please enter search content", + "allCategories": "All", + "history": "History", + "noDataAvailable": "No data available", + "isRequired": " is required", + "avatar": "Avatar", + "otherLoginType": "Other login methods", + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Second": "Second", + "Min": "Min", + "Hour": "Hour", + "Day": "Day", + "Month": "Month", + "Week": "Week", + "yes": "Yes", + "no": "No" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/cron.json b/easyflow-ui-admin/app/src/locales/langs/en-US/cron.json new file mode 100644 index 0000000..cb967bb --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/cron.json @@ -0,0 +1,18 @@ +{ + "cronExpressionGenerator": "CronExpressionGenerator", + "GenerateResult": "GenerateResult", + "CronExpression": "CronExpression", + "UseThisValue": "UseThisValue", + "CheckLast5ExecutionTimes": "CheckLast5ExecutionTimes", + "Last5ExecutionTimes": "Last5ExecutionTimes", + "ClickGenerate": "ClickGenerate", + "Per": "Per", + "NotSpecified": "NotSpecified", + "Cycle": "Cycle", + "From": "From", + "StartPer": "Start, Per", + "ExecuteOnce": "ExecuteOnce", + "Rang": "Rang", + "To": "To", + "Specify": "Specify" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/cropper.json b/easyflow-ui-admin/app/src/locales/langs/en-US/cropper.json new file mode 100644 index 0000000..142d076 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/cropper.json @@ -0,0 +1,20 @@ +{ + "ImageCropping": "ImageCropping", + "message": { + "onlyImage": "Only image files can be uploaded!", + "imgSize": "Image size must not exceed {limit}MB!", + "uploadFailed": "Upload failed", + "notUrl": "Upload successful but no image URL returned", + "uploadSuccessful": "Upload successful!", + "reuploadSuccessful": "Re-upload successful!", + "notInitialized": "Cropper not initialized", + "cropFailed": "Crop failed, unable to get cropped image", + "fileCount": "Maximum {count} files allowed", + "avatarFormat": "Avatar must be {format} format only", + "avatarSize": "Avatar limit {limit} MB" + }, + "Uploading": "Uploading...", + "ConfirmCrop": "ConfirmCrop", + "Re-upload": "Re-upload", + "ClickToUpload": "ClickToUpload" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTable.json b/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTable.json new file mode 100644 index 0000000..fa9c56d --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTable.json @@ -0,0 +1,33 @@ +{ + "title": "DataCenter", + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "tableName": "TableName", + "tableDesc": "TableDesc", + "actualTable": "ActualTable", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "fields": "Fields", + "fieldName": "FiledName", + "fieldDesc": "Description", + "fieldType": "Type", + "required": "Required", + "noFieldError": "At least one field is required", + "fieldInfoError": "Field info is not completed", + "nameRegx": "It can only contain lowercase letters, numbers, and underscores, and must start with a lowercase letter", + "structure": "TableStructure", + "data": "Data", + "uploadTitle": "Click to upload", + "uploadDesc": "Choose an excel file to upload, file size limit 10MB.", + "downloadTemplate": "Download template", + "importComplete": "Import is completed", + "totalNum": "Total Count", + "successNum": "Success Count", + "failNum": "Fail Count", + "failList": "Fail List" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTableFields.json b/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTableFields.json new file mode 100644 index 0000000..a125757 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/datacenterTableFields.json @@ -0,0 +1,13 @@ +{ + "id": "Id", + "tableId": "TableId", + "fieldName": "FieldName", + "fieldDesc": "FieldDesc", + "fieldType": "FieldType", + "required": "Required", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/demos.json b/easyflow-ui-admin/app/src/locales/langs/en-US/demos.json new file mode 100644 index 0000000..44d41c6 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/demos.json @@ -0,0 +1,14 @@ +{ + "title": "Demos", + "elementPlus": "Element Plus", + "form": "Form", + "easyflow": { + "title": "Project", + "about": "About", + "document": "Document", + "antdv": "Ant Design Vue Version", + "naive-ui": "Naive UI Version", + "element-plus": "Element Plus Version", + "tdesign": "TDesign Vue Version" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/dictSelect.json b/easyflow-ui-admin/app/src/locales/langs/en-US/dictSelect.json new file mode 100644 index 0000000..db64bc8 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/dictSelect.json @@ -0,0 +1,4 @@ +{ + "placeholder": "Please select", + "getError": "Get Data Error!" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json new file mode 100644 index 0000000..086894e --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json @@ -0,0 +1,88 @@ +{ + "id": "Id", + "alias": "Alias", + "deptId": "DeptId", + "tenantId": "TenantId", + "icon": "Icon", + "title": "Title", + "categoryId": "Category", + "description": "Description", + "slug": "Slug", + "vectorStoreEnable": "VectorStoreEnable", + "vectorStoreType": "VectorStoreType", + "vectorStoreCollection": "VectorStoreCollection", + "vectorStoreConfig": "VectorStoreConfig", + "vectorEmbedLlmId": "VectorEmbedLlm", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "options": "Options", + "rerankLlmId": "RerankLlm", + "searchEngineEnable": "SearchEngineEnable", + "englishName": "EnglishName", + "documentType": "DocumentType", + "fileName": "fileName", + "knowledgeCount": "Number of knowledge items", + "createdModifyTime": "Creation/update time", + "documentList": "documentList", + "knowledgeRetrieval": "knowledgeRetrieval", + "config": "DocumentCollection", + "sorting": "Sorting", + "content": "Content", + "placeholder": { + "title": "Please input title", + "description": "Please provide a description so that the large model can better understand the knowledge base and make calls", + "englishName": "Please enter an English name", + "alias": "Please enter an alias, Chinese is not allowed", + "embedLlm": "Please choose a vector model", + "rerankLlm": "Please choose to rearrange the model", + "vectorStoreCollection": "Can only contain letters, numbers, and underscores with a length between 3-20 characters", + "vectorStoreType": "Please select the vector database type" + }, + "importDoc": { + "fileUpload": "File upload", + "parameterSettings": "ParameterSettings", + "segmentedPreview": "SegmentedPreview", + "confirmImport": "ConfirmImport", + "fileName": "File Name", + "progressUpload": "Progress of file upload", + "fileSize": "File size" + }, + "splitterDoc": { + "fileType": "FileType", + "splitterName": "Segmenter", + "chunkSize": "SegmentLength", + "overlapSize": "SegmentOverlap", + "regex": "RegularExpression", + "document": "Document", + "simpleDocumentSplitter": "SimpleDocumentSplitter", + "simpleTokenizeSplitter": "SimpleTokenizeSplitter", + "regexDocumentSplitter": "RegexDocumentSplitter", + "markdownHeaderSplitter": "MarkdownHeaderSplitter", + "mdSplitterLevel": "MarkdownSplitterLevel", + "uploadStatus": "UploadStatus", + "pendingUpload": "PendingUpload", + "completed": "Completed", + "uploading": "Parsing in progress", + "importSuccess": "ImportSuccess" + }, + "documentManagement": "Document management", + "actions": { + "knowledge": "Knowledge", + "retrieve": "Retrieve", + "addKnowledge": "AddKnowledge", + "confirmImport": "ConfirmImport", + "cancelImport": "CancelImport" + }, + "searchResults": "SearchResults", + "documentPreview": "DocumentPreview", + "total": "Total", + "segments": "Segments", + "similarityScore": "SimilarityScore", + "alibabaCloud": "AlibabaCloud", + "tencentCloud": "tencentCloud", + "vectorEmbedModelTips": "After successful vector data, it is not allowed to modify the vector model", + "dimensionOfVectorModelTips": "After successful vector data, it is not allowed to modify the dimensions of the vector model", + "dimensionOfVectorModel": "Dimension of vector model" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollectionSearch.json b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollectionSearch.json new file mode 100644 index 0000000..f07592f --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollectionSearch.json @@ -0,0 +1,40 @@ +{ + "title": "Knowledge Retrieval Configuration", + "docRecallMaxNum": { + "label": "Max Recall Documents", + "tooltip": "Maximum number of documents returned during retrieval. Larger values recall more comprehensively but slower." + }, + "simThreshold": { + "label": "Minimum Similarity", + "tooltip": "Higher values mean stricter filtering, only returning documents with higher similarity (range: 0-1, recommended: 0.6-0.8)." + }, + "button": { + "save": "Save Configuration" + }, + "message": { + "saveSuccess": "Configuration saved successfully", + "saveFailed": "Configuration saved failed" + }, + "placeholder": { + "count": "1-50" + }, + "switch": { + "on": "On", + "off": "Off" + }, + "unit": { + "count": "items" + }, + "searchEngineType": { + "label": "Search Engine Type", + "placeholder": "Please select search engine type", + "tooltip": "Select the engine type for document retrieval: Keyword search (fast)", + "vector": "Vector Retrieval", + "hybrid": "Hybrid Retrieval", + "keyword": "Keyword Retrieval" + }, + "searchEngineEnable": { + "label": "Enable Search Engine", + "tooltip": "Enable to use the search engine for document retrieval." + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/headerSearch.json b/easyflow-ui-admin/app/src/locales/langs/en-US/headerSearch.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/headerSearch.json @@ -0,0 +1 @@ +{} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/llm.json b/easyflow-ui-admin/app/src/locales/langs/en-US/llm.json new file mode 100644 index 0000000..3548087 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/llm.json @@ -0,0 +1,80 @@ +{ + "filed": + { + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "title": "Title", + "brand": "Brand", + "icon": "Icon", + "description": "Description", + "supportChat": "SupportChat", + "supportFunctionCalling": "SupportFunctionCalling", + "supportEmbed": "SupportEmbed", + "supportReranker": "SupportReranker", + "supportTextToImage": "SupportTextToImage", + "supportImageToImage": "SupportImageToImage", + "supportTextToAudio": "SupportTextToAudio", + "supportAudioToAudio": "SupportAudioToAudio", + "supportTextToVideo": "SupportTextToVideo", + "supportImageToVideo": "SupportImageToVideo", + "multimodal": "multimodal", + "llmEndpoint": "LlmEndpoint", + "chatPath": "ChatPath", + "embedPath": "embedPath", + "llmModel": "LlmModel", + "llmApiKey": "apiKey", + "llmExtraConfig": "LlmExtraConfig", + "options": "Options", + "ability": "Ability" + } + , + "llmModal": { + "TitleRequired": "Please enter the name", + "BrandRequired": "Please enter the brand", + "ModelRequired": "Please enter the model", + "ApiKeyRequired": "Please enter the apiKey", + "QuickAddLlm": "One-click addition of large models" + }, + "placeholder": { + "title": "Please enter the title", + "brand": "Please enter the brand", + "llmModel": "Please enter the llmModel", + "description": "Please enter the description" + }, + "actions": { + "verifyConfiguration": "Verify Configuration" + }, + "message": { + "verifySuccess": "Verification successful" + }, + "addProvider": "Provider list", + "modelType": "ModelType", + "llmModel": "ModelName", + "title": "name", + "groupName": "GroupName", + "provider": "供应商", + "ability": "ModelAbility", + "button": { + "management": "Management", + "test": "Test", + "addAllLlm": "Add models from the list", + "RetrieveAgain": "Retrieve the model list again" + }, + "all": "All", + "verifyLlmTitle": "Verify Large Model", + "searchTextPlaceholder": "Search for model name or name", + "testSuccess": "TestSuccess", + "modelAbility": { + "supportThinking": "Thinking", + "supportTool": "Tool", + "SupportAudio": "Audio", + "SupportVideo": "Video", + "SupportImage": "Image", + "supportFree": "Free", + "supportImageB64Only": "ImageB64Only", + "supportToolMessage": "SupportToolMessage" + }, + "requestPath": "RequestPath", + "modelToBeTested": "ModelToBeTested" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/llmProvider.json b/easyflow-ui-admin/app/src/locales/langs/en-US/llmProvider.json new file mode 100644 index 0000000..b0e9d6f --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/llmProvider.json @@ -0,0 +1,15 @@ +{ + "providerName": "ProviderName", + "provider": "Provider", + "icon": "Icon", + "apiKey": "ApiKey", + "endpoint": "APIAddress", + "embedPath": "EmbedPath", + "chatPath": "ChatPath", + "rerankPath": "RerankPath", + "embeddingModel": "EmbeddingModel", + "chatModel": "ChatModel", + "rerankModel": "RerankModel", + "model": "Model", + "apiType": "ApiType" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/mcp.json b/easyflow-ui-admin/app/src/locales/langs/en-US/mcp.json new file mode 100644 index 0000000..618fb27 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/mcp.json @@ -0,0 +1,31 @@ +{ + "id": "Id", + "title": "Title", + "description": "Description", + "configJson": "ConfigJson", + "deptId": "DeptId", + "tenantId": "TenantId", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "status": "Status", + "modal": { + "config": "Config", + "tool": "Tool", + "table": { + "availableTools": "Available Tools" + } + }, + "example": "Example:", + "restartMcpServer": "RestartMcpServer", + "message": { + "startupFail": "StartupFail", + "startupSuccessful": "StartupSuccessful", + "stopSuccessful": "StopSuccessful" + }, + "labels": { + "clientOnline": "ClientOnline", + "clientOffline": "ClientOffline" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json b/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json new file mode 100644 index 0000000..342b95d --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json @@ -0,0 +1,33 @@ +{ + "system": { + "title": "System", + "sysAccount": "Account", + "sysRole": "Role", + "sysMenu": "Menu", + "sysDept": "Department", + "sysPosition": "Position", + "sysDict": "Dictionary", + "sysJob": "Job", + "sysLog": "Log", + "sysFeedback": "UserFeedback", + "sysAppearance": "Appearance", + "oauth": "OAuth" + }, + "ai": { + "bots": "ChatAssistant", + "title": "AI", + "resources": "Resources", + "datacenter": "Datacenter", + "workflow": "Workflow", + "plugin": "Plugin", + "model": "Model", + "documentCollection": "DocumentCollection", + "knowledge": "DocumentManagement", + "mcp": "MCP" + }, + "settings": { + "title": "SettingsConfiguration", + "settingsConfig": "SettingsConfig", + "apiKey": "API Key" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/message.json b/easyflow-ui-admin/app/src/locales/langs/en-US/message.json new file mode 100644 index 0000000..b905403 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/message.json @@ -0,0 +1,49 @@ +{ + "deleteAlert": "Do you want to delete this item?", + "noticeTitle": "Notice", + "ok": "Yes", + "cancel": "No", + "required": "Item is required", + "saveOkMessage": "Saved successfully", + "saveFailMessage": "Save failed", + "deleteOkMessage": "Deleted successfully", + "deleteFailMessage": "Delete failed", + "loading": "Loading...", + "getDataError": "Get data error", + "updateOkMessage": "Updated successfully", + "startAlert": "Are you sure to start?", + "stopAlert": "Are you sure to stop?", + "preview": "Preview", + "notEmpty": "Can not be empty", + "success": "Success", + "fail": "Fail", + "pleaseSelect": "Please Select {name}", + "pleaseInputContent": "Please input content", + "notSupported": "Not supported yet", + "englishNameRule": "Rural subsistence allowance please enter characters consisting of letters, numbers, underscores, and hyphens, with a length not exceeding 64 characters", + "downloadSuccess": "Download success", + "downloadFail": "Download fail", + "copySuccess": "Copy success", + "copyFail": "Copy fail", + "upload": { + "title": "Click or drag and drop files here to upload", + "description": "TXT, PDF, DOCX, MD, PPT, PPTX files are supported. Each upload allows one file only, with a maximum size of 20M per file." + }, + "uploadFileFirst": "Please upload the file first", + "deleteModelAlert": "This operation will delete the large model. Are you sure to delete it?", + "deleteModelGroupAlert": "This operation will delete all large models under this group. Are you sure to delete them?", + "cannotBeEmpty": { + "name": "Parameter name cannot be empty", + "description": "Parameter description cannot be empty", + "method": "Input method cannot be empty", + "type": "Parameter type cannot be empty", + "all": "Please complete all required fields before submitting", + "error": "Parameter validation failed. Please complete all required fields" + }, + "completeForm": "Please complete the form information", + "notVideo": "Your browser does not support the video element.", + "notAudio": "Your browser does not support the audio element.", + "selectTip": "Please select", + "mustBeNumber": "Please enter a valid number", + "confirmItem": "Confirm Item" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/page.json b/easyflow-ui-admin/app/src/locales/langs/en-US/page.json new file mode 100644 index 0000000..fada1d6 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/page.json @@ -0,0 +1,23 @@ +{ + "auth": { + "login": "Login", + "register": "Register", + "codeLogin": "Code Login", + "qrcodeLogin": "Qr Code Login", + "forgetPassword": "Forget Password", + "profile": "Profile", + "accountPassword": "Account Password", + "systemMessage": "System Message", + "todoTasks": "Todo Tasks" + }, + "dashboard": { + "title": "Dashboard", + "analytics": "Analytics", + "workspace": "Workspace" + }, + "description": { + "accountPassword": "Messages from other users will be notified via site message", + "systemMessage": "System messages will be notified via site message", + "todoTasks": "Todo tasks will be notified via site message." + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/plugin.json b/easyflow-ui-admin/app/src/locales/langs/en-US/plugin.json new file mode 100644 index 0000000..a2589e4 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/plugin.json @@ -0,0 +1,33 @@ +{ + "pluginCategory": "Plugin category", + "id": "Id", + "alias": "Alias", + "name": "Name", + "description": "Description", + "type": "Type", + "baseUrl": "BaseUrl", + "authType": "AuthType", + "created": "Created", + "icon": "Icon", + "position": "Position", + "headers": "Headers", + "tokenKey": "TokenKey", + "tokenValue": "TokenValue", + "deptId": "DeptId", + "tenantId": "TenantId", + "createdBy": "CreatedBy", + "category": "Category", + "placeholder": { + "name": "Please enter plugin name", + "description": "Please enter plugin description", + "categorize": "Please enter categorize" + }, + "button": { + "addPlugin": "Add Plugin", + "categorize": "categorize", + "tools": "tools" + }, + "toolsManagement": "Tools Management", + "searchUsers": "Search Users", + "parameterValue": "ParameterValue" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/pluginItem.json b/easyflow-ui-admin/app/src/locales/langs/en-US/pluginItem.json new file mode 100644 index 0000000..54dc215 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/pluginItem.json @@ -0,0 +1,35 @@ +{ + "id": "Id", + "pluginId": "PluginId", + "name": "Tool name", + "description": "Description", + "basePath": "BasePath", + "created": "Created", + "status": "Status", + "inputData": "InputData", + "outputData": "OutputData", + "requestMethod": "RequestMethod", + "serviceStatus": "ServiceStatus", + "debugStatus": "DebugStatus", + "englishName": "EnglishName", + "createPluginTool": "Create tool", + "pluginToolEdit": { + "basicInfo": "Basic Info", + "configureInputParameters": "Configure input parameters", + "configureOutputParameters": "Configure output parameters", + "trialRun": "Trial run", + "toolPath": "Tool path", + "requestMethod": "RequestMethod", + "runResult": "Run result", + "run": "run" + }, + "parameterName": "Name", + "parameterDescription": "Description", + "parameterType": "Type", + "inputMethod": "InputMethod", + "required": "Required", + "defaultValue": "DefaultValue", + "enabledStatus": "EnabledStatus", + "addChildNode": "AddChildNode", + "addParameter": "Add Parameter" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/settingsConfig.json b/easyflow-ui-admin/app/src/locales/langs/en-US/settingsConfig.json new file mode 100644 index 0000000..171e521 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/settingsConfig.json @@ -0,0 +1,10 @@ +{ + "title": "Large Model Configuration", + "modelOfChat": "Chat Model Provider", + "dialogModel": "Chat Model Settings", + "modelName": "Model Name", + "basic": "BasicInformation", + "updatePwd": "UpdatePassword", + "systemAIFunctionSettings": "System AI Function Settings", + "note": "Note: This config only applies to system AI features, not [Chat Assistant]." +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysAccount.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysAccount.json new file mode 100644 index 0000000..e7796dc --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysAccount.json @@ -0,0 +1,27 @@ +{ + "id": "Id", + "deptId": "Dept", + "tenantId": "TenantId", + "loginName": "LoginName", + "password": "Password", + "accountType": "AccountType", + "nickname": "Nickname", + "mobile": "Mobile", + "email": "Email", + "avatar": "Avatar", + "dataScope": "DataScope", + "deptIdList": "DeptIdList", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "roleIds": "Role", + "oldPwd": "OldPassword", + "newPwd": "NewPassword", + "confirmPwd": "ConfirmPassword", + "repeatPwd": "Please confirm your password again", + "notSamePwd": "The two passwords are inconsistent" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json new file mode 100644 index 0000000..19cc1ec --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json @@ -0,0 +1,18 @@ +{ + "id": "Id", + "apiKey": "ApiKey", + "created": "Created", + "status": "Status", + "deptId": "DeptId", + "tenantId": "TenantId", + "expiredAt": "ExpiredAt", + "createdBy": "CreatedBy", + "addApiKey": "addApiKey", + "actions": { + "enable": "Enable", + "disable": "NotDisable", + "failure": "Failure" + }, + "permissions": "AuthInterface", + "addApiKeyNotice": "This operation will generate an API key. Please confirm whether to proceed" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json new file mode 100644 index 0000000..484e442 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKeyResourcePermission.json @@ -0,0 +1,6 @@ +{ + "id": "Id", + "requestInterface": "RequestInterface", + "title": "Title", + "addPermission": "AddRequestInterface" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysAppearance.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysAppearance.json new file mode 100644 index 0000000..bb0dae1 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysAppearance.json @@ -0,0 +1,24 @@ +{ + "Theme and Color Scheme": "Theme and Color Scheme", + "Theme Mode": "Theme Mode", + "Theme Color": "Theme Color", + "Layout & Navigation": "Layout & Navigation", + "Layout Mode": "Layout Mode", + "Interface Display": "Interface Display", + "Page Tabs": "Page Tabs", + "Animation": "Animation", + "Login Page Appearance": "Login Page Appearance", + "Login Page Layout": "Login Page Layout", + "Login Page Image": "Login Page Image", + "OnlyJPG": "Only .jpg format supported", + "Login Page Brand Copy": "Login Page Brand Copy", + "Welcome Title": "Welcome Title", + "Please enter the welcome title": "Please enter the welcome title", + "Welcome Description": "Welcome Description", + "Please enter the welcome description": "Please enter the welcome description", + "Slogan Title": "Slogan Title", + "Please enter the slogan title": "Please enter the slogan title", + "Slogan Description": "Slogan Description", + "Please enter the slogan description": "Please enter the slogan description", + "Thumbnail": "Thumbnail" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysDept.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysDept.json new file mode 100644 index 0000000..9f9cbd4 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysDept.json @@ -0,0 +1,17 @@ +{ + "root": "Root", + "id": "Id", + "tenantId": "TenantId", + "parentId": "Parent", + "ancestors": "Ancestors", + "deptName": "DeptName", + "deptCode": "DeptCode", + "sortNo": "SortNo", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysDict.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysDict.json new file mode 100644 index 0000000..4d39378 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysDict.json @@ -0,0 +1,12 @@ +{ + "id": "Id", + "name": "Name", + "code": "Code", + "description": "Description", + "dictType": "DictType", + "sortNo": "SortNo", + "status": "Status", + "options": "Options", + "created": "Created", + "modified": "Modified" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysFeedback.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysFeedback.json new file mode 100644 index 0000000..7905c58 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysFeedback.json @@ -0,0 +1,20 @@ +{ + "feedbackType": "FeedbackType", + "processingStatus": "ProcessingStatus", + "category": "Category", + "description": "Description", + "contactInformation": "ContactInformation", + "submittedAt": "SubmittedAt", + "functionalFailure": "FunctionalFailure", + "optimizationSuggestions": "OptimizationSuggestions", + "accountIssue": "AccountIssue", + "other": "Other", + "notViewed": "NotViewed", + "viewed": "Viewed", + "processed": "Processed", + "closed/Invalid": "Closed/Invalid", + "markedSuccessfully": "Marked Successfully!", + "basicInformation": "BasicInformation", + "feedbackContent": "FeedbackContent", + "attachments": "Attachments" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysJob.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysJob.json new file mode 100644 index 0000000..35437af --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysJob.json @@ -0,0 +1,23 @@ +{ + "id": "Id", + "deptId": "DeptId", + "tenantId": "TenantId", + "jobName": "JobName", + "jobType": "JobType", + "jobParams": "JobParams", + "cronExpression": "CronExpression", + "allowConcurrent": "AllowConcurrent", + "misfirePolicy": "MisfirePolicy", + "options": "Options", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "workflow": "Workflow", + "beanMethod": "BeanMethod", + "javaMethod": "JavaMethod", + "example": "example" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysJobLog.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysJobLog.json new file mode 100644 index 0000000..521ac9b --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysJobLog.json @@ -0,0 +1,14 @@ +{ + "title": "JobLog", + "id": "Id", + "jobId": "JobId", + "jobName": "JobName", + "jobParams": "JobParams", + "jobResult": "JobResult", + "errorInfo": "ErrorInfo", + "status": "Status", + "startTime": "StartTime", + "endTime": "EndTime", + "created": "Created", + "remark": "Remark" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysLog.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysLog.json new file mode 100644 index 0000000..651d7be --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysLog.json @@ -0,0 +1,14 @@ +{ + "id": "Id", + "accountId": "AccountId", + "actionName": "ActionName", + "actionType": "ActionType", + "actionClass": "ActionClass", + "actionMethod": "ActionMethod", + "actionUrl": "ActionUrl", + "actionIp": "ActionIp", + "actionParams": "ActionParams", + "actionBody": "ActionBody", + "status": "Status", + "created": "Created" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysMenu.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysMenu.json new file mode 100644 index 0000000..acda776 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysMenu.json @@ -0,0 +1,20 @@ +{ +"root":"Top", +"id": "Id", +"parentId": "Parent", +"menuType": "MenuType", +"menuTitle": "MenuTitle", +"menuUrl": "MenuUrl", +"component": "Component", +"menuIcon": "MenuIcon", +"isShow": "IsShow", +"permissionTag": "PermissionTag", +"sortNo": "SortNo", +"status": "Status", +"created": "Created", +"createdBy": "CreatedBy", +"modified": "Modified", +"modifiedBy": "ModifiedBy", +"remark": "Remark", +"isDeleted": "IsDeleted" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysOption.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysOption.json new file mode 100644 index 0000000..614f41d --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysOption.json @@ -0,0 +1,4 @@ +{ + "oauthWxWeb": "WechatLogin", + "oauthDingTalk": "DingTalkLogin" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysPosition.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysPosition.json new file mode 100644 index 0000000..a3d2a53 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysPosition.json @@ -0,0 +1,24 @@ +{ + "id": "Id", + "tenantId": "TenantId", + "deptId": "Dept", + "positionName": "PositionName", + "positionCode": "PositionCode", + "sortNo": "SortNo", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "enable": "Enable", + "disable": "Disable", + "message": { + "title": "Confirm to {actionText} \"{positionName}\"?" + }, + "placeholder": { + "positionName": "Please enter position name", + "positionCode": "Please enter position code" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysRole.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysRole.json new file mode 100644 index 0000000..8759257 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysRole.json @@ -0,0 +1,17 @@ +{ + "id": "Id", + "tenantId": "TenantId", + "roleName": "RoleName", + "roleKey": "RoleKey", + "status": "Status", + "created": "Created", + "createdBy": "CreatedBy", + "modified": "Modified", + "modifiedBy": "ModifiedBy", + "remark": "Remark", + "isDeleted": "IsDeleted", + "menuPermission": "MenuPermission", + "dataPermission": "DataPermission", + "checkStrictlyTrue": "Linked", + "checkStrictlyFalse": "NotLinked" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiResource.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiResource.json new file mode 100644 index 0000000..999dc3e --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiResource.json @@ -0,0 +1,20 @@ +{ + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "resourceType": "素材类型", + "resourceName": "素材名称", + "suffix": "后缀", + "resourceUrl": "素材地址", + "origin": "素材来源", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项", + "isDeleted": "删除标识", + "fileSize": "文件大小", + "categoryId": "分类", + "choose": "选择素材" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflow.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflow.json new file mode 100644 index 0000000..c79fd68 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflow.json @@ -0,0 +1,70 @@ +{ + "id": "ID 主键", + "alias": "别名", + "deptId": "部门ID", + "tenantId": "租户ID", + "title": "名称", + "description": "描述", + "icon": "图标", + "content": "工作流设计的 JSON 内容", + "created": "创建时间", + "createdBy": "创建人", + "modified": "最后修改时间", + "modifiedBy": "最后修改的人", + "englishName": "英文名称", + "status": "在用户中心显示", + "categoryId": "分类", + "params": "执行参数", + "steps": "执行步骤", + "result": "执行结果", + "confirm": "有待确认的内容,请先确认!", + "completed": "流程已执行完毕,请重新发起。", + "fileContentExtraction": "文件内容提取", + "documentAddress": "文档地址", + "parsedText": "解析后的文本", + "resourceSync": "素材同步", + "originUrl": "源地址", + "savedUrl": "保存后的地址", + "saveOptions": "保存选项", + "image": "图片", + "video": "视频", + "audio": "音频", + "document": "文档", + "other": "其他", + "fileGeneration": "文件生成", + "fileSettings": "文件设置", + "fileDownloadURL": "文件下载地址", + "pluginSelect": "插件选择", + "saveData": "保存数据", + "dataToBeSaved": "待保存的数据", + "successInsertedRecords": "成功插入条数", + "dataTable": "数据表", + "queryData": "查询数据", + "queryResult": "查询结果", + "filterConditions": "过滤条件", + "limit": "限制条数", + "sqlQuery": "SQL 查询", + "subProcess": "子流程", + "workflowSelect": "工作流选择", + "bochaSearch": "博查搜索", + "descriptions": { + "fileContentExtraction": "提取 PDF 或者 Word 等文件中的文字内容", + "documentAddress": "文档的url地址", + "parsedText": "解析后的文本内容", + "resourceSync": "下载素材文件并保存到系统素材库", + "originUrl": "文件的源地址", + "resourceType": "请选择素材的类型", + "fileGeneration": "生成 Word、PDF、HTML 等文件供用户下载", + "fileType": "请选择生成的文件类型", + "fileDownloadURL": "生成后的文件地址", + "plugin": "选择定义好的插件", + "saveData": "保存数据到数据中枢", + "dataToBeSaved": "待保存的数据列表", + "dataTable": "请选择数据表", + "queryData": "查询数据中枢的数据", + "sqlQuery": "通过 SQL 查询数据库", + "enterSQL": "请输入SQL语句", + "queryResultJson": "查询结果(json对象)", + "subProcess": "选择定义好的流程" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowCategory.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowCategory.json new file mode 100644 index 0000000..45ce6ba --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowCategory.json @@ -0,0 +1,10 @@ +{ + "id": "主键", + "categoryName": "分类名称", + "sortNo": "排序", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "status": "数据状态" +} \ No newline at end of file diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json new file mode 100644 index 0000000..25d4a6b --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowExecRecord.json @@ -0,0 +1,25 @@ +{ + "id": "主键", + "execKey": "执行标识", + "workflowId": "工作流ID", + "title": "标题", + "description": "描述", + "input": "输入", + "output": "输出", + "workflowJson": "工作流执行时的配置", + "startTime": "开始时间", + "endTime": "结束时间", + "tokens": "消耗总token", + "status": "状态", + "createdKey": "执行人标识[有可能是用户|外部|定时任务等情况]", + "createdBy": "执行人", + "errorInfo": "错误信息", + "moduleName": "执行记录", + "execTime": "耗时", + "status1": "运行中", + "status5": "挂起", + "status10": "错误", + "status20": "成功", + "status21": "失败", + "status22": "取消" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json new file mode 100644 index 0000000..a4d0f12 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/aiWorkflowRecordStep.json @@ -0,0 +1,22 @@ +{ + "id": "主键", + "recordId": "执行记录ID", + "execKey": "执行标识", + "nodeId": "节点ID", + "nodeName": "节点名称", + "input": "输入", + "output": "输出", + "nodeData": "节点信息", + "startTime": "开始时间", + "endTime": "结束时间", + "tokens": "消耗总token", + "status": "状态", + "errorInfo": "错误信息", + "moduleName": "步骤信息", + "execTime": "耗时", + "status1": "运行中", + "status6": "挂起", + "status10": "错误", + "status20": "成功", + "status21": "失败" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/bot.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/bot.json new file mode 100644 index 0000000..58e2bb5 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/bot.json @@ -0,0 +1,27 @@ +{ + "problemPresupposition": "问题预设", + "noPermission": "没有权限", + "llm": "大模型", + "temperature": "温度", + "maxReplyLength": "最大回复长度", + "historyCount": "携带历史条数", + "skill": "技能", + "conversationSettings": "对话设置", + "welcomeMessage": "欢迎语", + "deepThinking": "深度思考", + "enableDeepThinking": "是否启用深度思考", + "publish": "发布", + "postToWeChatOfficialAccount": "发布到微信公众号", + "configured": "已配置", + "notConfigured": "未配置", + "placeholder": { + "welcome": "请输入欢迎语", + "prompt": "你是一个AI助手,请根据用户的问题给出清晰、准确的回答。", + "permission": "你没有配置bot的权限!" + }, + "systemPrompt": "系统提示词", + "aiOptimization": "AI优化", + "weChatOfficialAccountConfiguration": "微信公众号配置", + "aiOptimizedPrompts": "AI优化提示词", + "chatAssistant": "聊天助手" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/button.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/button.json new file mode 100644 index 0000000..cc640fb --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/button.json @@ -0,0 +1,47 @@ +{ + "query": "查询", + "reset": "重置", + "add": "添加", + "edit": "编辑", + "delete": "删除", + "export": "导出", + "import": "导入", + "save": "保存", + "cancel": "取消", + "confirm": "确认", + "addLlm": "新增大模型", + "oneClickAdd": "一键添加", + "newConversation": "新建会话", + "start": "启动", + "stop": "停止", + "log": "日志", + "back": "返回", + "importFile": "导入文件", + "view": "查看", + "download": "下载", + "upload": "上传", + "preview": "预览", + "nextStep": "下一步", + "previousStep": "上一步", + "addLine": "增加行", + "batchImport": "批量导入", + "startImport": "开始导入", + "design": "设计", + "run": "运行", + "runTest": "试运行", + "copy": "复制", + "selectAll": "全选", + "choose": "选择", + "setting": "设置", + "create": "创建", + "update": "更新", + "oneClickOptimization": "一键优化", + "replace": "替换", + "markAsRead": "标记已查看", + "markAsResolved": "标记已处理", + "optimizing": "正在优化中...", + "regenerate": "重新生成", + "hide": "隐藏", + "more": "更多", + "viewSegmentation": "查看分段" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/common.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/common.json new file mode 100644 index 0000000..552d2d9 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/common.json @@ -0,0 +1,25 @@ +{ + "handle": "操作", + "searchPlaceholder": "请输入搜索内容", + "allCategories": "全部", + "history": "历史记录", + "noDataAvailable": "暂无数据", + "isRequired": "不能为空", + "avatar": "头像", + "otherLoginType": "其他登录方式", + "Sun": "周日", + "Mon": "周一", + "Tue": "周二", + "Wed": "周三", + "Thu": "周四", + "Fri": "周五", + "Sat": "周六", + "Second": "秒", + "Min": "分", + "Hour": "时", + "Day": "日", + "Month": "月", + "Week": "周", + "yes": "是", + "no": "否" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/cron.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/cron.json new file mode 100644 index 0000000..de4c1a1 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/cron.json @@ -0,0 +1,18 @@ +{ + "cronExpressionGenerator": "Cron 表达式生成器", + "GenerateResult": "生成结果", + "CronExpression": "Cron 表达式", + "UseThisValue": "使用该值", + "CheckLast5ExecutionTimes": "查看最近5次执行时间", + "Last5ExecutionTimes": "最近5次执行时间", + "ClickGenerate": "点击生成", + "Per": "每", + "NotSpecified": "不指定", + "Cycle": "周期", + "From": "从", + "StartPer": "开始,每", + "ExecuteOnce": "执行一次", + "Rang": "区间", + "To": "至", + "Specify": "指定" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/cropper.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/cropper.json new file mode 100644 index 0000000..af8ccbc --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/cropper.json @@ -0,0 +1,20 @@ +{ + "ImageCropping": "图片裁剪", + "message": { + "onlyImage": "只能上传图片文件!", + "imgSize": "图片大小不能超过 {limit}MB!", + "uploadFailed": "上传失败", + "notUrl": "上传成功但未返回图片URL", + "uploadSuccessful": "上传成功!", + "reuploadSuccessful": "重新上传成功!", + "notInitialized": "裁剪器未初始化", + "cropFailed": "裁剪失败,无法获取裁剪后的图片", + "fileCount": "最多只能上传 {count} 个文件", + "avatarFormat": "头像只能是{format}格式", + "avatarSize": "头像限制 {limit} MB" + }, + "Uploading": "上传中...", + "ConfirmCrop": "确认裁剪", + "Re-upload": "重新上传", + "ClickToUpload": "点击上传" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTable.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTable.json new file mode 100644 index 0000000..4cbc381 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTable.json @@ -0,0 +1,33 @@ +{ + "title": "数据中枢", + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "tableName": "数据表名", + "tableDesc": "数据表描述", + "actualTable": "物理表名", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项", + "fields": "字段", + "fieldName": "字段名", + "fieldDesc": "字段描述", + "fieldType": "字段类型", + "required": "是否必填", + "noFieldError": "至少包含一个字段", + "fieldInfoError": "字段信息不完善", + "nameRegx": "只能包含小写字母、数字和下划线,且必须以小写字母开头", + "structure": "表结构", + "data": "数据", + "uploadTitle": "点击或将文件拖拽到这里上传", + "uploadDesc": "上传一份Excel文档,文件大小限制10MB以内。", + "downloadTemplate": "下载模板", + "importComplete": "导入完成", + "totalNum": "总数", + "successNum": "成功数", + "failNum": "失败数", + "failList": "失败记录" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTableFields.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTableFields.json new file mode 100644 index 0000000..55abd14 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/datacenterTableFields.json @@ -0,0 +1,13 @@ +{ + "id": "主键", + "tableId": "数据表ID", + "fieldName": "字段名称", + "fieldDesc": "字段描述", + "fieldType": "字段类型", + "required": "是否必填", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "options": "扩展项" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/demos.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/demos.json new file mode 100644 index 0000000..8f03756 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/demos.json @@ -0,0 +1,14 @@ +{ + "title": "演示", + "elementPlus": "Element Plus", + "form": "表单演示", + "easyflow": { + "title": "项目", + "about": "关于", + "document": "文档", + "antdv": "Ant Design Vue 版本", + "naive-ui": "Naive UI 版本", + "element-plus": "Element Plus 版本", + "tdesign": "TDesign Vue 版本" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/dictSelect.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/dictSelect.json new file mode 100644 index 0000000..16798be --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/dictSelect.json @@ -0,0 +1,4 @@ +{ + "placeholder": "请选择", + "getError": "获取字典数据失败" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json new file mode 100644 index 0000000..ce9eb80 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json @@ -0,0 +1,88 @@ +{ + "id": "Id", + "alias": "别名", + "deptId": "部门ID", + "tenantId": "租户ID", + "icon": "ICON", + "title": "名称", + "categoryId": "分类", + "description": "描述", + "slug": "URL 别名", + "vectorStoreEnable": "是否启用向量数据库", + "vectorStoreType": "向量数据库类型", + "vectorStoreCollection": "向量数据库集合", + "vectorStoreConfig": "向量数据库配置", + "vectorEmbedLlmId": "向量模型", + "created": "创建时间", + "createdBy": "创建用户ID", + "modified": "最后一次修改时间", + "modifiedBy": "最后一次修改用户ID", + "options": "其他配置", + "rerankLlmId": "重排模型", + "searchEngineEnable": "是否启用搜索引擎", + "englishName": "英文名称", + "documentType": "文件类型", + "fileName": "文件名", + "knowledgeCount": "知识条数", + "createdModifyTime": "创建/更新时间", + "documentList": "文档列表", + "knowledgeRetrieval": "知识检索", + "config": "配置", + "sorting": "排序", + "content": "内容", + "placeholder": { + "title": "请输入名称", + "description": "请输入描述,以便大模型更好的理解该知识库并且调用", + "englishName": "请输入英文名称", + "alias": "请输入别名,不允许含中文", + "embedLlm": "请选择向量模型", + "rerankLlm": "请选择重排模型", + "vectorStoreCollection": "只能包含字母、数字和下划线且长度在3-20个字符之间", + "vectorStoreType": "请选择向量数据库类型" + }, + "importDoc": { + "fileUpload": "文件上传", + "parameterSettings": "参数设置", + "segmentedPreview": "分段预览", + "confirmImport": "确认导入", + "fileName": "文件名称", + "progressUpload": "文件上传进度", + "fileSize": "文件大小" + }, + "splitterDoc": { + "fileType": "文件类型", + "splitterName": "分割器", + "chunkSize": "分段长度", + "overlapSize": "分段重叠", + "regex": "正则表达式", + "document": "文档", + "simpleDocumentSplitter": "简单文档分割器", + "simpleTokenizeSplitter": "简单分词器", + "regexDocumentSplitter": "正则文档分割器", + "markdownHeaderSplitter": "Markdown标题层级拆分器", + "mdSplitterLevel": "Markdown标题等级", + "uploadStatus": "上传状态", + "pendingUpload": "待上传", + "completed": "已完成", + "uploading": "解析中", + "importSuccess": "导入成功" + }, + "documentManagement": "文档管理", + "actions": { + "knowledge": "知识", + "retrieve": "检索", + "addKnowledge": "新增知识库", + "confirmImport": "确认导入", + "cancelImport": "取消导入" + }, + "searchResults": "检索结果", + "documentPreview": "文档预览", + "total": "共", + "segments": "个分段", + "similarityScore": "相似度", + "alibabaCloud": "阿里云", + "tencentCloud": "腾讯云", + "vectorEmbedModelTips": "成功向量数据之后不允许修改向量模型", + "dimensionOfVectorModelTips": "成功向量数据之后不允许修改向量模型维度", + "dimensionOfVectorModel": "向量模型维度" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollectionSearch.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollectionSearch.json new file mode 100644 index 0000000..bde9391 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollectionSearch.json @@ -0,0 +1,41 @@ +{ + "title": "知识检索配置", + "docRecallMaxNum": { + "label": "召回最大条数", + "tooltip": "检索时返回的最大文档数量,数值越大召回越全但速度越慢" + }, + "simThreshold": { + "label": "相似度最小值", + "tooltip": "值越大筛选越严格,仅返回相似度更高的文档,范围0-1,建议设置0.6-0.8" + }, + "button": { + "save": "保存配置" + }, + "message": { + "saveSuccess": "配置保存成功", + "saveFailed": "配置保存失败" + }, + "placeholder": { + "count": "1-50" + }, + "switch": { + "on": "开", + "off": "关" + }, + "unit": { + "count": "条" + }, + "searchEngineType": { + "label": "搜索引擎类型", + "placeholder": "请选择搜索引擎类型", + "tooltip": "选择检索文档的引擎类型:关键词检索(快速)", + "vector": "向量检索", + "hybrid": "混合检索", + "keyword": "关键词检索" + }, + "searchEngineEnable": { + "label": "是否启用搜索引擎", + "tooltip": "是否启用搜索引擎,开启后,将使用搜索引擎进行文档检索" + }, + "vectorEmbedModelTips": "After successful vector data, it is not allowed to modify the vector model" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/headerSearch.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/headerSearch.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/headerSearch.json @@ -0,0 +1 @@ +{} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/llm.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/llm.json new file mode 100644 index 0000000..fe1febd --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/llm.json @@ -0,0 +1,78 @@ +{ + "filed": + { + "title": "标题", + "brand": "供应商", + "llmModel": "大模型名称", + "icon": "ICON", + "description": "描述", + "supportChat": "对话模型", + "supportFunctionCalling": "方法调用", + "supportEmbed": "向量化", + "supportReranker": "重排", + "supportTextToImage": "文生图", + "supportImageToImage": "图生图", + "supportTextToAudio": "文生音频", + "supportAudioToAudio": "音频转音频", + "supportTextToVideo": "文生视频", + "supportImageToVideo": "图生成视频", + "llmEndpoint": "请求地址", + "chatPath": "对话路径", + "embedPath": "向量化路径", + "multimodal": "多模态", + "llmApiKey": "apiKey", + "llmExtraConfig": "大模型其他属性配置", + "options": "其他配置内容", + "ability": "能力" + } +, + "llmModal": { + "TitleRequired": "请输入名称", + "BrandRequired": "请选择供应商", + "ModelRequired": "请输入大模型", + "ApiKeyRequired": "请输入apiKey", + "QuickAddLlm": "一键添加大模型" + }, + "placeholder": { + "title": "请输入名称", + "brand": "请选择品牌", + "llmModel": "请输入大模型名称", + "description": "请输入描述" + }, + "actions": { + "verifyConfiguration": "验证配置" + }, + "message": { + "verifySuccess": "验证成功" + }, + "addProvider": "供应商列表", + "modelType": "模型类型", + "llmModel": "模型名称", + "title": "名称", + "groupName": "分组名称", + "provider": "供应商", + "ability": "模型能力", + "button": { + "management": "管理", + "test": "检测", + "addAllLlm": "添加列表中的所有模型", + "RetrieveAgain": "重新获取模型列表" + }, + "all": "全部", + "verifyLlmTitle": "请选择要检测的模型", + "testSuccess": "检测成功", + "searchTextPlaceholder": "搜索模型名称或名称", + "modelAbility": { + "supportThinking": "推理", + "supportTool": "工具", + "supportAudio": "音频", + "supportVideo": "视频", + "supportImage": "图片", + "supportFree": "免费", + "supportImageB64Only": "仅支持Base64图片", + "supportToolMessage": "支持Tool消息" + }, + "requestPath": "请求路径", + "modelToBeTested": "待检测模型" + +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/llmProvider.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/llmProvider.json new file mode 100644 index 0000000..5bdc6f8 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/llmProvider.json @@ -0,0 +1,15 @@ +{ + "providerName": "供应商名称", + "provider": "供应商", + "icon": "Icon", + "apiKey": "API 密钥", + "endpoint": "API 地址", + "embedPath": "向量地址", + "chatPath": "对话地址", + "rerankPath": "重排地址", + "embeddingModel": "向量模型", + "chatModel": "对话模型", + "rerankModel": "重排模型", + "model": "模型", + "apiType": "API类型" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/mcp.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/mcp.json new file mode 100644 index 0000000..b51f486 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/mcp.json @@ -0,0 +1,31 @@ +{ + "id": "id", + "title": "名称", + "description": "描述", + "configJson": "MCP配置JSON", + "deptId": "部门ID", + "tenantId": "租户ID", + "created": "创建时间", + "createdBy": "创建者ID", + "modified": "修改时间", + "modifiedBy": "修改者ID", + "status": "是否启用", + "modal": { + "config": "配置", + "tool": "工具", + "table": { + "availableTools": "可用工具" + } + }, + "example": "示例:", + "restartMcpServer": "重启MCP服务", + "message": { + "startupFail": "启动失败", + "startupSuccessful": "启动成功", + "stopSuccessful": "关闭成功" + }, + "labels": { + "clientOnline": "客户端在线", + "clientOffline": "客户端离线" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json new file mode 100644 index 0000000..877cd7b --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json @@ -0,0 +1,33 @@ +{ + "system": { + "title": "系统管理", + "sysAccount": "用户管理", + "sysRole": "角色管理", + "sysMenu": "菜单管理", + "sysDept": "部门管理", + "sysPosition": "岗位管理", + "sysDict": "字典管理", + "sysJob": "定时任务", + "sysLog": "日志管理", + "sysFeedback": "用户反馈", + "sysAppearance": "外观设置", + "oauth": "认证设置" + }, + "ai": { + "bots": "聊天助手", + "title": "AI能力", + "resources": "素材库", + "datacenter": "数据中枢", + "workflow": "工作流", + "plugin": "插件", + "model": "模型管理", + "documentCollection": "知识库", + "knowledge": "知识管理", + "mcp": "MCP" + }, + "settings": { + "title": "系统配置", + "settingsConfig": "系统设置", + "apiKey": "访问令牌" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/message.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/message.json new file mode 100644 index 0000000..68b8492 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/message.json @@ -0,0 +1,49 @@ +{ + "deleteAlert": "确定删除吗?", + "noticeTitle": "提示", + "ok": "确定", + "cancel": "取消", + "required": "该项为必填项", + "saveOkMessage": "保存成功!", + "saveFailMessage": "保存失败!", + "deleteOkMessage": "删除成功!", + "deleteFailMessage": "删除失败!", + "loading": "加载中...", + "getDataError": "获取数据失败", + "updateOkMessage": "更新成功!", + "startAlert": "确定启动吗?", + "stopAlert": "确定停止吗?", + "preview": "预览", + "notEmpty": "不能为空", + "success": "成功", + "fail": "失败", + "pleaseSelect": "请选择{name}", + "pleaseInputContent": "请输入内容", + "notSupported": "暂不支持", + "englishNameRule": "请输入由字母、数字、下划线、连字符组成的字符,且长度不超过64位", + "downloadSuccess": "下载成功", + "downloadFail": "下载失败", + "copySuccess": "复制成功", + "copyFail": "复制失败", + "upload": { + "title": "点击或将文件拖拽到这里上传", + "description": "TXT, PDF, DOCX, MD, PPT, PPTX 格式文件,单个大小不超过20M。" + }, + "uploadFileFirst": "请先上传文件", + "deleteModelAlert": "该操作会删除大模型,确定删除吗?", + "deleteModelGroupAlert": "该操作会删除该分组下所有大模型,确定删除吗?", + "cannotBeEmpty": { + "name": "参数名称不能为空", + "description": "参数描述不能为空", + "method": "传入方法不能为空", + "type": "参数类型不能为空", + "all": "请完善所有必填项后提交", + "error": "参数校验失败,请完善必填项" + }, + "completeForm": "请完善表单信息", + "notVideo": "您的浏览器不支持 video 元素。", + "notAudio": "您的浏览器不支持 audio 元素。", + "selectTip": "请选择数据", + "mustBeNumber": "请输入有效的数字", + "confirmItem": "待确认项" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/page.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/page.json new file mode 100644 index 0000000..13fe95e --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/page.json @@ -0,0 +1,23 @@ +{ + "auth": { + "login": "登录", + "register": "注册", + "codeLogin": "验证码登录", + "qrcodeLogin": "二维码登录", + "forgetPassword": "忘记密码", + "profile": "个人资料", + "accountPassword": "账户密码", + "systemMessage": "系统消息", + "todoTasks": "待办任务" + }, + "dashboard": { + "title": "概览", + "analytics": "分析页", + "workspace": "工作台" + }, + "description": { + "accountPassword": "其他用户的消息将以站内信的形式通知", + "systemMessage": "系统消息将以站内信的形式通知", + "todoTasks": "待办任务将以站内信的形式通知" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/plugin.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/plugin.json new file mode 100644 index 0000000..a0444b2 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/plugin.json @@ -0,0 +1,33 @@ +{ + "pluginCategory": "插件分类", + "id": "插件id", + "alias": "别名", + "name": "名称", + "description": "描述", + "type": "类型", + "baseUrl": "基础URL", + "authType": "认证方式", + "created": "创建时间", + "icon": "图标地址", + "position": "认证参数位置", + "headers": "请求头", + "tokenKey": "token键", + "tokenValue": "token值", + "deptId": "部门id", + "tenantId": "租户id", + "createdBy": "创建人", + "category": "分类", + "placeholder": { + "name": "请输入插件名称", + "description": "请输入插件描述", + "categorize": "请选择分类" + }, + "button": { + "addPlugin": "新增插件", + "categorize": "归类", + "tools": "工具" + }, + "toolsManagement": "工具管理", + "searchUsers": "搜索用户", + "parameterValue": "参数值" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/pluginItem.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/pluginItem.json new file mode 100644 index 0000000..781a3c9 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/pluginItem.json @@ -0,0 +1,35 @@ +{ + "id": "插件工具id", + "pluginId": "插件id", + "name": "工具名称", + "description": "工具描述", + "basePath": "基础路径", + "created": "创建时间", + "status": "是否启用", + "inputData": "输入参数", + "outputData": "输出参数", + "requestMethod": "请求方式【Post, Get, Put, Delete】", + "serviceStatus": "服务状态[0 下线 1 上线]", + "debugStatus": "调试状态【0失败 1成功】", + "englishName": "英文名称", + "createPluginTool": "创建工具", + "pluginToolEdit": { + "basicInfo": "基本信息", + "configureInputParameters": "配置输入参数", + "configureOutputParameters": "配置输出参数", + "trialRun": "试运行", + "toolPath": "工具路径", + "requestMethod": "请求方法", + "runResult": "运行结果", + "run": "运行" + }, + "parameterName": "参数名称", + "parameterDescription": "参数描述", + "parameterType": "参数类型", + "inputMethod": "传入方法", + "required": "是否必填", + "defaultValue": "默认值", + "enabledStatus": "启用状态", + "addChildNode": "添加子节点", + "addParameter": "新增参数" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/settingsConfig.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/settingsConfig.json new file mode 100644 index 0000000..b9ec6c4 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/settingsConfig.json @@ -0,0 +1,10 @@ +{ + "title": "大模型配置", + "modelOfChat": "对话模型供应商", + "dialogModel": "对话模型配置", + "modelName": "模型名称", + "basic": "基本设置", + "updatePwd": "修改密码", + "systemAIFunctionSettings": "系统 AI 功能设置", + "note": "注意:此项配置,仅用于系统的 AI 功能,而非【聊天助手】。" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAccount.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAccount.json new file mode 100644 index 0000000..a55262a --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAccount.json @@ -0,0 +1,28 @@ +{ + "id": "主键", + "deptId": "部门", + "tenantId": "租户ID", + "loginName": "登录账号", + "password": "密码", + "accountType": "账户类型", + "nickname": "昵称", + "mobile": "手机电话", + "email": "邮件", + "avatar": "账户头像", + "dataScope": "数据权限类型", + "deptIdList": "自定义部门权限", + "status": "是否启用", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "roleIds": "角色", + "positionIds": "岗位", + "oldPwd": "旧密码", + "newPwd": "新密码", + "confirmPwd": "确认密码", + "repeatPwd": "请再次输入密码", + "notSamePwd": "两次输入的密码不一致" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json new file mode 100644 index 0000000..dac3fc0 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json @@ -0,0 +1,18 @@ +{ + "id": "id", + "apiKey": "apiKey", + "created": "创建时间", + "status": "数据状态", + "deptId": "部门id", + "tenantId": "租户id", + "expiredAt": "失效时间", + "createdBy": "创建人", + "addApiKey": "新增apiKey", + "actions": { + "enable": "启用", + "disable": "未启用", + "failure": "已失效" + }, + "permissions": "授权接口", + "addApiKeyNotice": "该操作会生成一个apiKey,请确认是否生成" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json new file mode 100644 index 0000000..51e9183 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKeyResourcePermission.json @@ -0,0 +1,6 @@ +{ + "id": "id", + "requestInterface": "请求接口", + "title": "标题", + "addPermission": "添加请求接口" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAppearance.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAppearance.json new file mode 100644 index 0000000..e6ed991 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysAppearance.json @@ -0,0 +1,24 @@ +{ + "Theme and Color Scheme": "主题与配色", + "Theme Mode": "主题模式", + "Theme Color": "主题色", + "Layout & Navigation": "布局与导航", + "Layout Mode": "布局模式", + "Interface Display": "界面显示", + "Page Tabs": "页面标签页", + "Animation": "动画", + "Login Page Appearance": "登录页外观", + "Login Page Layout": "登录页布局", + "Login Page Image": "登录页图片", + "OnlyJPG": "只支持.jpg 格式", + "Login Page Brand Copy": "登录页品牌文案", + "Welcome Title": "欢迎语标题", + "Please enter the welcome title": "请填写欢迎语标题", + "Welcome Description": "欢迎语描述", + "Please enter the welcome description": "请填写欢迎语描述", + "Slogan Title": "Slogn标题", + "Please enter the slogan title": "请填写Slogn标题", + "Slogan Description": "Slogn描述", + "Please enter the slogan description": "请填写Slogn描述", + "Thumbnail": "缩略图" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDept.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDept.json new file mode 100644 index 0000000..e1f5130 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDept.json @@ -0,0 +1,17 @@ +{ + "root": "根部门", + "id": "主键", + "tenantId": "租户ID", + "parentId": "父级", + "ancestors": "父级部门ID集合", + "deptName": "部门名称", + "deptCode": "部门编码", + "sortNo": "排序", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDict.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDict.json new file mode 100644 index 0000000..f9db052 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysDict.json @@ -0,0 +1,12 @@ +{ + "id": "主键", + "name": "数据字典名称", + "code": "字典编码", + "description": "字典描述或备注", + "dictType": "字典类型 1 自定义字典、2 数据表字典、 3 枚举类字典、 4 系统字典(自定义 DictLoader)", + "sortNo": "排序编号", + "status": "是否启用", + "options": "扩展字典 存放 json", + "created": "创建时间", + "modified": "修改时间" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysFeedback.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysFeedback.json new file mode 100644 index 0000000..67a95a7 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysFeedback.json @@ -0,0 +1,20 @@ +{ + "feedbackType": "反馈类型", + "processingStatus": "处理状态", + "category": "问题类型", + "description": "问题摘要", + "contactInformation": "联系方式", + "submittedAt": "提交时间", + "functionalFailure": "功能故障", + "optimizationSuggestions": "优化建议", + "accountIssue": "账号问题", + "other": "其它", + "notViewed": "未查看", + "viewed": "已查看", + "processed": "已处理", + "closed/Invalid": "已关闭/无效", + "markedSuccessfully": "标记成功!", + "basicInformation": "基础信息", + "feedbackContent": "反馈内容", + "attachments": "附件" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJob.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJob.json new file mode 100644 index 0000000..9813018 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJob.json @@ -0,0 +1,23 @@ +{ + "id": "主键", + "deptId": "部门ID", + "tenantId": "租户ID", + "jobName": "任务名称", + "jobType": "任务类型", + "jobParams": "任务参数", + "cronExpression": "cron表达式", + "allowConcurrent": "是否并发执行", + "misfirePolicy": "错过策略", + "options": "其他配置", + "status": "任务状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "workflow": "工作流", + "beanMethod": "bean方法", + "javaMethod": "java方法", + "example": "示例" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJobLog.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJobLog.json new file mode 100644 index 0000000..32dabc1 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysJobLog.json @@ -0,0 +1,14 @@ +{ + "title": "任务日志", + "id": "主键", + "jobId": "任务ID", + "jobName": "任务名称", + "jobParams": "任务参数", + "jobResult": "执行结果", + "errorInfo": "错误信息", + "status": "执行状态", + "startTime": "开始时间", + "endTime": "结束时间", + "created": "创建时间", + "remark": "备注" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysLog.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysLog.json new file mode 100644 index 0000000..82fd4fe --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysLog.json @@ -0,0 +1,14 @@ +{ + "id": "ID", + "accountId": "操作人", + "actionName": "操作名称", + "actionType": "操作的类型", + "actionClass": "操作涉及的类", + "actionMethod": "操作涉及的方法", + "actionUrl": "操作涉及的 URL 地址", + "actionIp": "操作涉及的用户 IP 地址", + "actionParams": "操作请求参数", + "actionBody": "操作请求body", + "status": "操作状态 1 成功 9 失败", + "created": "操作时间" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysMenu.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysMenu.json new file mode 100644 index 0000000..6aee2d5 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysMenu.json @@ -0,0 +1,20 @@ +{ +"root":"顶级", +"id": "主键", +"parentId": "父菜单", +"menuType": "菜单类型", +"menuTitle": "菜单标题", +"menuUrl": "菜单url", +"component": "组件路径", +"menuIcon": "图标", +"isShow": "是否显示", +"permissionTag": "权限标识", +"sortNo": "排序", +"status": "数据状态", +"created": "创建时间", +"createdBy": "创建者", +"modified": "修改时间", +"modifiedBy": "修改者", +"remark": "备注", +"isDeleted": "删除标识" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysOption.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysOption.json new file mode 100644 index 0000000..0ac65bf --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysOption.json @@ -0,0 +1,4 @@ +{ + "oauthWxWeb": "微信登录", + "oauthDingTalk": "钉钉登录" +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysPosition.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysPosition.json new file mode 100644 index 0000000..20c6c18 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysPosition.json @@ -0,0 +1,24 @@ +{ + "id": "主键", + "tenantId": "租户ID", + "deptId": "部门", + "positionName": "岗位名称", + "positionCode": "岗位编码", + "sortNo": "排序", + "status": "数据状态", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "enable": "启用", + "disable": "禁用", + "message": { + "title": "确认要{actionText}\"{positionName}\"吗?" + }, + "placeholder": { + "positionName": "请输入岗位名称", + "positionCode": "请输入岗位编码" + } +} diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysRole.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysRole.json new file mode 100644 index 0000000..0b77250 --- /dev/null +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysRole.json @@ -0,0 +1,17 @@ +{ + "id": "主键", + "tenantId": "租户ID", + "roleName": "角色名称", + "roleKey": "角色标识", + "status": "是否启用", + "created": "创建时间", + "createdBy": "创建者", + "modified": "修改时间", + "modifiedBy": "修改者", + "remark": "备注", + "isDeleted": "删除标识", + "menuPermission": "菜单权限", + "dataPermission": "数据权限", + "checkStrictlyTrue": "联动", + "checkStrictlyFalse": "不联动" +} diff --git a/easyflow-ui-admin/app/src/main.ts b/easyflow-ui-admin/app/src/main.ts new file mode 100644 index 0000000..8256c7e --- /dev/null +++ b/easyflow-ui-admin/app/src/main.ts @@ -0,0 +1,31 @@ +import { initPreferences } from '@easyflow/preferences'; +import { unmountGlobalLoading } from '@easyflow/utils'; + +import { overridesPreferences } from './preferences'; + +/** + * 应用初始化完成之后再进行页面加载渲染 + */ +async function initApplication() { + // name用于指定项目唯一标识 + // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 + const env = import.meta.env.PROD ? 'prod' : 'dev'; + const appVersion = import.meta.env.VITE_APP_VERSION; + const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; + + // app偏好设置初始化 + await initPreferences({ + namespace, + overrides: overridesPreferences, + }); + + // 启动应用并挂载 + // vue应用主要逻辑及视图 + const { bootstrap } = await import('./bootstrap'); + await bootstrap(namespace); + + // 移除并销毁loading + unmountGlobalLoading(); +} + +initApplication(); diff --git a/easyflow-ui-admin/app/src/preferences.ts b/easyflow-ui-admin/app/src/preferences.ts new file mode 100644 index 0000000..dc1f1f6 --- /dev/null +++ b/easyflow-ui-admin/app/src/preferences.ts @@ -0,0 +1,19 @@ +import { defineOverridesPreferences } from '@easyflow/preferences'; + +/** + * @description 项目配置文件 + * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 + * !!! 更改配置后请清空缓存,否则可能不生效 + */ +export const overridesPreferences = defineOverridesPreferences({ + // overrides + app: { + name: import.meta.env.VITE_APP_TITLE, + accessMode: 'mixed', + }, + transition: { + enable: false, + loading: false, + progress: false, + }, +}); diff --git a/easyflow-ui-admin/app/src/router/access.ts b/easyflow-ui-admin/app/src/router/access.ts new file mode 100644 index 0000000..a8ddc83 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/access.ts @@ -0,0 +1,42 @@ +import type { + ComponentRecordType, + GenerateMenuAndRoutesOptions, +} from '@easyflow/types'; + +import { generateAccessible } from '@easyflow/access'; +import { preferences } from '@easyflow/preferences'; + +import { ElMessage } from 'element-plus'; + +import { getAllMenusApi } from '#/api'; +import { BasicLayout, IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); + +async function generateAccess(options: GenerateMenuAndRoutesOptions) { + const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); + + const layoutMap: ComponentRecordType = { + BasicLayout, + IFrameView, + }; + + return await generateAccessible(preferences.app.accessMode, { + ...options, + fetchMenuListAsync: async () => { + ElMessage({ + duration: 1500, + message: `${$t('common.loadingMenu')}...`, + }); + return await getAllMenusApi(); + }, + // 可以指定没有权限跳转403页面 + forbiddenComponent, + // 如果 route.meta.menuVisibleWithForbidden = true + layoutMap, + pageMap, + }); +} + +export { generateAccess }; diff --git a/easyflow-ui-admin/app/src/router/guard.ts b/easyflow-ui-admin/app/src/router/guard.ts new file mode 100644 index 0000000..971c92e --- /dev/null +++ b/easyflow-ui-admin/app/src/router/guard.ts @@ -0,0 +1,133 @@ +import type { Router } from 'vue-router'; + +import { LOGIN_PATH } from '@easyflow/constants'; +import { preferences } from '@easyflow/preferences'; +import { useAccessStore, useUserStore } from '@easyflow/stores'; +import { startProgress, stopProgress } from '@easyflow/utils'; + +import { accessRoutes, coreRouteNames } from '#/router/routes'; +import { useAuthStore } from '#/store'; + +import { generateAccess } from './access'; + +/** + * 通用守卫配置 + * @param router + */ +function setupCommonGuard(router: Router) { + // 记录已经加载的页面 + const loadedPaths = new Set(); + + router.beforeEach((to) => { + to.meta.loaded = loadedPaths.has(to.path); + + // 页面加载进度条 + if (!to.meta.loaded && preferences.transition.progress) { + startProgress(); + } + return true; + }); + + router.afterEach((to) => { + // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 + + loadedPaths.add(to.path); + + // 关闭页面加载进度条 + if (preferences.transition.progress) { + stopProgress(); + } + }); +} + +/** + * 权限访问守卫配置 + * @param router + */ +function setupAccessGuard(router: Router) { + router.beforeEach(async (to, from) => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const authStore = useAuthStore(); + + // 基本路由,这些路由不需要进入权限拦截 + if (coreRouteNames.includes(to.name as string)) { + if (to.path === LOGIN_PATH && accessStore.accessToken) { + return decodeURIComponent( + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + preferences.app.defaultHomePath, + ); + } + return true; + } + + // accessToken 检查 + if (!accessStore.accessToken) { + // 明确声明忽略权限访问权限,则可以访问 + if (to.meta.ignoreAccess) { + return true; + } + + // 没有访问权限,跳转登录页面 + if (to.fullPath !== LOGIN_PATH) { + return { + path: LOGIN_PATH, + // 如不需要,直接删除 query + query: + to.fullPath === preferences.app.defaultHomePath + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, + // 携带当前跳转的页面,登录后重新跳转该页面 + replace: true, + }; + } + return to; + } + + // 是否已经生成过动态路由 + if (accessStore.isAccessChecked) { + return true; + } + + // 生成路由表 + // 当前登录用户拥有的角色标识列表 + const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); + const userRoles = userInfo.roles ?? []; + + // 生成菜单和路由 + const { accessibleMenus, accessibleRoutes } = await generateAccess({ + roles: userRoles, + router, + // 则会在菜单中显示,但是访问会被重定向到403 + routes: accessRoutes, + }); + + // 保存菜单信息和路由信息 + accessStore.setAccessMenus(accessibleMenus); + accessStore.setAccessRoutes(accessibleRoutes); + accessStore.setIsAccessChecked(true); + const redirectPath = (from.query.redirect ?? + (to.path === preferences.app.defaultHomePath + ? userInfo.homePath || preferences.app.defaultHomePath + : to.fullPath)) as string; + + return { + ...router.resolve(decodeURIComponent(redirectPath)), + replace: true, + }; + }); +} + +/** + * 项目守卫配置 + * @param router + */ +function createRouterGuard(router: Router) { + /** 通用 */ + setupCommonGuard(router); + /** 权限访问 */ + setupAccessGuard(router); +} + +export { createRouterGuard }; diff --git a/easyflow-ui-admin/app/src/router/index.ts b/easyflow-ui-admin/app/src/router/index.ts new file mode 100644 index 0000000..cf8413b --- /dev/null +++ b/easyflow-ui-admin/app/src/router/index.ts @@ -0,0 +1,37 @@ +import { + createRouter, + createWebHashHistory, + createWebHistory, +} from 'vue-router'; + +import { resetStaticRoutes } from '@easyflow/utils'; + +import { createRouterGuard } from './guard'; +import { routes } from './routes'; + +/** + * @zh_CN 创建vue-router实例 + */ +const router = createRouter({ + history: + import.meta.env.VITE_ROUTER_HISTORY === 'hash' + ? createWebHashHistory(import.meta.env.VITE_BASE) + : createWebHistory(import.meta.env.VITE_BASE), + // 应该添加到路由的初始路由列表。 + routes, + scrollBehavior: (to, _from, savedPosition) => { + if (savedPosition) { + return savedPosition; + } + return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; + }, + // 是否应该禁止尾部斜杠。 + // strict: true, +}); + +const resetRoutes = () => resetStaticRoutes(router, routes); + +// 创建路由守卫 +createRouterGuard(router); + +export { resetRoutes, router }; diff --git a/easyflow-ui-admin/app/src/router/routes/core.ts b/easyflow-ui-admin/app/src/router/routes/core.ts new file mode 100644 index 0000000..ad5bde9 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/core.ts @@ -0,0 +1,108 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { LOGIN_PATH } from '@easyflow/constants'; +import { preferences } from '@easyflow/preferences'; + +import { $t } from '#/locales'; + +const BasicLayout = () => import('#/layouts/basic.vue'); +const AuthPageLayout = () => import('#/layouts/auth.vue'); +/** 全局404页面 */ +const fallbackNotFoundRoute: RouteRecordRaw = { + component: () => import('#/views/_core/fallback/not-found.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: '404', + }, + name: 'FallbackNotFound', + path: '/:path(.*)*', +}; + +/** 基本路由,这些路由是必须存在的 */ +const coreRoutes: RouteRecordRaw[] = [ + { + component: () => import('#/views/_core/authentication/oauth-page.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: 'OAuth', + }, + name: 'OAuth', + path: '/oauth', + }, + /** + * 根路由 + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 + * 此路由必须存在,且不应修改 + */ + { + component: BasicLayout, + meta: { + hideInBreadcrumb: true, + title: 'Root', + }, + name: 'Root', + path: '/', + redirect: preferences.app.defaultHomePath, + children: [], + }, + { + component: AuthPageLayout, + meta: { + hideInTab: true, + title: 'Authentication', + }, + name: 'Authentication', + path: '/auth', + redirect: LOGIN_PATH, + children: [ + { + name: 'Login', + path: 'login', + component: () => import('#/views/_core/authentication/login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, + { + name: 'CodeLogin', + path: 'code-login', + component: () => import('#/views/_core/authentication/code-login.vue'), + meta: { + title: $t('page.auth.codeLogin'), + }, + }, + { + name: 'QrCodeLogin', + path: 'qrcode-login', + component: () => + import('#/views/_core/authentication/qrcode-login.vue'), + meta: { + title: $t('page.auth.qrcodeLogin'), + }, + }, + { + name: 'ForgetPassword', + path: 'forget-password', + component: () => + import('#/views/_core/authentication/forget-password.vue'), + meta: { + title: $t('page.auth.forgetPassword'), + }, + }, + { + name: 'Register', + path: 'register', + component: () => import('#/views/_core/authentication/register.vue'), + meta: { + title: $t('page.auth.register'), + }, + }, + ], + }, +]; + +export { coreRoutes, fallbackNotFoundRoute }; diff --git a/easyflow-ui-admin/app/src/router/routes/index.ts b/easyflow-ui-admin/app/src/router/routes/index.ts new file mode 100644 index 0000000..a1cc113 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/index.ts @@ -0,0 +1,47 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { mergeRouteModules, traverseTreeValues } from '@easyflow/utils'; + +import { coreRoutes, fallbackNotFoundRoute } from './core'; + +const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { + eager: true, +}); + +// 有需要可以自行打开注释,并创建文件夹 +// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); +// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); + +/** 动态路由 */ +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); + +/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ +// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); +// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); +const staticRoutes: RouteRecordRaw[] = []; +const externalRoutes: RouteRecordRaw[] = []; + +/** 路由列表,由基本路由、外部路由和404兜底路由组成 + * 无需走权限验证(会一直显示在菜单中) */ +const routes: RouteRecordRaw[] = [ + ...coreRoutes, + ...externalRoutes, + fallbackNotFoundRoute, +]; + +/** 基本路由列表,这些路由不需要进入权限拦截 */ +const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); + +/** 有权限校验的路由列表,包含动态路由和静态路由 */ +const accessRoutes = [...dynamicRoutes, ...staticRoutes]; + +const componentKeys: string[] = Object.keys( + import.meta.glob('../../views/**/*.vue'), +) + .filter((item) => !item.includes('/modules/')) + .map((v) => { + const path = v.replace('../../views/', '/'); + return path.endsWith('.vue') ? path.slice(0, -4) : path; + }); + +export { accessRoutes, componentKeys, coreRouteNames, routes }; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/bot.ts b/easyflow-ui-admin/app/src/router/routes/modules/bot.ts new file mode 100644 index 0000000..6975c89 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/bot.ts @@ -0,0 +1,32 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + name: 'BotRun', + path: '/ai/bots/run/:botId/:sessionId?', + component: () => import('#/views/ai/bots/pages/Run.vue'), + meta: { + title: 'Bots', + noBasicLayout: true, + openInNewWindow: true, + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + }, + }, + { + name: 'BotSetting', + path: '/ai/bots/setting/:id', + component: () => import('#/views/ai/bots/pages/setting/index.vue'), + meta: { + title: 'Bots', + openInNewWindow: true, + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + activePath: '/ai/bots', + }, + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/dashboard.ts b/easyflow-ui-admin/app/src/router/routes/modules/dashboard.ts new file mode 100644 index 0000000..02c9055 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/dashboard.ts @@ -0,0 +1,38 @@ +import type { RouteRecordRaw } from 'vue-router'; + +// import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + // { + // meta: { + // icon: 'lucide:layout-dashboard', + // order: -1, + // title: $t('page.dashboard.title'), + // }, + // name: 'Dashboard', + // path: '/dashboard', + // children: [ + // { + // name: 'Analytics', + // path: '/analytics', + // component: () => import('#/views/dashboard/analytics/index.vue'), + // meta: { + // affixTab: true, + // icon: 'lucide:area-chart', + // title: $t('page.dashboard.analytics'), + // }, + // }, + // { + // name: 'Workspace', + // path: '/workspace', + // component: () => import('#/views/dashboard/workspace/index.vue'), + // meta: { + // icon: 'carbon:workspace', + // title: $t('page.dashboard.workspace'), + // }, + // }, + // ], + // }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/datacenter.ts b/easyflow-ui-admin/app/src/router/routes/modules/datacenter.ts new file mode 100644 index 0000000..8de210a --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/datacenter.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'clarity:database', + title: $t('datacenterTable.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'TableDetail', + path: '/datacenter/table/tableDetail', + component: () => import('#/views/datacenter/DatacenterTableDetail.vue'), + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/demos.ts b/easyflow-ui-admin/app/src/router/routes/modules/demos.ts new file mode 100644 index 0000000..4400576 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/demos.ts @@ -0,0 +1,52 @@ +// import type { RouteRecordRaw } from 'vue-router'; +// +// import { $t } from '#/locales'; +// +// const routes: RouteRecordRaw[] = [ +// { +// meta: { +// icon: 'ic:baseline-view-in-ar', +// keepAlive: true, +// order: 1000, +// title: $t('demos.title'), +// }, +// name: 'Demos', +// path: '/demos', +// children: [ +// { +// meta: { +// title: $t('demos.elementPlus'), +// }, +// name: 'NaiveDemos', +// path: '/demos/element', +// component: () => import('#/views/demos/element/index.vue'), +// }, +// { +// meta: { +// title: '卡片组件', +// }, +// name: 'NaiveDemos1', +// path: '/demos/cardTest', +// component: () => import('#/views/demos/cardTest/index.vue'), +// }, +// { +// meta: { +// title: '分类组件', +// }, +// name: 'NaiveDemos2', +// path: '/demos/categoryPanel', +// component: () => import('#/views/demos/categoryPanel/index.vue'), +// }, +// { +// meta: { +// title: $t('demos.form'), +// }, +// name: 'BasicForm', +// path: '/demos/form', +// component: () => import('#/views/demos/form/basic.vue'), +// }, +// ], +// }, +// ]; +// +// export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/document.ts b/easyflow-ui-admin/app/src/router/routes/modules/document.ts new file mode 100644 index 0000000..abe9be5 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/document.ts @@ -0,0 +1,21 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + title: $t('documentCollection.documentManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + activePath: '/ai/documentCollection', + }, + name: 'Document', + path: '/ai/documentCollection/document', + component: () => import('#/views/ai/documentCollection/Document.vue'), + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/easyflow.ts b/easyflow-ui-admin/app/src/router/routes/modules/easyflow.ts new file mode 100644 index 0000000..145b7c2 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/easyflow.ts @@ -0,0 +1,63 @@ +import type { RouteRecordRaw } from 'vue-router'; + +// import { APP_DOC_URL, APP_GITHUB_URL, APP_LOGO_URL } from '@easyflow/constants'; +// +// import { IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + // { + // meta: { + // badgeType: 'dot', + // icon: APP_LOGO_URL, + // order: 9998, + // title: $t('demos.easyflow.title'), + // }, + // name: 'EasyFlowProject', + // path: '/easyflow-admin', + // children: [ + // { + // name: 'EasyFlowDocument', + // path: '/easyflow-admin/document', + // component: IFrameView, + // meta: { + // icon: 'lucide:book-open-text', + // link: APP_DOC_URL, + // title: $t('demos.easyflow.document'), + // }, + // }, + // { + // name: 'EasyFlowGithub', + // path: '/easyflow-admin/github', + // component: IFrameView, + // meta: { + // icon: 'mdi:github', + // link: APP_GITHUB_URL, + // title: 'Github', + // }, + // }, + // ], + // }, + // { + // name: 'EasyFlowAbout', + // path: '/easyflow-admin/about', + // component: () => import('#/views/_core/about/index.vue'), + // meta: { + // icon: 'lucide:copyright', + // title: $t('demos.easyflow.about'), + // order: 9999, + // }, + // }, + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'lucide:user', + hideInMenu: true, + title: $t('page.auth.profile'), + }, + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts b/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts new file mode 100644 index 0000000..49650ea --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts @@ -0,0 +1,32 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + title: $t('plugin.toolsManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + }, + name: 'PluginTools', + path: '/ai/plugin/tools', + component: () => import('#/views/ai/plugin/PluginTools.vue'), + }, + { + meta: { + title: $t('plugin.toolsManagement'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + fullPathKey: true, + }, + name: 'PluginToolEdit', + path: '/ai/plugin/tool/edit', + component: () => import('#/views/ai/plugin/PluginToolEdit.vue'), + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/sysFeedback.ts b/easyflow-ui-admin/app/src/router/routes/modules/sysFeedback.ts new file mode 100644 index 0000000..720ca1e --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/sysFeedback.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + name: 'SysFeedbackDetail', + path: '/sys/sysFeedback/:id', + component: () => import('#/views/system/sysFeedback/sysFeedbackDetail.vue'), + meta: { + title: $t('menus.system.sysFeedback'), + hideInMenu: true, + hideInBreadcrumb: true, + hideInTab: true, + activePath: '/sys/sysFeedback', + }, + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/sysJob.ts b/easyflow-ui-admin/app/src/router/routes/modules/sysJob.ts new file mode 100644 index 0000000..a293590 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/sysJob.ts @@ -0,0 +1,20 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'clarity:time-line', + title: $t('sysJobLog.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'SysJobLog', + path: '/sys/sysJob/sysJobLog', + component: () => import('#/views/system/sysJob/SysJobLogList.vue'), + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts b/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts new file mode 100644 index 0000000..c3d7d82 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts @@ -0,0 +1,56 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ant-design:apartment-outlined', + title: $t('datacenterTable.title'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'WorkflowDesign', + path: '/ai/workflow/design', + component: () => import('#/views/ai/workflow/WorkflowDesign.vue'), + }, + { + meta: { + title: '运行', + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + activePath: '/ai/workflow', + }, + name: 'RunPage', + path: '/ai/workflow/run', + component: () => import('#/views/ai/workflow/RunPage.vue'), + }, + { + meta: { + title: $t('aiWorkflowExecRecord.moduleName'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'ExecRecord', + path: '/ai/workflow/executeRecords', + component: () => + import('#/views/ai/workflow/execute/WorkflowExecResultList.vue'), + }, + { + meta: { + title: $t('aiWorkflowRecordStep.moduleName'), + hideInMenu: true, + hideInTab: true, + hideInBreadcrumb: true, + }, + name: 'RecordStep', + path: '/ai/workflow/executeSteps', + component: () => + import('#/views/ai/workflow/execute/WorkflowExecStepList.vue'), + }, +]; + +export default routes; diff --git a/easyflow-ui-admin/app/src/shims-vue.d.ts b/easyflow-ui-admin/app/src/shims-vue.d.ts new file mode 100644 index 0000000..7c32d10 --- /dev/null +++ b/easyflow-ui-admin/app/src/shims-vue.d.ts @@ -0,0 +1,17 @@ +declare module '#/components/page/PageData.vue' { + import type { DefineComponent } from 'vue'; + + interface PageDataSlots { + default: (props: { pageList: any[] }) => any; + } + + const component: DefineComponent; + export default component; +} + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + + const component: DefineComponent; + export default component; +} diff --git a/easyflow-ui-admin/app/src/store/auth.ts b/easyflow-ui-admin/app/src/store/auth.ts new file mode 100644 index 0000000..9dbef63 --- /dev/null +++ b/easyflow-ui-admin/app/src/store/auth.ts @@ -0,0 +1,119 @@ +import type { Recordable, UserInfo } from '@easyflow/types'; + +import { ref } from 'vue'; +import { useRouter } from 'vue-router'; + +import { LOGIN_PATH } from '@easyflow/constants'; +import { preferences } from '@easyflow/preferences'; +import { resetAllStores, useAccessStore, useUserStore } from '@easyflow/stores'; + +import { ElNotification } from 'element-plus'; +import { defineStore } from 'pinia'; + +import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { $t } from '#/locales'; + +export const useAuthStore = defineStore('auth', () => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const router = useRouter(); + + const loginLoading = ref(false); + + /** + * 异步处理登录操作 + * Asynchronously handle the login process + * @param params 登录表单数据 + */ + async function authLogin( + params: Recordable, + onSuccess?: () => Promise | void, + ) { + // 异步处理用户登录操作并获取 accessToken + let userInfo: null | UserInfo = null; + try { + loginLoading.value = true; + const { token: accessToken } = await loginApi(params); + + // 如果成功获取到 accessToken + if (accessToken) { + // 将 accessToken 存储到 accessStore 中 + accessStore.setAccessToken(accessToken); + + // 获取用户信息并存储到 accessStore 中 + const [fetchUserInfoResult, accessCodes] = await Promise.all([ + fetchUserInfo(), + getAccessCodesApi(), + ]); + + userInfo = fetchUserInfoResult; + + userStore.setUserInfo(userInfo); + accessStore.setAccessCodes(accessCodes); + + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + onSuccess + ? await onSuccess?.() + : await router.push( + userInfo.homePath || preferences.app.defaultHomePath, + ); + } + + if (userInfo?.nickname) { + ElNotification({ + message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`, + title: $t('authentication.loginSuccess'), + type: 'success', + }); + } + } + } finally { + loginLoading.value = false; + } + + return { + userInfo, + }; + } + + async function logout(redirect: boolean = true) { + try { + await logoutApi(); + } catch { + // 不做任何处理 + } + resetAllStores(); + accessStore.setLoginExpired(false); + + // 回登录页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); + } + + async function fetchUserInfo() { + let userInfo: null | UserInfo = null; + userInfo = await getUserInfoApi(); + userStore.setUserInfo(userInfo); + return userInfo; + } + + function $reset() { + loginLoading.value = false; + } + + return { + $reset, + authLogin, + fetchUserInfo, + loginLoading, + logout, + }; +}); diff --git a/easyflow-ui-admin/app/src/store/dict.ts b/easyflow-ui-admin/app/src/store/dict.ts new file mode 100644 index 0000000..55fb6c8 --- /dev/null +++ b/easyflow-ui-admin/app/src/store/dict.ts @@ -0,0 +1,54 @@ +import { defineStore } from 'pinia'; + +import { api } from '#/api/request'; + +export const useDictStore = defineStore('dictionary', { + state: () => ({ + dictCache: new Map(), // 缓存字典数据 + }), + + getters: { + // 获取特定字典的 Map 对象 + getDictByType: (state) => (dictType: string) => { + return state.dictCache.get(dictType) || new Map(); + }, + }, + + actions: { + // 获取字典数据 + async fetchDictionary(dictType: string) { + // 如果已经有缓存数据,直接返回 + if (this.dictCache.has(dictType)) { + return this.dictCache.get(dictType); + } + + try { + const requestPromise = api.get(`/api/v1/dict/items/${dictType}`); + const dictData = await requestPromise; + // 转换为 { value: label } 格式便于查找 + const dictMap = new Map( + dictData.data.map((item: any) => [item.value, item.label]), + ); + + // 缓存数据并清理加载状态 + this.dictCache.set(dictType, dictMap); + + return dictMap; + } catch (error) { + console.error(`get dict ${dictType} error:`, error); + return new Map(); + } + }, + + // 根据字典类型和值获取标签 + getDictLabel(dictType: string, value: any) { + const dictMap = this.dictCache.get(dictType); + if (!dictMap) { + return value; // 返回原值作为降级处理 + } + + const label = dictMap.get(value); + return label === undefined ? value : label; + }, + }, +}); diff --git a/easyflow-ui-admin/app/src/store/index.ts b/easyflow-ui-admin/app/src/store/index.ts new file mode 100644 index 0000000..b6a7763 --- /dev/null +++ b/easyflow-ui-admin/app/src/store/index.ts @@ -0,0 +1,2 @@ +export * from './auth'; +export * from './dict'; diff --git a/easyflow-ui-admin/app/src/utils/resource.ts b/easyflow-ui-admin/app/src/utils/resource.ts new file mode 100644 index 0000000..c5f4e94 --- /dev/null +++ b/easyflow-ui-admin/app/src/utils/resource.ts @@ -0,0 +1,57 @@ +import audioIcon from '#/assets/ai/resource/audio-icon.png'; +import docIcon from '#/assets/ai/resource/doc-icon.png'; +import otherIcon from '#/assets/ai/resource/other-icon.png'; +import videoIcon from '#/assets/ai/resource/video-icon.png'; + +export function getSrc(item: any) { + switch (item.resourceType) { + case 0: { + return item.resourceUrl; + } + case 1: { + return audioIcon; + } + case 2: { + return videoIcon; + } + case 3: { + return docIcon; + } + default: { + return otherIcon; + } + } +} + +export function getResourceTypeColor(item: any) { + switch (item.resourceType) { + case 0: { + return '#0066FF'; + } + case 1: { + return '#FFA200'; + } + case 2: { + return '#5600FF'; + } + case 3: { + return '#0099CC'; + } + default: { + return '#757575'; + } + } +} +export function getResourceOriginColor(item: any) { + switch (item.origin) { + case 0: { + return '#039e90'; + } + case 1: { + return '#0066FF'; + } + default: { + return '#757575'; + } + } +} diff --git a/easyflow-ui-admin/app/src/views/_core/README.md b/easyflow-ui-admin/app/src/views/_core/README.md new file mode 100644 index 0000000..8248afe --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/README.md @@ -0,0 +1,3 @@ +# \_core + +此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/easyflow-ui-admin/app/src/views/_core/about/index.vue b/easyflow-ui-admin/app/src/views/_core/about/index.vue new file mode 100644 index 0000000..abe8861 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/about/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/code-login.vue b/easyflow-ui-admin/app/src/views/_core/authentication/code-login.vue new file mode 100644 index 0000000..fa2090b --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/code-login.vue @@ -0,0 +1,69 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/forget-password.vue b/easyflow-ui-admin/app/src/views/_core/authentication/forget-password.vue new file mode 100644 index 0000000..2c1b6e4 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/forget-password.vue @@ -0,0 +1,43 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/login.vue b/easyflow-ui-admin/app/src/views/_core/authentication/login.vue new file mode 100644 index 0000000..5cf31e1 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/login.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/oauth-page.vue b/easyflow-ui-admin/app/src/views/_core/authentication/oauth-page.vue new file mode 100644 index 0000000..8550ee4 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/oauth-page.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/qrcode-login.vue b/easyflow-ui-admin/app/src/views/_core/authentication/qrcode-login.vue new file mode 100644 index 0000000..1a04945 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/qrcode-login.vue @@ -0,0 +1,10 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/authentication/register.vue b/easyflow-ui-admin/app/src/views/_core/authentication/register.vue new file mode 100644 index 0000000..c50543f --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/authentication/register.vue @@ -0,0 +1,96 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/fallback/coming-soon.vue b/easyflow-ui-admin/app/src/views/_core/fallback/coming-soon.vue new file mode 100644 index 0000000..306613d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/fallback/coming-soon.vue @@ -0,0 +1,7 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/fallback/forbidden.vue b/easyflow-ui-admin/app/src/views/_core/fallback/forbidden.vue new file mode 100644 index 0000000..4cb6ead --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/fallback/forbidden.vue @@ -0,0 +1,9 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/fallback/internal-error.vue b/easyflow-ui-admin/app/src/views/_core/fallback/internal-error.vue new file mode 100644 index 0000000..a7e393c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/fallback/internal-error.vue @@ -0,0 +1,9 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/fallback/not-found.vue b/easyflow-ui-admin/app/src/views/_core/fallback/not-found.vue new file mode 100644 index 0000000..6a96f07 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/fallback/not-found.vue @@ -0,0 +1,9 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/fallback/offline.vue b/easyflow-ui-admin/app/src/views/_core/fallback/offline.vue new file mode 100644 index 0000000..dbf928f --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/fallback/offline.vue @@ -0,0 +1,9 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/_core/profile/base-setting.vue b/easyflow-ui-admin/app/src/views/_core/profile/base-setting.vue new file mode 100644 index 0000000..497b569 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/profile/base-setting.vue @@ -0,0 +1,77 @@ + + diff --git a/easyflow-ui-admin/app/src/views/_core/profile/index.vue b/easyflow-ui-admin/app/src/views/_core/profile/index.vue new file mode 100644 index 0000000..66e9723 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/profile/index.vue @@ -0,0 +1,51 @@ + + diff --git a/easyflow-ui-admin/app/src/views/_core/profile/notification-setting.vue b/easyflow-ui-admin/app/src/views/_core/profile/notification-setting.vue new file mode 100644 index 0000000..e5004d9 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/profile/notification-setting.vue @@ -0,0 +1,33 @@ + + diff --git a/easyflow-ui-admin/app/src/views/_core/profile/password-setting.vue b/easyflow-ui-admin/app/src/views/_core/profile/password-setting.vue new file mode 100644 index 0000000..13ed59e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/profile/password-setting.vue @@ -0,0 +1,78 @@ + + diff --git a/easyflow-ui-admin/app/src/views/_core/profile/security-setting.vue b/easyflow-ui-admin/app/src/views/_core/profile/security-setting.vue new file mode 100644 index 0000000..e74ac7a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/_core/profile/security-setting.vue @@ -0,0 +1,43 @@ + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/index.vue b/easyflow-ui-admin/app/src/views/ai/bots/index.vue new file mode 100644 index 0000000..5b65238 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/index.vue @@ -0,0 +1,344 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/modal.vue b/easyflow-ui-admin/app/src/views/ai/bots/modal.vue new file mode 100644 index 0000000..ed782d6 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/modal.vue @@ -0,0 +1,120 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/Run.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/Run.vue new file mode 100644 index 0000000..67d90ea --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/Run.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue new file mode 100644 index 0000000..7f3a174 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/PromptChoreChatModal.vue @@ -0,0 +1,91 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/config.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/config.vue new file mode 100644 index 0000000..6e87a5d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/config.vue @@ -0,0 +1,1056 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue new file mode 100644 index 0000000..cca6f92 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue @@ -0,0 +1,66 @@ + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/preview.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/preview.vue new file mode 100644 index 0000000..ff9f76c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/preview.vue @@ -0,0 +1,36 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/prompt.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/prompt.vue new file mode 100644 index 0000000..e983318 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/prompt.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/ChunkDocumentTable.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/ChunkDocumentTable.vue new file mode 100644 index 0000000..fc3889b --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/ChunkDocumentTable.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/ComfirmImportDocument.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/ComfirmImportDocument.vue new file mode 100644 index 0000000..a092e0a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/ComfirmImportDocument.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue new file mode 100644 index 0000000..6419f1e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue new file mode 100644 index 0000000..cda39ab --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionDataConfig.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionDataConfig.vue new file mode 100644 index 0000000..5fc2058 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionDataConfig.vue @@ -0,0 +1,346 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionModal.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionModal.vue new file mode 100644 index 0000000..fab762a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionModal.vue @@ -0,0 +1,381 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentTable.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentTable.vue new file mode 100644 index 0000000..722bdea --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentTable.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue new file mode 100644 index 0000000..c57b41c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeDocFile.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue new file mode 100644 index 0000000..aac2fc3 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/ImportKnowledgeFileContainer.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearch.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearch.vue new file mode 100644 index 0000000..317262e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearch.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearchConfig.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearchConfig.vue new file mode 100644 index 0000000..8b3fdc3 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/KnowledgeSearchConfig.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue new file mode 100644 index 0000000..1027580 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/PreviewSearchKnowledge.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/SegmenterDoc.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/SegmenterDoc.vue new file mode 100644 index 0000000..c5b2709 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/SegmenterDoc.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/SplitterDocPreview.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/SplitterDocPreview.vue new file mode 100644 index 0000000..4132645 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/SplitterDocPreview.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/mcp/Mcp.vue b/easyflow-ui-admin/app/src/views/ai/mcp/Mcp.vue new file mode 100644 index 0000000..35037d4 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/mcp/Mcp.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/mcp/McpModal.vue b/easyflow-ui-admin/app/src/views/ai/mcp/McpModal.vue new file mode 100644 index 0000000..30b04f4 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/mcp/McpModal.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/AddModelModal.vue b/easyflow-ui-admin/app/src/views/ai/model/AddModelModal.vue new file mode 100644 index 0000000..052b2dc --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/AddModelModal.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/AddModelProviderModal.vue b/easyflow-ui-admin/app/src/views/ai/model/AddModelProviderModal.vue new file mode 100644 index 0000000..a4a547d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/AddModelProviderModal.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/ManageModelModal.vue b/easyflow-ui-admin/app/src/views/ai/model/ManageModelModal.vue new file mode 100644 index 0000000..5dbfc3d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/ManageModelModal.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/Model.vue b/easyflow-ui-admin/app/src/views/ai/model/Model.vue new file mode 100644 index 0000000..ad7c714 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/Model.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/ModelVerifyConfig.vue b/easyflow-ui-admin/app/src/views/ai/model/ModelVerifyConfig.vue new file mode 100644 index 0000000..c60ca4c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/ModelVerifyConfig.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/ModelViewItemOperation.vue b/easyflow-ui-admin/app/src/views/ai/model/ModelViewItemOperation.vue new file mode 100644 index 0000000..157f7c2 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/ModelViewItemOperation.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/model/modelUtils/defaultIcon.ts b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/defaultIcon.ts new file mode 100644 index 0000000..34b42ba --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/defaultIcon.ts @@ -0,0 +1,27 @@ +import { ref } from 'vue'; + +import providerList from './providerList.json'; + +const providerOptions = + ref>( + providerList, + ); + +/** + * 根据传入的value,返回对应的icon属性 + * @param targetValue 要匹配的value值 + * @returns 匹配到的icon字符串,未匹配到返回空字符串 + */ +export const getIconByValue = (targetValue: string): string => { + const matchItem = providerOptions.value.find( + (item) => item.value === targetValue, + ); + + return matchItem?.icon || ''; +}; + +export const isSvgString = (icon: any) => { + if (typeof icon !== 'string') return false; + // 简单判断:是否包含 SVG 根标签 + return icon.trim().startsWith(''); +}; diff --git a/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability-utils.ts b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability-utils.ts new file mode 100644 index 0000000..c6bcee8 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability-utils.ts @@ -0,0 +1,71 @@ +import type { BooleanField, ModelAbilityItem } from './model-ability'; + +import type { llmType } from '#/api'; + +/** + * 将 llm 数据转换为标签选中状态 + * @param llm LLM数据对象 + * @param modelAbility 模型能力数组 + * @returns 更新后的模型能力数组 + */ +export const mapLlmToModelAbility = ( + llm: llmType, + modelAbility: ModelAbilityItem[], +): ModelAbilityItem[] => { + return modelAbility.map((tag) => ({ + ...tag, + selected: Boolean(llm[tag.field as keyof llmType]), + })); +}; + +/** + * 从标签选中状态生成 features 对象 + * @param modelAbility 模型能力数组 + * @returns 包含所有字段的features对象 + */ +export const generateFeaturesFromModelAbility = ( + modelAbility: ModelAbilityItem[], +): Record => { + const features: Partial> = {}; + + modelAbility.forEach((tag) => { + features[tag.field] = tag.selected; + }); + + return features as Record; +}; + +/** + * 过滤显示选中的标签 + * @param modelAbility 模型能力数组 + * @returns 选中的标签数组 + */ +export const getSelectedModelAbility = ( + modelAbility: ModelAbilityItem[], +): ModelAbilityItem[] => { + return modelAbility.filter((tag) => tag.selected); +}; + +/** + * 重置所有标签为未选中状态 + * @param modelAbility 模型能力数组 + */ +export const resetModelAbility = (modelAbility: ModelAbilityItem[]): void => { + modelAbility.forEach((tag) => { + tag.selected = false; + }); +}; + +/** + * 根据标签选中状态更新表单数据 + * @param modelAbility 模型能力数组 + * @param formData 表单数据对象 + */ +export const updateFormDataFromModelAbility = ( + modelAbility: ModelAbilityItem[], + formData: Record, +): void => { + modelAbility.forEach((tag) => { + formData[tag.field] = tag.selected; + }); +}; diff --git a/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability.ts b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability.ts new file mode 100644 index 0000000..abc4d62 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/model-ability.ts @@ -0,0 +1,169 @@ +import { $t } from '#/locales'; + +export type BooleanField = + | 'supportAudio' + | 'supportFree' + | 'supportImage' + | 'supportImageB64Only' + | 'supportThinking' + | 'supportTool' + | 'supportToolMessage' + | 'supportVideo'; + +export interface ModelAbilityItem { + activeType: 'danger' | 'info' | 'primary' | 'success' | 'warning'; + defaultType: 'info'; + field: BooleanField; + label: string; + selected: boolean; + value: string; +} + +/** + * 获取模型能力标签的默认配置 + * @returns ModelAbilityItem[] 模型能力配置数组 + */ +export const getDefaultModelAbility = (): ModelAbilityItem[] => [ + { + label: $t('llm.modelAbility.supportThinking'), + value: 'thinking', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportThinking', + }, + { + label: $t('llm.modelAbility.supportTool'), + value: 'tool', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportTool', + }, + { + label: $t('llm.modelAbility.supportVideo'), + value: 'video', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportVideo', + }, + { + label: $t('llm.modelAbility.supportImage'), + value: 'image', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportImage', + }, + { + label: $t('llm.modelAbility.supportFree'), + value: 'free', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportFree', + }, + { + label: $t('llm.modelAbility.supportAudio'), + value: 'audio', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportAudio', + }, + { + label: $t('llm.modelAbility.supportImageB64Only'), + value: 'imageB64', + defaultType: 'info', + activeType: 'success', + selected: false, + field: 'supportImageB64Only', + }, + { + label: $t('llm.modelAbility.supportToolMessage'), + value: 'toolMessage', + defaultType: 'info', + activeType: 'success', + selected: true, + field: 'supportToolMessage', + }, +]; + +/** + * 根据字段数组获取对应的标签选中状态 + * @param modelAbility 模型能力数组 + * @param fields 需要获取的字段数组 + * @returns 以字段名为键、选中状态为值的对象 + */ +export const getTagsSelectedStatus = ( + modelAbility: ModelAbilityItem[], + fields: BooleanField[], +): Record => { + const result: Partial> = {}; + + fields.forEach((field) => { + const tagItem = modelAbility.find((tag) => tag.field === field); + result[field] = tagItem?.selected ?? false; + }); + + return result as Record; +}; + +/** + * 同步标签选中状态与formData中的布尔字段 + * @param modelAbility 模型能力数组 + * @param formData 表单数据对象 + */ +export const syncTagSelectedStatus = ( + modelAbility: ModelAbilityItem[], + formData: Record, +): void => { + modelAbility.forEach((tag) => { + tag.selected = formData[tag.field] ?? false; + }); +}; + +/** + * 处理标签点击事件 + * @param modelAbility 模型能力数组 + * @param item 被点击的标签项 + * @param formData 表单数据对象 + */ +export const handleTagClick = ( + // modelAbility: ModelAbilityItem[], + item: ModelAbilityItem, + formData: Record, +): void => { + // 切换标签选中状态 + item.selected = !item.selected; + + // 同步更新formData中的布尔字段 + formData[item.field] = item.selected; +}; + +/** + * 根据字段获取对应的标签项 + * @param modelAbility 模型能力数组 + * @param field 布尔字段名 + * @returns 标签项 | undefined + */ +export const getTagByField = ( + modelAbility: ModelAbilityItem[], + field: BooleanField, +): ModelAbilityItem | undefined => { + return modelAbility.find((tag) => tag.field === field); +}; + +/** + * 获取所有支持的BooleanField数组 + */ +export const getAllBooleanFields = (): BooleanField[] => [ + 'supportThinking', + 'supportTool', + 'supportImage', + 'supportImageB64Only', + 'supportVideo', + 'supportAudio', + 'supportFree', +]; diff --git a/easyflow-ui-admin/app/src/views/ai/model/modelUtils/modelTypes.ts b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/modelTypes.ts new file mode 100644 index 0000000..73908ea --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/modelTypes.ts @@ -0,0 +1,16 @@ +import { $t } from '@easyflow/locales'; + +export const modelTypes = [ + { + label: $t('llmProvider.chatModel'), + value: 'chatModel', + }, + { + label: $t('llmProvider.embeddingModel'), + value: 'embeddingModel', + }, + { + label: $t('llmProvider.rerankModel'), + value: 'rerankModel', + }, +]; diff --git a/easyflow-ui-admin/app/src/views/ai/model/modelUtils/providerList.json b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/providerList.json new file mode 100644 index 0000000..1c7b213 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/model/modelUtils/providerList.json @@ -0,0 +1,577 @@ +[ + { + "label": "DeepSeek", + "value": "deepseek", + "options":{ + "llmEndpoint":"https://api.deepseek.com", + "chatPath":"/chat/completions", + "modelList":[ + { + "llmModel":"deepseek-reasoner", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-0528", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果" + }, + { + "llmModel":"deepseek-chat", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3-0324", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n" + + }, + { + "label": "Open AI", + "value": "openai", + "options":{ + "llmEndpoint":"https://api.openai.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + + "modelList":[ + { + "llmModel":"o4-mini", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o4-mini", + "description":"O4-mini 是OpenAi最新的小型 O 系列型号。它针对快速、有效的推理进行了优化,在编码和可视化任务中具有非常高效的性能。" + }, + { + "llmModel":"o3", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o3", + "description":"O3 是一个全面而强大的跨领域模型。它为数学、科学、编码和视觉推理任务设定了新标准。它还擅长技术写作和指导遵循。使用它来思考涉及跨文本、代码和图像分析的多步骤问题。" + }, + { + "llmModel":"o3-pro", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"o3-pro", + "description":"o 系列模型经过强化学习训练,在回答和执行复杂推理之前先思考。o3-pro 模型使用更多的计算来更深入地思考并始终提供更好的答案。" + }, + { + "llmModel":"o3-mini", + "supportChat":true, + "supportFunctionCalling":true, + "label":"o3-mini", + "description":"O3-mini 是OpenAi最新的小型推理模型,以与 O1-mini 相同的成本和延迟目标提供高智能。o3-mini 支持关键的开发人员功能,如结构化输出、函数调用和批处理 API。" + }, + { + "llmModel":"GPT-4.1", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GPT-4.1", + "description":"GPT-4.1 是OpenAi用于复杂任务的旗舰模型。它非常适合跨领域解决问题。" + }, + { + "llmModel":"GPT-4o", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GPT-4o", + "description":"GPT-4o(“o”代表“omni”)是OpenAi多功能、高智能的旗舰模型。它接受文本和图像输入,并生成文本输出(包括结构化输出)。它是大多数任务的最佳模型,也是 o 系列模型之外功能最强大的模型" + }, + { + "llmModel":"text-embedding-3-small", + "supportEmbed":true, + "label":"text-embedding-3-small", + "description":"text-embedding-3-small 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用" + }, + { + "llmModel":"text-embedding-3-large", + "supportEmbed":true, + "label":"text-embedding-3-large", + "description":"text-embedding-3-large 是OpenAi最强大的嵌入模型,适用于英语和非英语任务。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + }, + { + "llmModel":"text-embedding-ada-002", + "supportEmbed":true, + "label":"text-embedding-ada-002", + "description":"text-embedding-ada-002 是 ADA 嵌入模型的改进版,性能更高。嵌入是文本的数字表示形式,可用于度量两段文本之间的相关性。嵌入对于搜索、聚类、推荐、异常检测和分类任务非常有用。" + } + + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "阿里百炼", + "value": "aliyun", + "options":{ + "llmEndpoint":"https://dashscope.aliyuncs.com", + "chatPath":"/compatible-mode/v1/chat/completions", + "embedPath":"/compatible-mode/v1/embeddings", + "rerankPath":"/api/v1/services/rerank/text-rerank/text-rerank", + "modelList":[ + { + "llmModel":"qwen-plus", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Plus", + "description":"Qwen3系列Plus模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力显著超过QwQ、通用能力显著超过Qwen2.5-Plus,达到同规模业界SOTA水平。" + + }, + { + "llmModel":"qwen-turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Turbo", + "description":"Qwen3系列Turbo模型,实现思考模式和非思考模式的有效融合,可在对话中切换模式。推理能力以更小参数规模比肩QwQ-32B、通用能力显著超过Qwen2.5-Turbo,达到同规模业界SOTA水平。" + }, + { + "llmModel":"qwen-max", + "supportChat":true, + "supportFunctionCalling":true, + "label":"通义千问-Max", + "description":"通义千问2.5系列千亿级别超大规模语言模型,支持中文、英文等不同语言输入。随着模型的升级,qwen-max将滚动更新升级。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-7b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-7B", + "description":"DeepSeek-R1-Distill-Qwen-7B是一个基于Qwen2.5-Math-7B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-14b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-14B", + "description":"DeepSeek-R1-Distill-Qwen-14B是一个基于Qwen2.5-14B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"deepseek-r1-distill-qwen-32b", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1-Distill-Qwen-32B", + "description":"DeepSeek-R1-Distill-Qwen-32B是一个基于Qwen2.5-32B的蒸馏大型语言模型,使用了 DeepSeek R1 的输出。" + }, + { + "llmModel":"qwen-vl-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问VL-Max", + "description":"通义千问VL-Max(qwen-vl-max),即通义千问超大规模视觉语言模型。相比增强版,再次提升视觉推理能力和指令遵循能力,提供更高的视觉感知和认知水平。在更多复杂任务上提供最佳的性能。" + }, + { + "llmModel":"qwen-vl-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问VL-Plus", + "description":"通义千问VL-Plus(qwen-vl-plus),即通义千问大规模视觉语言模型增强版。大幅提升细节识别能力和文字识别能力,支持超百万像素分辨率和任意长宽比规格的图像。在广泛的视觉任务上提供卓越的性能。" + }, + { + "llmModel":"qvq-max", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问-QVQ-Max", + "description":"通义千问QVQ视觉推理模型,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"qvq-plus", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"通义千问-QVQ-Plus", + "description":"通义千问QVQ视觉推理模型增强版,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。" + }, + { + "llmModel":"text-embedding-v4", + "supportEmbed":true, + "label":"通用文本向量-v4", + "description":"通义实验室基于Qwen3训练的多语言文本统一向量模型,相较V3版本在文本检索、聚类、分类性能大幅提升;在MTEB多语言、中英、Code检索等评测任务上效果提升15%~40%;支持64~2048维用户自定义向量维度。" + }, + { + "llmModel":"text-embedding-v3", + "supportEmbed":true, + "label":"通用文本向量-v3", + "description":"通用文本向量,是通义实验室基于LLM底座的多语言文本统一向量模型,面向全球多个主流语种,提供高水准的向量服务,帮助开发者将文本数据快速转换为高质量的向量数据。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "火山引擎", + "value": "volcengine", + "options":{ + "llmEndpoint":"https://ark.cn-beijing.volces.com", + "chatPath":"/api/v3/chat/completions", + "embedPath":"/api/v3/embeddings", + "modelList":[ + { + "llmModel":"doubao-seed-1-6-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6", + "description":"全新多模态深度思考模型,同时支持 thinking、non-thinking、auto三种思考模式。其中 non-thinking 模型对比 doubao-1-5-pro-32k-250115 模型大幅提升。" + + }, + { + "llmModel":"doubao-seed-1-6-flash-250615", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6-flash", + "description":"有极致推理速度的多模态深度思考模型;同时支持文本和视觉理解。文本理解能力超过上一代 Lite 系列模型,视觉理解比肩友商 Pro 系列模型。" + }, + { + "llmModel":"doubao-seed-1-6-thinking-250715", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"doubao-seed-1.6-thinking", + "description":"在思考能力上进行了大幅强化, 对比 doubao 1.5 代深度理解模型,在编程、数学、逻辑推理等基础能力上进一步提升, 支持视觉理解。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "label":"deepseek-r1", + "description":"deepseek-r1 在后训练阶段大规模使用了强化学习技术,在数学、代码、自然语言推理等任务上,能力比肩 OpenAI o1 正式版。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "百度千帆", + "value": "baidu", + "options":{ + "llmEndpoint":"https://qianfan.baidubce.com", + "chatPath":"/v2/chat/completions", + "embedPath":"/v2/embeddings", + "rerankPath":"/v2/rerank", + "modelList":[ + { + "llmModel":"ernie-x1-turbo-32k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE X1 Turbo", + "description":"核心定位:深度思考模型,具备更强的理解、规划、反思、进化能力。适用场景: 在中文知识问答、文学创作、文稿写作、日常对话、逻辑推理、复杂计算及工具调用等方面表现尤为出色。" + + }, + { + "llmModel":"ernie-4.5-turbo-128k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE 4.5 Turbo", + "description":"​核心定位:更好的满足多轮长历史对话处理、长文档理解问答任务。适用场景:​1)复杂语义理解:支持中文知识问答、文学创作,尤其擅长文档理解(如DocVQA任务)。 ​2)数学推理:在中文数学问题(CMath基准)表现突出。" + }, + { + "llmModel":"ernie-4.5-turbo-vl-32k", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"ERNIE 4.5 Turbo VL", + "description":"​核心定位:多模态基础模型,支持文本、图像跨模态输入与生成。​适用场景:结合图文生成营销文案、视频脚本设计等。" + }, + { + "llmModel":"deepseek-r1-250528", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"核心定位:专业优化推理模型,聚焦数学与逻辑任务。 ​适用场景: ​复杂数学问题:如高等数学题求解、科学计算模拟。 ​逻辑拆解与规划:业务流程自动化、学术研究中的假设验证。 ​STEM领域应用:物理建模、金融量化分析等需高精度推理的场景。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "星火大模型", + "value": "spark", + "options":{ + "llmEndpoint":"https://spark-api-open.xf-yun.com", + "chatPath":"/v1/chat/completions", + "modelList":[ + { + "llmModel":"generalv3.5", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Spark Max", + "description":"旗舰级大语言模型,具有千亿级参数,核心能力全面升级,具备更强的数学、中文、代码和多模态能力。适用数理计算、逻辑推理等对效果有更高要求的业务场景。" + }, + { + "llmModel":"4.0Ultra", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Spark4.0 Ultra", + "description":"最强大的大语言模型版本,文本生成、语言理解、知识问答、逻辑推理、数学能力等方面实现超越GPT4 Turbo,优化联网搜索链路,提供更精准回答。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "Gitee", + "value": "gitee", + "options":{ + "llmEndpoint":"https://ai.gitee.com", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"kimi-k2-instruct", + "supportChat":true, + "supportFunctionCalling":true, + "label":"kimi-k2-instruct", + "description":"Kimi K2 是一个最先进的混合专家 (MoE) 语言模型,激活参数为 320 亿,总参数为 1 万亿。通过 Muon 优化器进行训练,Kimi K2 在前沿知识、推理和编码任务上表现出色,同时在智能体能力方面进行了精心优化" + + }, + { + "llmModel":"ERNIE-4.5-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE-4.5-Turbo", + "description":"文心4.5 Turbo在去幻觉、逻辑推理和代码能力等方面也有着明显增强。对比文心4.5,速度更快、价格更低。" + + }, + { + "llmModel":"ERNIE-X1-Turbo", + "supportChat":true, + "supportFunctionCalling":true, + "label":"ERNIE-X1-Turbo", + "description":"文心ERNIE X1 Turbo具备更长的思维链,更强的深度思考能力,进一步增强了多模态和工具调用能力,擅长文学创作、逻辑推理等" + + }, + { + "llmModel":"DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"DeepSeek-R1 是一款采用强化学习技术的推理模型,凭借少量标注数据大幅提升推理能力,性能媲美 OpenAI o1。" + + }, + { + "llmModel":"DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3", + "description":"DeepSeek-V3 是 685B 参数的高效 MoE 语言模型,性能优越,训练稳定,超越开源模型,并接近顶级闭源模型。" + + }, + { + "llmModel":"Qwen3-235B-A22B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-235B-A22B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-30B-A3B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-32B", + "description":"Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一系列密集型和混合专家(MoE)模型。基于在训练数据、模型架构和优化技术方面的广泛进步。" + + }, + { + "llmModel":"ERNIE-4.5-Turbo-VL", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"ERNIE-4.5-Turbo-VL", + "description":"文心一言大模型全新版本,图片理解、创作、翻译、代码等能力显著提升,首次支持32K上下文长度,首Token时延显著降低。" + + }, + { + "llmModel":"Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是一款拥有 320 亿参数、支持多图输入与复杂图文推理的大规模多模态指令微调模型。" + + }, + { + "llmModel":"InternVL3-78B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"InternVL3-78B", + "description":"InternVL3-78B 是一款支持中英文、多图多轮对话的大规模多模态模型,具备超强图文理解、推理与生成能力,广泛适用于复杂 AI 应用场景。" + + }, + { + "llmModel":"InternVL3-38B", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"InternVL3-38B", + "description":"InternVL3-38B 是一款支持中英双语、多模态对话与图像理解的大规模视觉语言模型,具备强大的跨模态推理与视觉问答能力。" + }, + { + "llmModel":"Qwen3-Embedding-8B", + "supportEmbed":true, + "label":"Qwen3-Embedding-8B", + "description":"Qwen3‑Embedding‑8B 是 Qwen 系列推出的大规模嵌入模型,专注于生成高质量、多语言及代码向量,支持多种下游任务中的语义匹配与信息检索需求。" + }, + { + "llmModel":"Qwen3-Embedding-4B", + "supportEmbed":true, + "label":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是由 Qwen 团队开发的一款高性能文本和代码嵌入模型,专为多语言、多模态任务设计,能够将文本和代码内容转换为语义丰富的向量表示。它广泛适用于语义搜索、跨语言检索、信息匹配、文本相似度分析等多种自然语言处理和代码理解场景。" + } + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "硅基流动", + "value": "siliconlow", + "options":{ + "llmEndpoint":"https://api.siliconflow.cn", + "chatPath":"/v1/chat/completions", + "embedPath":"/v1/embeddings", + "rerankPath":"/v1/rerank", + "modelList":[ + { + "llmModel":"deepseek-ai/DeepSeek-R1", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-R1", + "description":"DeepSeek-R1-0528 是一款强化学习(RL)驱动的推理模型,解决了模型中的重复性和可读性问题。在 RL 之前,DeepSeek-R1 引入了冷启动数据,进一步优化了推理性能。它在数学、代码和推理任务中与 OpenAI-o1 表现相当,并且通过精心设计的训练方法,提升了整体效果。" + + }, + { + "llmModel":"deepseek-ai/DeepSeek-V3", + "supportChat":true, + "supportFunctionCalling":true, + "label":"DeepSeek-V3", + "description":"新版 DeepSeek-V3 (DeepSeek-V3-0324)与之前的 DeepSeek-V3-1226 使用同样的 base 模型,仅改进了后训练方法。新版 V3 模型借鉴 DeepSeek-R1 模型训练过程中所使用的强化学习技术,大幅提高了在推理类任务上的表现水平,在数学、代码类相关评测集上取得了超过 GPT-4.5 的得分成绩。此外该模型在工具调用、角色扮演、问答闲聊等方面也得到了一定幅度的能力提升。" + + }, + { + "llmModel":"moonshotai/Kimi-K2-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Kimi-K2-Instruct", + "description":"Kimi K2 是一款具备超强代码和 Agent 能力的 MoE 架构基础模型,总参数 1T,激活参数 32B。在通用知识推理、编程、数学、Agent 等主要类别的基准性能测试中,K2 模型的性能超过其他主流开源模型" + + }, + { + "llmModel":"Tongyi-Zhiwen/QwenLong-L1-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"QwenLong-L1-32B", + "description":"QwenLong-L1-32B 是首个使用强化学习训练的长上下文大型推理模型(LRM),专门针对长文本推理任务进行优化。该模型通过渐进式上下文扩展的强化学习框架,实现了从短上下文到长上下文的稳定迁移。在七个长上下文文档问答基准测试中,QwenLong-L1-32B 超越了 OpenAI-o3-mini 和 Qwen3-235B-A22B 等旗舰模型,性能可媲美 Claude-3.7-Sonnet-Thinking。该模型特别擅长数学推理、逻辑推理和多跳推理等复杂任务" + + }, + { + "llmModel":"Qwen/Qwen3-30B-A3B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-30B-A3B", + "description":"Qwen3-30B-A3B 是通义千问系列的最新大语言模型,采用混合专家(MoE)架构,拥有 30.5B 总参数量和 3.3B 激活参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"Qwen/Qwen3-32B", + "supportChat":true, + "supportFunctionCalling":true, + "label":"Qwen3-32B", + "description":"Qwen3-32B 是通义千问系列的最新大语言模型,拥有 32.8B 参数量。该模型独特地支持在思考模式(适用于复杂逻辑推理、数学和编程)和非思考模式(适用于高效的通用对话)之间无缝切换,显著增强了推理能力。模型在数学、代码生成和常识逻辑推理上表现优异,并在创意写作、角色扮演和多轮对话等方面展现出卓越的人类偏好对齐能力。此外,该模型支持 100 多种语言和方言,具备出色的多语言指令遵循和翻译能力" + + }, + { + "llmModel":"MiniMaxAI/MiniMax-M1-80k", + "supportChat":true, + "supportFunctionCalling":true, + "label":"MiniMax-M1-80k", + "description":"MiniMax-M1 是开源权重的大规模混合注意力推理模型,拥有 4560 亿参数,每个 Token 可激活约 459 亿参数。模型原生支持 100 万 Token 的超长上下文,并通过闪电注意力机制,在 10 万 Token 的生成任务中相比 DeepSeek R1 节省 75% 的浮点运算量。同时,MiniMax-M1 采用 MoE(混合专家)架构,结合 CISPO 算法与混合注意力设计的高效强化学习训练,在长输入推理与真实软件工程场景中实现了业界领先的性能。" + + }, + { + "llmModel":"THUDM/GLM-4.1V-9B-Thinking", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"GLM-4.1V-9B-Thinking", + "description":"GLM-4.1V-9B-Thinking 是由智谱 AI 和清华大学 KEG 实验室联合发布的一款开源视觉语言模型(VLM),专为处理复杂的多模态认知任务而设计。该模型基于 GLM-4-9B-0414 基础模型,通过引入“思维链”(Chain-of-Thought)推理机制和采用强化学习策略,显著提升了其跨模态的推理能力和稳定性。作为一个 9B 参数规模的轻量级模型,它在部署效率和性能之间取得了平衡,在 28 项权威评测基准中,有 18 项的表现持平甚至超越了 72B 参数规模的 Qwen-2.5-VL-72B。该模型不仅在图文理解、数学科学推理、视频理解等任务上表现卓越,还支持高达 4K 分辨率的图像和任意宽高比输入" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-32B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-32B-Instruct", + "description":"Qwen2.5-VL-32B-Instruct 是通义千问团队推出的多模态大模型,是 Qwen2.5-VL 系列的一部分。该模型不仅精通识别常见物体,还能分析图像中的文本、图表、图标、图形和布局。它可作为视觉智能体,能够推理并动态操控工具,具备使用电脑和手机的能力。此外,这个模型可以精确定位图像中的对象,并为发票、表格等生成结构化输出。相比前代模型 Qwen2-VL,该版本在数学和问题解决能力方面通过强化学习得到了进一步提升,响应风格也更符合人类偏好" + }, + { + "llmModel":"Qwen/Qwen2.5-VL-72B-Instruct", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"Qwen2.5-VL-72B-Instruct", + "description":"Qwen2.5-VL 是 Qwen2.5 系列中的视觉语言模型。该模型在多方面有显著提升:具备更强的视觉理解能力,能够识别常见物体、分析文本、图表和布局;作为视觉代理能够推理并动态指导工具使用;支持理解超过 1 小时的长视频并捕捉关键事件;能够通过生成边界框或点准确定位图像中的物体;支持生成结构化输出,尤其适用于发票、表格等扫描数据。模型在多项基准测试中表现出色,包括图像、视频和代理任务评测" + }, + { + "llmModel":"deepseek-ai/deepseek-vl2", + "supportChat":true, + "supportFunctionCalling":true, + "multimodal":true, + "label":"deepseek-vl2", + "description":"DeepSeek-VL2 是一个基于 DeepSeekMoE-27B 开发的混合专家(MoE)视觉语言模型,采用稀疏激活的 MoE 架构,在仅激活 4.5B 参数的情况下实现了卓越性能。该模型在视觉问答、光学字符识别、文档/表格/图表理解和视觉定位等多个任务中表现优异,与现有的开源稠密模型和基于 MoE 的模型相比,在使用相同或更少的激活参数的情况下,实现了具有竞争力的或最先进的性能表现" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-8B", + "supportEmbed":true, + "label":"Qwen3-Embedding-8B", + "description":"Qwen3-Embedding-8B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 80 亿参数规模,支持长达 32K 的上下文长度,可生成最高 4096 维的嵌入向量。该模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上排名第一(截至 2025 年 6 月 5 日,得分 70.58),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 4096)和指令感知功能,可根据特定任务、语言或场景进行优化" + }, + { + "llmModel":"Qwen/Qwen3-Embedding-4B", + "supportEmbed":true, + "label":"Qwen3-Embedding-4B", + "description":"Qwen3-Embedding-4B 是 Qwen3 嵌入模型系列的最新专有模型,专为文本嵌入和排序任务设计。该模型基于 Qwen3 系列的密集基础模型,具有 40 亿参数规模,支持长达 32K 的上下文长度,可生成最高 2560 维的嵌入向量。模型继承了基础模型卓越的多语言能力,支持超过 100 种语言,具备长文本理解和推理能力。在 MTEB 多语言排行榜上表现卓越(得分 69.45),在文本检索、代码检索、文本分类、文本聚类和双语挖掘等多项任务中表现出色。模型支持用户自定义输出维度(32 到 2560)和指令感知功能,可根据特定任务、语言或场景进行优化,在效率和效果之间达到良好平衡" + }, + { + "llmModel":"BAAI/bge-m3", + "supportEmbed":true, + "label":"bge-m3", + "description":"BGE-M3 是一个多功能、多语言、多粒度的文本嵌入模型。它支持三种常见的检索功能:密集检索、多向量检索和稀疏检索。该模型可以处理超过100种语言,并且能够处理从短句到长达8192个词元的长文档等不同粒度的输入。BGE-M3在多语言和跨语言检索任务中表现出色,在 MIRACL 和 MKQA 等基准测试中取得了领先结果。它还具有处理长文档检索的能力,在 MLDR 和 NarritiveQA 等数据集上展现了优秀性能" + }, + { + "llmModel":"netease-youdao/bce-embedding-base_v1", + "supportEmbed":true, + "label":"bce-embedding-base_v1", + "description":"bce-embedding-base_v1 是由网易有道开发的双语和跨语言嵌入模型。该模型在中英文语义表示和检索任务中表现出色,尤其擅长跨语言场景。它是为检索增强生成(RAG)系统优化的,可以直接应用于教育、医疗、法律等多个领域。该模型不需要特定指令即可使用,能够高效地生成语义向量,为语义搜索和问答系统提供关键支持" + } + + ] + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "label": "Ollama", + "value": "ollama", + "options":{ + + }, + "icon": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + } +] diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/AddPluginModal.vue b/easyflow-ui-admin/app/src/views/ai/plugin/AddPluginModal.vue new file mode 100644 index 0000000..ea1915a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/AddPluginModal.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/AiPluginToolModal.vue b/easyflow-ui-admin/app/src/views/ai/plugin/AiPluginToolModal.vue new file mode 100644 index 0000000..4984421 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/AiPluginToolModal.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/CategoryPluginModal.vue b/easyflow-ui-admin/app/src/views/ai/plugin/CategoryPluginModal.vue new file mode 100644 index 0000000..33f60e0 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/CategoryPluginModal.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue new file mode 100644 index 0000000..6484b03 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginInputAndOutParams.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginInputAndOutParams.vue new file mode 100644 index 0000000..420b1aa --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginInputAndOutParams.vue @@ -0,0 +1,703 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunParams.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunParams.vue new file mode 100644 index 0000000..1a97a97 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunParams.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunTestModal.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunTestModal.vue new file mode 100644 index 0000000..ee7ecab --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginRunTestModal.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolCollapse.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolCollapse.vue new file mode 100644 index 0000000..d879ed0 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolCollapse.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolEdit.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolEdit.vue new file mode 100644 index 0000000..ac045ff --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolEdit.vue @@ -0,0 +1,763 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolTable.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolTable.vue new file mode 100644 index 0000000..1e31f1e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginToolTable.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/PluginTools.vue b/easyflow-ui-admin/app/src/views/ai/plugin/PluginTools.vue new file mode 100644 index 0000000..637e597 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/plugin/PluginTools.vue @@ -0,0 +1,62 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/resource/ChooseResource.vue b/easyflow-ui-admin/app/src/views/ai/resource/ChooseResource.vue new file mode 100644 index 0000000..110ae80 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/resource/ChooseResource.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/resource/PreviewModal.vue b/easyflow-ui-admin/app/src/views/ai/resource/PreviewModal.vue new file mode 100644 index 0000000..e8c09f7 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/resource/PreviewModal.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/resource/ResourceCardList.vue b/easyflow-ui-admin/app/src/views/ai/resource/ResourceCardList.vue new file mode 100644 index 0000000..f7da0c7 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/resource/ResourceCardList.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/resource/ResourceList.vue b/easyflow-ui-admin/app/src/views/ai/resource/ResourceList.vue new file mode 100644 index 0000000..311a79c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/resource/ResourceList.vue @@ -0,0 +1,467 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/resource/ResourceModal.vue b/easyflow-ui-admin/app/src/views/ai/resource/ResourceModal.vue new file mode 100644 index 0000000..49b9eb1 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/resource/ResourceModal.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/RunPage.vue b/easyflow-ui-admin/app/src/views/ai/workflow/RunPage.vue new file mode 100644 index 0000000..e30d483 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/RunPage.vue @@ -0,0 +1,131 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue new file mode 100644 index 0000000..6f32594 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue new file mode 100644 index 0000000..c4e1289 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue @@ -0,0 +1,451 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowModal.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowModal.vue new file mode 100644 index 0000000..815b582 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowModal.vue @@ -0,0 +1,231 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItem.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItem.vue new file mode 100644 index 0000000..a62db12 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItem.vue @@ -0,0 +1,211 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItemMulti.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItemMulti.vue new file mode 100644 index 0000000..42789f3 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/ConfirmItemMulti.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResult.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResult.vue new file mode 100644 index 0000000..d358777 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResult.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResultItem.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResultItem.vue new file mode 100644 index 0000000..2ac3563 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/ExecResultItem.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/SingleRun.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/SingleRun.vue new file mode 100644 index 0000000..0a1283b --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/SingleRun.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowForm.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowForm.vue new file mode 100644 index 0000000..388e5bd --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowForm.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowFormItem.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowFormItem.vue new file mode 100644 index 0000000..7249d44 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowFormItem.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowSteps.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowSteps.vue new file mode 100644 index 0000000..9974bfe --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowSteps.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/documentNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/documentNode.ts new file mode 100644 index 0000000..7529f1a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/documentNode.ts @@ -0,0 +1,37 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.documentNode]: { + title: $t('aiWorkflow.fileContentExtraction'), + group: 'base', + description: $t('aiWorkflow.descriptions.fileContentExtraction'), + icon: '', + sortNo: 801, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'fileUrl', + nameDisabled: true, + title: $t('aiWorkflow.documentAddress'), + dataType: 'File', + required: true, + description: $t('aiWorkflow.descriptions.documentAddress'), + }, + ], + outputDefs: [ + { + name: 'content', + title: $t('aiWorkflow.parsedText'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.parsedText'), + deleteDisabled: true, + }, + ], + }, +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/downloadNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/downloadNode.ts new file mode 100644 index 0000000..0fa7182 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/downloadNode.ts @@ -0,0 +1,90 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.downloadNode]: { + title: $t('aiWorkflow.resourceSync'), + group: 'base', + description: $t('aiWorkflow.descriptions.resourceSync'), + icon: '', + sortNo: 811, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'originUrl', + nameDisabled: true, + title: $t('aiWorkflow.originUrl'), + dataType: 'String', + required: true, + description: $t('aiWorkflow.descriptions.originUrl'), + }, + ], + outputDefs: [ + { + name: 'resourceUrl', + title: $t('aiWorkflow.savedUrl'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.savedUrl'), + deleteDisabled: true, + }, + ], + forms: [ + // 节点表单 + { + // 'input' | 'textarea' | 'select' | 'slider' | 'heading' | 'chosen' + type: 'heading', + label: $t('aiWorkflow.saveOptions'), + }, + { + type: 'select', + label: $t('aiResource.resourceType'), + description: $t('aiWorkflow.descriptions.resourceType'), + name: 'resourceType', // 属性名称 + defaultValue: '99', + options: [ + { + label: $t('aiWorkflow.image'), + value: '0', + }, + { + label: $t('aiWorkflow.video'), + value: '1', + }, + { + label: $t('aiWorkflow.audio'), + value: '2', + }, + { + label: $t('aiWorkflow.document'), + value: '3', + }, + { + label: $t('aiWorkflow.other'), + value: '99', + }, + ], + }, + // { + // // 用法可参考插件节点的代码 + // type: 'chosen', + // label: '插件选择', + // chosen: { + // // 节点自定义属性 + // labelDataKey: 'pluginName', + // valueDataKey: 'pluginId', + // // updateNodeData 可动态更新节点属性 + // // value 为选中的 value + // // label 为选中的 label + // onChosen: ((updateNodeData: (data: Record) => void, value?: string, label?: string, event?: Event) => { + // console.warn('No onChosen handler provided for plugin-node'); + // }) + // } + // } + ], + }, +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/index.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/index.ts new file mode 100644 index 0000000..6356070 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/index.ts @@ -0,0 +1,29 @@ +import docNode from './documentNode'; +import downloadNode from './downloadNode'; +import makeFileNode from './makeFileNode'; +import nodeNames from './nodeNames'; +import { PluginNode } from './pluginNode'; +import { SaveToDatacenterNode } from './saveToDatacenter'; +import { SearchDatacenterNode } from './searchDatacenter'; +import sqlNode from './sqlNode'; +import { WorkflowNode } from './workflowNode'; + +export interface CustomNodeOptions { + handleChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} +export const getCustomNode = async (options: CustomNodeOptions) => { + const pluginNode = PluginNode({ onChosen: options.handleChosen }); + const workflowNode = WorkflowNode({ onChosen: options.handleChosen }); + const searchDatacenterNode = await SearchDatacenterNode(); + const saveToDatacenterNode = await SaveToDatacenterNode(); + return { + ...docNode, + ...makeFileNode, + ...downloadNode, + ...sqlNode, + [nodeNames.pluginNode]: pluginNode, + [nodeNames.workflowNode]: workflowNode, + [nodeNames.searchDatacenterNode]: searchDatacenterNode, + [nodeNames.saveToDatacenterNode]: saveToDatacenterNode, + }; +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/makeFileNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/makeFileNode.ts new file mode 100644 index 0000000..9fc484d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/makeFileNode.ts @@ -0,0 +1,58 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.makeFileNode]: { + title: $t('aiWorkflow.fileGeneration'), + group: 'base', + description: $t('aiWorkflow.descriptions.fileGeneration'), + icon: '', + sortNo: 802, + parametersAddEnable: true, + outputDefsAddEnable: true, + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.fileSettings'), + }, + { + type: 'select', + label: $t('documentCollection.splitterDoc.fileType'), + description: $t('aiWorkflow.descriptions.fileType'), + name: 'suffix', + defaultValue: 'docx', + options: [ + { + label: 'docx', + value: 'docx', + }, + ], + }, + ], + parameters: [ + { + name: 'content', + nameDisabled: true, + title: $t('preferences.content'), + dataType: 'String', + required: true, + description: $t('preferences.content'), + deleteDisabled: true, + }, + ], + outputDefs: [ + { + name: 'url', + nameDisabled: true, + title: $t('aiWorkflow.fileDownloadURL'), + dataType: 'String', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.fileDownloadURL'), + deleteDisabled: true, + }, + ], + }, +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/nodeNames.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/nodeNames.ts new file mode 100644 index 0000000..d727f0c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/nodeNames.ts @@ -0,0 +1,10 @@ +export default { + documentNode: 'document-node', + makeFileNode: 'make-file', + downloadNode: 'download-node', + sqlNode: 'sql-node', + pluginNode: 'plugin-node', + workflowNode: 'workflow-node', + searchDatacenterNode: 'search-datacenter-node', + saveToDatacenterNode: 'save-to-datacenter-node', +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/pluginNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/pluginNode.ts new file mode 100644 index 0000000..acf58a6 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/pluginNode.ts @@ -0,0 +1,30 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export interface PluginNodeOptions { + onChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} + +export const PluginNode = (options: PluginNodeOptions = {}) => ({ + title: $t('menus.ai.plugin'), + group: 'base', + description: $t('aiWorkflow.descriptions.plugin'), + icon: '', + sortNo: 810, + parametersAddEnable: false, + outputDefsAddEnable: false, + forms: [ + { + type: 'chosen', + label: $t('aiWorkflow.pluginSelect'), + chosen: { + labelDataKey: 'pluginName', + valueDataKey: 'pluginId', + onChosen: (updateNodeData: any, value: any) => { + options.onChosen?.(nodeNames.pluginNode, updateNodeData, value); + }, + }, + }, + ], +}); diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/saveToDatacenter.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/saveToDatacenter.ts new file mode 100644 index 0000000..4205e92 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/saveToDatacenter.ts @@ -0,0 +1,58 @@ +import { getOptions } from '@easyflow/utils'; + +import { api } from '#/api/request'; +import { $t } from '#/locales'; + +export const SaveToDatacenterNode = async () => { + const res = await api.get('/api/v1/datacenterTable/list'); + + return { + title: $t('aiWorkflow.saveData'), + group: 'base', + description: $t('aiWorkflow.descriptions.saveData'), + icon: '', + sortNo: 812, + parametersAddEnable: false, + outputDefsAddEnable: false, + parameters: [ + { + name: 'saveList', + title: $t('aiWorkflow.dataToBeSaved'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.dataToBeSaved'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + outputDefs: [ + { + name: 'successRows', + title: $t('aiWorkflow.successInsertedRecords'), + dataType: 'Number', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.successInsertedRecords'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.dataTable'), + }, + { + type: 'select', + label: '', + description: $t('aiWorkflow.descriptions.dataTable'), + name: 'tableId', + defaultValue: '', + options: getOptions('tableName', 'id', res.data), + }, + ], + }; +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/searchDatacenter.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/searchDatacenter.ts new file mode 100644 index 0000000..9422777 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/searchDatacenter.ts @@ -0,0 +1,68 @@ +import { getOptions } from '@easyflow/utils'; + +import { api } from '#/api/request'; +import { $t } from '#/locales'; + +export const SearchDatacenterNode = async () => { + const res = await api.get('/api/v1/datacenterTable/list'); + + return { + title: $t('aiWorkflow.queryData'), + group: 'base', + description: $t('aiWorkflow.descriptions.queryData'), + icon: '', + sortNo: 813, + parametersAddEnable: true, + outputDefsAddEnable: false, + parameters: [], + outputDefs: [ + { + name: 'rows', + title: $t('aiWorkflow.queryResult'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.queryResult'), + deleteDisabled: true, + nameDisabled: false, + }, + ], + forms: [ + { + type: 'heading', + label: $t('aiWorkflow.dataTable'), + }, + { + type: 'select', + label: '', + description: $t('aiWorkflow.descriptions.dataTable'), + name: 'tableId', + defaultValue: '', + options: getOptions('tableName', 'id', res.data), + }, + { + type: 'heading', + label: $t('aiWorkflow.filterConditions'), + }, + { + type: 'textarea', + label: "如:name='张三' and age=21 or field = {{流程变量}}", + description: '', + name: 'where', + defaultValue: '', + }, + { + type: 'heading', + label: $t('aiWorkflow.limit'), + }, + { + type: 'input', + label: '', + description: '', + name: 'limit', + defaultValue: '10', + }, + ], + }; +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/sqlNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/sqlNode.ts new file mode 100644 index 0000000..7806bd6 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/sqlNode.ts @@ -0,0 +1,37 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export default { + [nodeNames.sqlNode]: { + title: $t('aiWorkflow.sqlQuery'), + group: 'base', + description: $t('aiWorkflow.descriptions.sqlQuery'), + icon: '', + sortNo: 803, + parametersAddEnable: true, + outputDefsAddEnable: true, + parameters: [], + forms: [ + { + name: 'sql', + type: 'textarea', + label: 'SQL', + placeholder: $t('aiWorkflow.descriptions.enterSQL'), + }, + ], + outputDefs: [ + { + name: 'queryData', + title: $t('aiWorkflow.queryResult'), + dataType: 'Array', + dataTypeDisabled: true, + required: true, + parametersAddEnable: false, + description: $t('aiWorkflow.descriptions.queryResultJson'), + deleteDisabled: true, + nameDisabled: true, + }, + ], + }, +}; diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/customNode/workflowNode.ts b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/workflowNode.ts new file mode 100644 index 0000000..486e38d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/customNode/workflowNode.ts @@ -0,0 +1,30 @@ +import { $t } from '#/locales'; + +import nodeNames from './nodeNames'; + +export interface WorkflowNodeOptions { + onChosen?: (nodeType: string, updateNodeData: any, value: string) => void; +} + +export const WorkflowNode = (options: WorkflowNodeOptions = {}) => ({ + title: $t('aiWorkflow.subProcess'), + group: 'base', + description: $t('aiWorkflow.descriptions.subProcess'), + icon: '', + sortNo: 815, + parametersAddEnable: false, + outputDefsAddEnable: false, + forms: [ + { + type: 'chosen', + label: $t('aiWorkflow.workflowSelect'), + chosen: { + labelDataKey: 'workflowName', + valueDataKey: 'workflowId', + onChosen: (updateNodeData: any, value: any) => { + options.onChosen?.(nodeNames.workflowNode, updateNodeData, value); + }, + }, + }, + ], +}); diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue b/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue new file mode 100644 index 0000000..8b925e2 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecResultList.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue b/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue new file mode 100644 index 0000000..4d24c6e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/ai/workflow/execute/WorkflowExecStepList.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKey.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKey.vue new file mode 100644 index 0000000..a97256e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKey.vue @@ -0,0 +1,30 @@ + + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue new file mode 100644 index 0000000..f6fa1f2 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue @@ -0,0 +1,201 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyModal.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyModal.vue new file mode 100644 index 0000000..d3130f2 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyModal.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue new file mode 100644 index 0000000..9285e6c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionList.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue new file mode 100644 index 0000000..ff1a365 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionModal.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue new file mode 100644 index 0000000..350b479 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyResourcePermissionSelectModal.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/config/settings/Settings.vue b/easyflow-ui-admin/app/src/views/config/settings/Settings.vue new file mode 100644 index 0000000..53ad26e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/config/settings/Settings.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-trends.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 0000000..21e8206 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,98 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-data.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 0000000..8aa3f44 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,82 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-sales.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 0000000..f99c857 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,46 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-source.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 0000000..2f2119a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,65 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 0000000..ba2b84a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,55 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/analytics/index.vue b/easyflow-ui-admin/app/src/views/dashboard/analytics/index.vue new file mode 100644 index 0000000..9c270c2 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue b/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue new file mode 100644 index 0000000..b05f22c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue @@ -0,0 +1,266 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/datacenter/BatchImportModal.vue b/easyflow-ui-admin/app/src/views/datacenter/BatchImportModal.vue new file mode 100644 index 0000000..850dd0a --- /dev/null +++ b/easyflow-ui-admin/app/src/views/datacenter/BatchImportModal.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableDetail.vue b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableDetail.vue new file mode 100644 index 0000000..d2553b6 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableDetail.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableList.vue b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableList.vue new file mode 100644 index 0000000..03890ba --- /dev/null +++ b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableList.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableModal.vue b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableModal.vue new file mode 100644 index 0000000..5d70d0c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/datacenter/DatacenterTableModal.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/datacenter/RecordModal.vue b/easyflow-ui-admin/app/src/views/datacenter/RecordModal.vue new file mode 100644 index 0000000..62e696f --- /dev/null +++ b/easyflow-ui-admin/app/src/views/datacenter/RecordModal.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/demos/cardTest/index.vue b/easyflow-ui-admin/app/src/views/demos/cardTest/index.vue new file mode 100644 index 0000000..c68984c --- /dev/null +++ b/easyflow-ui-admin/app/src/views/demos/cardTest/index.vue @@ -0,0 +1,177 @@ + + + + + + diff --git a/easyflow-ui-admin/app/src/views/demos/categoryPanel/index.vue b/easyflow-ui-admin/app/src/views/demos/categoryPanel/index.vue new file mode 100644 index 0000000..a569480 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/demos/categoryPanel/index.vue @@ -0,0 +1,34 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/demos/element/index.vue b/easyflow-ui-admin/app/src/views/demos/element/index.vue new file mode 100644 index 0000000..d0b2ac8 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/demos/element/index.vue @@ -0,0 +1,117 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/demos/form/basic.vue b/easyflow-ui-admin/app/src/views/demos/form/basic.vue new file mode 100644 index 0000000..e96f23f --- /dev/null +++ b/easyflow-ui-admin/app/src/views/demos/form/basic.vue @@ -0,0 +1,191 @@ + + diff --git a/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountList.vue b/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountList.vue new file mode 100644 index 0000000..bfec70d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountList.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountModal.vue b/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountModal.vue new file mode 100644 index 0000000..52994b0 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysAccount/SysAccountModal.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptList.vue b/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptList.vue new file mode 100644 index 0000000..2b9c689 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptList.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptModal.vue b/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptModal.vue new file mode 100644 index 0000000..bdd440e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysDept/SysDeptModal.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackDetail.vue b/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackDetail.vue new file mode 100644 index 0000000..abd74f8 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackDetail.vue @@ -0,0 +1,187 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackList.vue b/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackList.vue new file mode 100644 index 0000000..6a07f19 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysFeedback/sysFeedbackList.vue @@ -0,0 +1,241 @@ + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysJob/SysJobList.vue b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobList.vue new file mode 100644 index 0000000..4e187c0 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobList.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysJob/SysJobLogList.vue b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobLogList.vue new file mode 100644 index 0000000..97d710e --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobLogList.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysJob/SysJobModal.vue b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobModal.vue new file mode 100644 index 0000000..a21d611 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysJob/SysJobModal.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysLog/SysLogList.vue b/easyflow-ui-admin/app/src/views/system/sysLog/SysLogList.vue new file mode 100644 index 0000000..e777506 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysLog/SysLogList.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysLog/SysLogModal.vue b/easyflow-ui-admin/app/src/views/system/sysLog/SysLogModal.vue new file mode 100644 index 0000000..a216b32 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysLog/SysLogModal.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuList.vue b/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuList.vue new file mode 100644 index 0000000..9976673 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuList.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuModal.vue b/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuModal.vue new file mode 100644 index 0000000..fd1004d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysMenu/SysMenuModal.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionList.vue b/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionList.vue new file mode 100644 index 0000000..c4fdcb3 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionList.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionModal.vue b/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionModal.vue new file mode 100644 index 0000000..7a8acc6 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysPosition/SysPositionModal.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleList.vue b/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleList.vue new file mode 100644 index 0000000..f8f6337 --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleList.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleModal.vue b/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleModal.vue new file mode 100644 index 0000000..b85e69d --- /dev/null +++ b/easyflow-ui-admin/app/src/views/system/sysRole/SysRoleModal.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/easyflow-ui-admin/app/tailwind.config.mjs b/easyflow-ui-admin/app/tailwind.config.mjs new file mode 100644 index 0000000..737aebc --- /dev/null +++ b/easyflow-ui-admin/app/tailwind.config.mjs @@ -0,0 +1 @@ +export { default } from '@easyflow/tailwind-config'; diff --git a/easyflow-ui-admin/app/tsconfig.json b/easyflow-ui-admin/app/tsconfig.json new file mode 100644 index 0000000..420fb59 --- /dev/null +++ b/easyflow-ui-admin/app/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/web-app.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/easyflow-ui-admin/app/tsconfig.node.json b/easyflow-ui-admin/app/tsconfig.node.json new file mode 100644 index 0000000..3dc71eb --- /dev/null +++ b/easyflow-ui-admin/app/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/node.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "noEmit": false + }, + "include": ["vite.config.mts"] +} diff --git a/easyflow-ui-admin/app/vite.config.mts b/easyflow-ui-admin/app/vite.config.mts new file mode 100644 index 0000000..ae2ab8d --- /dev/null +++ b/easyflow-ui-admin/app/vite.config.mts @@ -0,0 +1,27 @@ +import { defineConfig } from '@easyflow/vite-config'; + +import ElementPlus from 'unplugin-element-plus/vite'; + +export default defineConfig(async () => { + return { + application: {}, + vite: { + plugins: [ + ElementPlus({ + format: 'esm', + }), + ], + server: { + proxy: { + '/api': { + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + // mock代理目标地址 + target: 'http://localhost:5320/api', + ws: true, + }, + }, + }, + }, + }; +}); diff --git a/easyflow-ui-admin/cspell.json b/easyflow-ui-admin/cspell.json new file mode 100644 index 0000000..710ecfe --- /dev/null +++ b/easyflow-ui-admin/cspell.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en,en-US", + "allowCompoundWords": true, + "words": [ + "acmr", + "antd", + "antdv", + "astro", + "brotli", + "clsx", + "defu", + "demi", + "echarts", + "ependencies", + "esno", + "etag", + "execa", + "iconify", + "iconoir", + "intlify", + "lockb", + "lucide", + "minh", + "minw", + "mkdist", + "mockjs", + "naiveui", + "nocheck", + "noopener", + "noreferrer", + "nprogress", + "nuxt", + "pinia", + "prefixs", + "publint", + "qrcode", + "reka", + "shadcn", + "sonner", + "sortablejs", + "styl", + "taze", + "ui-kit", + "uicons", + "unplugin", + "unref", + "easyflow", + "easyflow", + "vite", + "vitejs", + "vitepress", + "vnode", + "vueuse", + "yxxx" + ], + "ignorePaths": [ + "**/node_modules/**", + "**/dist/**", + "**/*-dist/**", + "**/icons/**", + "pnpm-lock.yaml", + "**/*.log", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" + ] +} diff --git a/easyflow-ui-admin/easyflow.code-workspace b/easyflow-ui-admin/easyflow.code-workspace new file mode 100644 index 0000000..250aecd --- /dev/null +++ b/easyflow-ui-admin/easyflow.code-workspace @@ -0,0 +1,152 @@ +{ + "folders": [ + { + "name": "@easyflow/app", + "path": "app", + }, + { + "name": "@easyflow/commitlint-config", + "path": "internal/lint-configs/commitlint-config", + }, + { + "name": "@easyflow/eslint-config", + "path": "internal/lint-configs/eslint-config", + }, + { + "name": "@easyflow/prettier-config", + "path": "internal/lint-configs/prettier-config", + }, + { + "name": "@easyflow/stylelint-config", + "path": "internal/lint-configs/stylelint-config", + }, + { + "name": "@easyflow/node-utils", + "path": "internal/node-utils", + }, + { + "name": "@easyflow/tailwind-config", + "path": "internal/tailwind-config", + }, + { + "name": "@easyflow/tsconfig", + "path": "internal/tsconfig", + }, + { + "name": "@easyflow/vite-config", + "path": "internal/vite-config", + }, + { + "name": "@easyflow-core/design", + "path": "packages/@core/base/design", + }, + { + "name": "@easyflow-core/icons", + "path": "packages/@core/base/icons", + }, + { + "name": "@easyflow-core/shared", + "path": "packages/@core/base/shared", + }, + { + "name": "@easyflow-core/typings", + "path": "packages/@core/base/typings", + }, + { + "name": "@easyflow-core/composables", + "path": "packages/@core/composables", + }, + { + "name": "@easyflow-core/preferences", + "path": "packages/@core/preferences", + }, + { + "name": "@easyflow-core/form-ui", + "path": "packages/@core/ui-kit/form-ui", + }, + { + "name": "@easyflow-core/layout-ui", + "path": "packages/@core/ui-kit/layout-ui", + }, + { + "name": "@easyflow-core/menu-ui", + "path": "packages/@core/ui-kit/menu-ui", + }, + { + "name": "@easyflow-core/popup-ui", + "path": "packages/@core/ui-kit/popup-ui", + }, + { + "name": "@easyflow-core/shadcn-ui", + "path": "packages/@core/ui-kit/shadcn-ui", + }, + { + "name": "@easyflow-core/tabs-ui", + "path": "packages/@core/ui-kit/tabs-ui", + }, + { + "name": "@easyflow/constants", + "path": "packages/constants", + }, + { + "name": "@easyflow/access", + "path": "packages/effects/access", + }, + { + "name": "@easyflow/common-ui", + "path": "packages/effects/common-ui", + }, + { + "name": "@easyflow/hooks", + "path": "packages/effects/hooks", + }, + { + "name": "@easyflow/layouts", + "path": "packages/effects/layouts", + }, + { + "name": "@easyflow/plugins", + "path": "packages/effects/plugins", + }, + { + "name": "@easyflow/request", + "path": "packages/effects/request", + }, + { + "name": "@easyflow/icons", + "path": "packages/icons", + }, + { + "name": "@easyflow/locales", + "path": "packages/locales", + }, + { + "name": "@easyflow/preferences", + "path": "packages/preferences", + }, + { + "name": "@easyflow/stores", + "path": "packages/stores", + }, + { + "name": "@easyflow/styles", + "path": "packages/styles", + }, + { + "name": "@easyflow/types", + "path": "packages/types", + }, + { + "name": "@easyflow/utils", + "path": "packages/utils", + }, + { + "name": "@easyflow/turbo-run", + "path": "scripts/turbo-run", + }, + { + "name": "@easyflow/vsh", + "path": "scripts/vsh", + }, + ], +} diff --git a/easyflow-ui-admin/eslint.config.mjs b/easyflow-ui-admin/eslint.config.mjs new file mode 100644 index 0000000..0337b46 --- /dev/null +++ b/easyflow-ui-admin/eslint.config.mjs @@ -0,0 +1,5 @@ +// @ts-check + +import { defineConfig } from '@easyflow/eslint-config'; + +export default defineConfig(); diff --git a/easyflow-ui-admin/internal/lint-configs/commitlint-config/index.mjs b/easyflow-ui-admin/internal/lint-configs/commitlint-config/index.mjs new file mode 100644 index 0000000..a42d820 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/commitlint-config/index.mjs @@ -0,0 +1,153 @@ +import { execSync } from 'node:child_process'; + +import { getPackagesSync } from '@easyflow/node-utils'; + +const { packages } = getPackagesSync(); + +const allowedScopes = [ + ...packages.map((pkg) => pkg.packageJson.name), + 'project', + 'style', + 'lint', + 'ci', + 'dev', + 'deploy', + 'other', +]; + +// precomputed scope +const scopeComplete = execSync('git status --porcelain || true') + .toString() + .trim() + .split('\n') + .find((r) => ~r.indexOf('M src')) + ?.replace(/(\/)/g, '%%') + ?.match(/src%%((\w|-)*)/)?.[1] + ?.replace(/s$/, ''); + +/** + * @type {import('cz-git').UserConfig} + */ +const userConfig = { + extends: ['@commitlint/config-conventional'], + plugins: ['commitlint-plugin-function-rules'], + prompt: { + /** @use `pnpm commit :f` */ + alias: { + b: 'build: bump dependencies', + c: 'chore: update config', + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + }, + allowCustomIssuePrefixs: false, + // scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: false, + customScopesAlign: scopeComplete ? 'bottom' : 'top', + defaultScope: scopeComplete, + // English + typesAppend: [ + { name: 'workflow: workflow improvements', value: 'workflow' }, + { name: 'types: type definition file changes', value: 'types' }, + ], + + // 中英文对照版 + // messages: { + // type: '选择你要提交的类型 :', + // scope: '选择一个提交范围 (可选):', + // customScope: '请输入自定义的提交范围 :', + // subject: '填写简短精炼的变更描述 :\n', + // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', + // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', + // footerPrefixsSelect: '选择关联issue前缀 (可选):', + // customFooterPrefixs: '输入自定义issue前缀 :', + // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + // confirmCommit: '是否提交或修改commit ?', + // }, + // types: [ + // { value: 'feat', name: 'feat: 新增功能' }, + // { value: 'fix', name: 'fix: 修复缺陷' }, + // { value: 'docs', name: 'docs: 文档变更' }, + // { value: 'style', name: 'style: 代码格式' }, + // { value: 'refactor', name: 'refactor: 代码重构' }, + // { value: 'perf', name: 'perf: 性能优化' }, + // { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, + // { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, + // { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, + // { value: 'revert', name: 'revert: 回滚 commit' }, + // { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, + // { value: 'wip', name: 'wip: 正在开发中' }, + // { value: 'workflow', name: 'workflow: 工作流程改进' }, + // { value: 'types', name: 'types: 类型定义文件修改' }, + // ], + // emptyScopesAlias: 'empty: 不填写', + // customScopesAlias: 'custom: 自定义', + }, + rules: { + /** + * type[scope]: [function] description + * + * ^^^^^^^^^^^^^^ empty line. + * - Something here + */ + 'body-leading-blank': [2, 'always'], + /** + * type[scope]: [function] description + * + * - something here + * + * ^^^^^^^^^^^^^^ + */ + 'footer-leading-blank': [1, 'always'], + /** + * type[scope]: [function] description + * ^^^^^ + */ + 'function-rules/scope-enum': [ + 2, // level: error + 'always', + (parsed) => { + if (!parsed.scope || allowedScopes.includes(parsed.scope)) { + return [true]; + } + + return [false, `scope must be one of ${allowedScopes.join(', ')}`]; + }, + ], + /** + * type[scope]: [function] description [No more than 108 characters] + * ^^^^^ + */ + 'header-max-length': [2, 'always', 108], + + 'scope-enum': [0], + 'subject-case': [0], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + /** + * type[scope]: [function] description + * ^^^^ + */ + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'perf', + 'style', + 'docs', + 'test', + 'refactor', + 'build', + 'ci', + 'chore', + 'revert', + 'types', + 'release', + ], + ], + }, +}; + +export default userConfig; diff --git a/easyflow-ui-admin/internal/lint-configs/commitlint-config/package.json b/easyflow-ui-admin/internal/lint-configs/commitlint-config/package.json new file mode 100644 index 0000000..b21e0f3 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/commitlint-config/package.json @@ -0,0 +1,26 @@ +{ + "name": "@easyflow/commitlint-config", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@commitlint/cli": "catalog:", + "@commitlint/config-conventional": "catalog:", + "@easyflow/node-utils": "workspace:*", + "commitlint-plugin-function-rules": "catalog:", + "cz-git": "catalog:", + "czg": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/build.config.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/package.json b/easyflow-ui-admin/internal/lint-configs/eslint-config/package.json new file mode 100644 index 0000000..f72b95f --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/package.json @@ -0,0 +1,49 @@ +{ + "name": "@easyflow/eslint-config", + "version": "5.0.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "dependencies": { + "eslint-config-turbo": "catalog:", + "eslint-plugin-command": "catalog:", + "eslint-plugin-import-x": "catalog:" + }, + "devDependencies": { + "@eslint/js": "catalog:", + "@types/eslint": "catalog:", + "@typescript-eslint/eslint-plugin": "catalog:", + "@typescript-eslint/parser": "catalog:", + "eslint": "catalog:", + "eslint-plugin-eslint-comments": "catalog:", + "eslint-plugin-jsdoc": "catalog:", + "eslint-plugin-jsonc": "catalog:", + "eslint-plugin-n": "catalog:", + "eslint-plugin-no-only-tests": "catalog:", + "eslint-plugin-perfectionist": "catalog:", + "eslint-plugin-prettier": "catalog:", + "eslint-plugin-regexp": "catalog:", + "eslint-plugin-unicorn": "catalog:", + "eslint-plugin-unused-imports": "catalog:", + "eslint-plugin-vitest": "catalog:", + "eslint-plugin-vue": "catalog:", + "globals": "catalog:", + "jsonc-eslint-parser": "catalog:", + "vue-eslint-parser": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/command.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/command.ts new file mode 100644 index 0000000..67651b2 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/command.ts @@ -0,0 +1,10 @@ +import createCommand from 'eslint-plugin-command/config'; + +export async function command() { + return [ + { + // @ts-expect-error - no types + ...createCommand(), + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/comments.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/comments.ts new file mode 100644 index 0000000..77ccd5d --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/comments.ts @@ -0,0 +1,24 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function comments(): Promise { + const [pluginComments] = await Promise.all([ + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-eslint-comments')), + ] as const); + + return [ + { + plugins: { + 'eslint-comments': pluginComments, + }, + rules: { + 'eslint-comments/no-aggregating-enable': 'error', + 'eslint-comments/no-duplicate-disable': 'error', + 'eslint-comments/no-unlimited-disable': 'error', + 'eslint-comments/no-unused-enable': 'error', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/disableds.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/disableds.ts new file mode 100644 index 0000000..152b84c --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/disableds.ts @@ -0,0 +1,28 @@ +import type { Linter } from 'eslint'; + +export async function disableds(): Promise { + return [ + { + files: ['**/__tests__/**/*.?([cm])[jt]s?(x)'], + name: 'disables/test', + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + 'no-console': 'off', + }, + }, + { + files: ['**/*.d.ts'], + name: 'disables/dts', + rules: { + '@typescript-eslint/triple-slash-reference': 'off', + }, + }, + { + files: ['**/*.js', '**/*.mjs', '**/*.cjs'], + name: 'disables/js', + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/ignores.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/ignores.ts new file mode 100644 index 0000000..136c956 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/ignores.ts @@ -0,0 +1,52 @@ +import type { Linter } from 'eslint'; + +export async function ignores(): Promise { + return [ + { + ignores: [ + '**/node_modules', + '**/dist', + '**/dist-*', + '**/*-dist', + '**/.husky', + '**/.nitro', + '**/.output', + '**/Dockerfile', + '**/package-lock.json', + '**/yarn.lock', + '**/pnpm-lock.yaml', + '**/bun.lockb', + '**/output', + '**/coverage', + '**/temp', + '**/.temp', + '**/tmp', + '**/.tmp', + '**/.history', + '**/.turbo', + '**/.nuxt', + '**/.next', + '**/.vercel', + '**/.changeset', + '**/.idea', + '**/.cache', + '**/.output', + '**/.vite-inspect', + + '**/CHANGELOG*.md', + '**/*.min.*', + '**/LICENSE*', + '**/__snapshots__', + '**/*.snap', + '**/fixtures/**', + '**/.vitepress/cache/**', + '**/auto-import?(s).d.ts', + '**/components.d.ts', + '**/vite.config.mts.*', + '**/*.sh', + '**/*.ttf', + '**/*.woff', + ], + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/import.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/import.ts new file mode 100644 index 0000000..ce6cf65 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/import.ts @@ -0,0 +1,25 @@ +import type { Linter } from 'eslint'; + +import * as pluginImport from 'eslint-plugin-import-x'; + +export async function importPluginConfig(): Promise { + return [ + { + plugins: { + // @ts-expect-error - This is a dynamic import + import: pluginImport, + }, + rules: { + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-named-default': 'error', + 'import/no-self-import': 'error', + 'import/no-unresolved': 'off', + 'import/no-webpack-loader-syntax': 'error', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/index.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/index.ts new file mode 100644 index 0000000..c0284ef --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/index.ts @@ -0,0 +1,17 @@ +export * from './command'; +export * from './comments'; +export * from './disableds'; +export * from './ignores'; +export * from './import'; +export * from './javascript'; +export * from './jsdoc'; +export * from './jsonc'; +export * from './node'; +export * from './perfectionist'; +export * from './prettier'; +export * from './regexp'; +export * from './test'; +export * from './turbo'; +export * from './typescript'; +export * from './unicorn'; +export * from './vue'; diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/javascript.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/javascript.ts new file mode 100644 index 0000000..44cf5b6 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -0,0 +1,241 @@ +import type { Linter } from 'eslint'; + +import js from '@eslint/js'; +import pluginUnusedImports from 'eslint-plugin-unused-imports'; +import globals from 'globals'; + +export async function javascript(): Promise { + return [ + { + languageOptions: { + ecmaVersion: 'latest', + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + sourceType: 'module', + }, + linterOptions: { + reportUnusedDisableDirectives: true, + }, + plugins: { + 'unused-imports': pluginUnusedImports, + }, + rules: { + ...js.configs.recommended.rules, + 'accessor-pairs': [ + 'error', + { enforceForClassMembers: true, setWithoutGet: true }, + ], + 'array-callback-return': 'error', + 'block-scoped-var': 'error', + 'constructor-super': 'error', + 'default-case-last': 'error', + 'dot-notation': ['error', { allowKeywords: true }], + eqeqeq: ['error', 'always'], + 'keyword-spacing': 'off', + + 'new-cap': [ + 'error', + { capIsNew: false, newIsCap: true, properties: true }, + ], + 'no-alert': 'error', + 'no-array-constructor': 'error', + 'no-async-promise-executor': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'always'], + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'no-const-assign': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-empty-character-class': 'error', + 'no-empty-function': 'off', + 'no-empty-pattern': 'error', + 'no-eval': 'error', + 'no-ex-assign': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-boolean-cast': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-implied-eval': 'error', + 'no-import-assign': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-object': 'error', + 'no-new-symbol': 'error', + 'no-new-wrappers': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-proto': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': ['error', { builtinGlobals: false }], + 'no-regex-spaces': 'error', + 'no-restricted-globals': [ + 'error', + { message: 'Use `globalThis` instead.', name: 'global' }, + { message: 'Use `globalThis` instead.', name: 'self' }, + ], + 'no-restricted-properties': [ + 'error', + { + message: + 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', + property: '__proto__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineGetter__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineSetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupGetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupSetter__', + }, + ], + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + 'TSEnumDeclaration[const=true]', + 'TSExportAssignment', + ], + 'no-self-assign': ['error', { props: true }], + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-this-before-super': 'error', + 'no-throw-literal': 'error', + 'no-undef': 'off', + 'no-undef-init': 'error', + 'no-unexpected-multiline': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + 'no-unreachable': 'error', + 'no-unreachable-loop': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTaggedTemplates: true, + allowTernary: true, + }, + ], + 'no-unused-vars': [ + 'error', + { + args: 'none', + caughtErrors: 'none', + ignoreRestSiblings: true, + vars: 'all', + }, + ], + 'no-use-before-define': [ + 'error', + { classes: false, functions: false, variables: false }, + ], + 'no-useless-backreference': 'error', + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'no-with': 'error', + 'object-shorthand': [ + 'error', + 'always', + { avoidQuotes: true, ignoreConstructors: false }, + ], + 'one-var': ['error', { initialized: 'never' }], + 'prefer-arrow-callback': [ + 'error', + { + allowNamedFunctions: false, + allowUnboundThis: true, + }, + ], + 'prefer-const': [ + 'error', + { + destructuring: 'all', + ignoreReadBeforeAssign: true, + }, + ], + 'prefer-exponentiation-operator': 'error', + + 'prefer-promise-reject-errors': 'error', + 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'space-before-function-paren': 'off', + 'spaced-comment': 'error', + 'symbol-description': 'error', + 'unicode-bom': ['error', 'never'], + + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + args: 'after-used', + argsIgnorePattern: '^_', + vars: 'all', + varsIgnorePattern: '^_', + }, + ], + 'use-isnan': [ + 'error', + { enforceForIndexOf: true, enforceForSwitchCase: true }, + ], + 'valid-typeof': ['error', { requireStringLiterals: true }], + + 'vars-on-top': 'error', + yoda: ['error', 'never'], + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsdoc.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsdoc.ts new file mode 100644 index 0000000..1368197 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsdoc.ts @@ -0,0 +1,34 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsdoc(): Promise { + const [pluginJsdoc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsdoc')), + ] as const); + + return [ + { + plugins: { + jsdoc: pluginJsdoc, + }, + rules: { + 'jsdoc/check-access': 'warn', + 'jsdoc/check-param-names': 'warn', + 'jsdoc/check-property-names': 'warn', + 'jsdoc/check-types': 'warn', + 'jsdoc/empty-tags': 'warn', + 'jsdoc/implements-on-classes': 'warn', + 'jsdoc/no-defaults': 'warn', + 'jsdoc/no-multi-asterisks': 'warn', + 'jsdoc/require-param-name': 'warn', + 'jsdoc/require-property': 'warn', + 'jsdoc/require-property-description': 'warn', + 'jsdoc/require-property-name': 'warn', + 'jsdoc/require-returns-check': 'warn', + 'jsdoc/require-returns-description': 'warn', + 'jsdoc/require-yields-check': 'warn', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsonc.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsonc.ts new file mode 100644 index 0000000..4072e4c --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/jsonc.ts @@ -0,0 +1,258 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsonc(): Promise { + const [pluginJsonc, parserJsonc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsonc')), + interopDefault(import('jsonc-eslint-parser')), + ] as const); + + return [ + { + files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'], + languageOptions: { + parser: parserJsonc as any, + }, + plugins: { + jsonc: pluginJsonc as any, + }, + rules: { + 'jsonc/no-bigint-literals': 'error', + 'jsonc/no-binary-expression': 'error', + 'jsonc/no-binary-numeric-literals': 'error', + 'jsonc/no-dupe-keys': 'error', + 'jsonc/no-escape-sequence-in-identifier': 'error', + 'jsonc/no-floating-decimal': 'error', + 'jsonc/no-hexadecimal-numeric-literals': 'error', + 'jsonc/no-infinity': 'error', + 'jsonc/no-multi-str': 'error', + 'jsonc/no-nan': 'error', + 'jsonc/no-number-props': 'error', + 'jsonc/no-numeric-separators': 'error', + 'jsonc/no-octal': 'error', + 'jsonc/no-octal-escape': 'error', + 'jsonc/no-octal-numeric-literals': 'error', + 'jsonc/no-parenthesized': 'error', + 'jsonc/no-plus-sign': 'error', + 'jsonc/no-regexp-literals': 'error', + 'jsonc/no-sparse-arrays': 'error', + 'jsonc/no-template-literals': 'error', + 'jsonc/no-undefined-value': 'error', + 'jsonc/no-unicode-codepoint-escapes': 'error', + 'jsonc/no-useless-escape': 'error', + 'jsonc/space-unary-ops': 'error', + 'jsonc/valid-json-number': 'error', + 'jsonc/vue-custom-block/no-parsing-error': 'error', + }, + }, + sortTsconfig(), + sortPackageJson(), + ]; +} + +function sortPackageJson(): Linter.Config { + return { + files: ['**/package.json'], + rules: { + 'jsonc/sort-array-values': [ + 'error', + { + order: { type: 'asc' }, + pathPattern: '^files$|^pnpm.neverBuiltDependencies$', + }, + ], + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'name', + 'version', + 'description', + 'private', + 'keywords', + 'homepage', + 'bugs', + 'repository', + 'license', + 'author', + 'contributors', + 'categories', + 'funding', + 'type', + 'scripts', + 'files', + 'sideEffects', + 'bin', + 'main', + 'module', + 'unpkg', + 'jsdelivr', + 'types', + 'typesVersions', + 'imports', + 'exports', + 'publishConfig', + 'icon', + 'activationEvents', + 'contributes', + 'peerDependencies', + 'peerDependenciesMeta', + 'dependencies', + 'optionalDependencies', + 'devDependencies', + 'engines', + 'packageManager', + 'pnpm', + 'overrides', + 'resolutions', + 'husky', + 'simple-git-hooks', + 'lint-staged', + 'eslintConfig', + ], + pathPattern: '^$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', + }, + { + order: ['types', 'import', 'require', 'default'], + pathPattern: '^exports.*$', + }, + ], + }, + }; +} + +function sortTsconfig(): Linter.Config { + return { + files: [ + '**/tsconfig.json', + '**/tsconfig.*.json', + 'internal/tsconfig/*.json', + ], + rules: { + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'extends', + 'compilerOptions', + 'references', + 'files', + 'include', + 'exclude', + ], + pathPattern: '^$', + }, + { + order: [ + /* Projects */ + 'incremental', + 'composite', + 'tsBuildInfoFile', + 'disableSourceOfProjectReferenceRedirect', + 'disableSolutionSearching', + 'disableReferencedProjectLoad', + /* Language and Environment */ + 'target', + 'jsx', + 'jsxFactory', + 'jsxFragmentFactory', + 'jsxImportSource', + 'lib', + 'moduleDetection', + 'noLib', + 'reactNamespace', + 'useDefineForClassFields', + 'emitDecoratorMetadata', + 'experimentalDecorators', + /* Modules */ + 'baseUrl', + 'rootDir', + 'rootDirs', + 'customConditions', + 'module', + 'moduleResolution', + 'moduleSuffixes', + 'noResolve', + 'paths', + 'resolveJsonModule', + 'resolvePackageJsonExports', + 'resolvePackageJsonImports', + 'typeRoots', + 'types', + 'allowArbitraryExtensions', + 'allowImportingTsExtensions', + 'allowUmdGlobalAccess', + /* JavaScript Support */ + 'allowJs', + 'checkJs', + 'maxNodeModuleJsDepth', + /* Type Checking */ + 'strict', + 'strictBindCallApply', + 'strictFunctionTypes', + 'strictNullChecks', + 'strictPropertyInitialization', + 'allowUnreachableCode', + 'allowUnusedLabels', + 'alwaysStrict', + 'exactOptionalPropertyTypes', + 'noFallthroughCasesInSwitch', + 'noImplicitAny', + 'noImplicitOverride', + 'noImplicitReturns', + 'noImplicitThis', + 'noPropertyAccessFromIndexSignature', + 'noUncheckedIndexedAccess', + 'noUnusedLocals', + 'noUnusedParameters', + 'useUnknownInCatchVariables', + /* Emit */ + 'declaration', + 'declarationDir', + 'declarationMap', + 'downlevelIteration', + 'emitBOM', + 'emitDeclarationOnly', + 'importHelpers', + 'importsNotUsedAsValues', + 'inlineSourceMap', + 'inlineSources', + 'mapRoot', + 'newLine', + 'noEmit', + 'noEmitHelpers', + 'noEmitOnError', + 'outDir', + 'outFile', + 'preserveConstEnums', + 'preserveValueImports', + 'removeComments', + 'sourceMap', + 'sourceRoot', + 'stripInternal', + /* Interop Constraints */ + 'allowSyntheticDefaultImports', + 'esModuleInterop', + 'forceConsistentCasingInFileNames', + 'isolatedModules', + 'preserveSymlinks', + 'verbatimModuleSyntax', + /* Completeness */ + 'skipDefaultLibCheck', + 'skipLibCheck', + ], + pathPattern: '^compilerOptions$', + }, + ], + }, + }; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/node.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/node.ts new file mode 100644 index 0000000..320e29c --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/node.ts @@ -0,0 +1,57 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function node(): Promise { + const pluginNode = await interopDefault(import('eslint-plugin-n')); + + return [ + { + plugins: { + n: pluginNode, + }, + rules: { + 'n/handle-callback-err': ['error', '^(err|error)$'], + 'n/no-deprecated-api': 'error', + 'n/no-exports-assign': 'error', + 'n/no-extraneous-import': [ + 'error', + { + allowModules: [ + 'unbuild', + '@easyflow/vite-config', + 'vitest', + 'vite', + '@vue/test-utils', + '@easyflow/tailwind-config', + '@playwright/test', + ], + }, + ], + 'n/no-new-require': 'error', + 'n/no-path-concat': 'error', + // 'n/no-unpublished-import': 'off', + 'n/no-unsupported-features/es-syntax': [ + 'error', + { + ignores: [], + version: '>=18.0.0', + }, + ], + 'n/prefer-global/buffer': ['error', 'never'], + // 'n/no-missing-import': 'off', + 'n/prefer-global/process': ['error', 'never'], + 'n/process-exit-as-throw': 'error', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'n/prefer-global/process': 'off', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/perfectionist.ts new file mode 100644 index 0000000..c9d38e1 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/perfectionist.ts @@ -0,0 +1,89 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function perfectionist(): Promise { + const perfectionistPlugin = await interopDefault( + // @ts-expect-error - no types + import('eslint-plugin-perfectionist'), + ); + + return [ + perfectionistPlugin.configs['recommended-natural'], + { + rules: { + 'perfectionist/sort-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-imports': [ + 'error', + { + customGroups: { + type: { + 'easyflow-core-type': ['^@easyflow-core/.+'], + 'easyflow-type': ['^@easyflow/.+'], + 'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'], + }, + value: { + easyflow: ['^@easyflow/.+'], + 'easyflow-core': ['^@easyflow-core/.+'], + vue: ['^vue$', '^vue-.+', '^@vue/.+'], + }, + }, + environment: 'node', + groups: [ + ['external-type', 'builtin-type', 'type'], + 'vue-type', + 'easyflow-type', + 'easyflow-core-type', + ['parent-type', 'sibling-type', 'index-type'], + ['internal-type'], + 'builtin', + 'vue', + 'easyflow', + 'easyflow-core', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + 'side-effect', + 'side-effect-style', + 'style', + 'object', + 'unknown', + ], + internalPattern: ['^#/.+'], + newlinesBetween: 'always', + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-modules': 'off', + 'perfectionist/sort-named-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-objects': [ + 'off', + { + customGroups: { + items: 'items', + list: 'list', + children: 'children', + }, + groups: ['unknown', 'items', 'list', 'children'], + ignorePattern: ['children'], + order: 'asc', + type: 'natural', + }, + ], + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/prettier.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/prettier.ts new file mode 100644 index 0000000..3cd7af4 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/prettier.ts @@ -0,0 +1,19 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function prettier(): Promise { + const [pluginPrettier] = await Promise.all([ + interopDefault(import('eslint-plugin-prettier')), + ] as const); + return [ + { + plugins: { + prettier: pluginPrettier, + }, + rules: { + 'prettier/prettier': 'error', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/regexp.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/regexp.ts new file mode 100644 index 0000000..c0f4c9f --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/regexp.ts @@ -0,0 +1,20 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function regexp(): Promise { + const [pluginRegexp] = await Promise.all([ + interopDefault(import('eslint-plugin-regexp')), + ] as const); + + return [ + { + plugins: { + regexp: pluginRegexp, + }, + rules: { + ...pluginRegexp.configs.recommended.rules, + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/test.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/test.ts new file mode 100644 index 0000000..ddfde2b --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/test.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function test(): Promise { + const [pluginTest, pluginNoOnlyTests] = await Promise.all([ + interopDefault(import('eslint-plugin-vitest')), + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-no-only-tests')), + ] as const); + + return [ + { + files: [ + `**/__tests__/**/*.?([cm])[jt]s?(x)`, + `**/*.spec.?([cm])[jt]s?(x)`, + `**/*.test.?([cm])[jt]s?(x)`, + `**/*.bench.?([cm])[jt]s?(x)`, + `**/*.benchmark.?([cm])[jt]s?(x)`, + ], + plugins: { + test: { + ...pluginTest, + rules: { + ...pluginTest.rules, + ...pluginNoOnlyTests.rules, + }, + }, + }, + rules: { + 'no-console': 'off', + 'node/prefer-global/process': 'off', + 'test/consistent-test-it': [ + 'error', + { fn: 'it', withinDescribe: 'it' }, + ], + 'test/no-identical-title': 'error', + 'test/no-import-node-test': 'error', + 'test/no-only-tests': 'error', + 'test/prefer-hooks-in-order': 'error', + 'test/prefer-lowercase-title': 'error', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/turbo.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/turbo.ts new file mode 100644 index 0000000..9f6bf75 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/turbo.ts @@ -0,0 +1,18 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function turbo(): Promise { + const [pluginTurbo] = await Promise.all([ + // @ts-expect-error - no types + interopDefault(import('eslint-config-turbo')), + ] as const); + + return [ + { + plugins: { + turbo: pluginTurbo, + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/typescript.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/typescript.ts new file mode 100644 index 0000000..cff9aa4 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/typescript.ts @@ -0,0 +1,72 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function typescript(): Promise { + const [pluginTs, parserTs] = await Promise.all([ + interopDefault(import('@typescript-eslint/eslint-plugin')), + // @ts-expect-error missing types + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + return [ + { + files: ['**/*.?([cm])[jt]s?(x)'], + languageOptions: { + parser: parserTs, + parserOptions: { + createDefaultProgram: false, + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + extraFileExtensions: ['.vue'], + jsxPragma: 'React', + project: './tsconfig.*.json', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': pluginTs, + }, + rules: { + ...pluginTs.configs['eslint-recommended'].overrides?.[0].rules, + ...pluginTs.configs.strict.rules, + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-check': false, + 'ts-expect-error': 'allow-with-description', + 'ts-ignore': 'allow-with-description', + 'ts-nocheck': 'allow-with-description', + }, + ], + + // '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-function': [ + 'error', + { + allow: ['arrowFunctions', 'functions', 'methods'], + }, + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-var-requires': 'error', + 'unused-imports/no-unused-vars': 'off', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/unicorn.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/unicorn.ts new file mode 100644 index 0000000..21b1902 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/unicorn.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function unicorn(): Promise { + const [pluginUnicorn] = await Promise.all([ + interopDefault(import('eslint-plugin-unicorn')), + ] as const); + + return [ + { + plugins: { + unicorn: pluginUnicorn, + }, + rules: { + ...pluginUnicorn.configs.recommended.rules, + + 'unicorn/better-regex': 'off', + 'unicorn/consistent-destructuring': 'off', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/expiring-todo-comments': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/import-style': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-at': 'off', + 'unicorn/prefer-dom-node-text-content': 'off', + 'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }], + 'unicorn/prefer-global-this': 'off', + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'unicorn/no-process-exit': 'off', + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/vue.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/vue.ts new file mode 100644 index 0000000..a64c55a --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/configs/vue.ts @@ -0,0 +1,153 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function vue(): Promise { + const [pluginVue, parserVue, parserTs] = await Promise.all([ + interopDefault(import('eslint-plugin-vue')), + interopDefault(import('vue-eslint-parser')), + // @ts-expect-error missing types + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + const flatEssential = pluginVue.configs?.['flat/essential'] || []; + const flatStronglyRecommended = + pluginVue.configs?.['flat/strongly-recommended'] || []; + const flatRecommended = pluginVue.configs?.['flat/recommended'] || []; + + return [ + ...flatEssential, + ...flatStronglyRecommended, + ...flatRecommended, + { + files: ['**/*.vue'], + languageOptions: { + // globals: { + // computed: 'readonly', + // defineEmits: 'readonly', + // defineExpose: 'readonly', + // defineProps: 'readonly', + // onMounted: 'readonly', + // onUnmounted: 'readonly', + // reactive: 'readonly', + // ref: 'readonly', + // shallowReactive: 'readonly', + // shallowRef: 'readonly', + // toRef: 'readonly', + // toRefs: 'readonly', + // watch: 'readonly', + // watchEffect: 'readonly', + // }, + parser: parserVue, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + extraFileExtensions: ['.vue'], + parser: parserTs, + sourceType: 'module', + }, + }, + plugins: { + vue: pluginVue, + }, + processor: pluginVue.processors?.['.vue'], + rules: { + ...pluginVue.configs?.base?.rules, + + 'vue/attribute-hyphenation': [ + 'error', + 'always', + { + ignore: [], + }, + ], + 'vue/attributes-order': 'off', + 'vue/block-order': [ + 'error', + { + order: ['script', 'template', 'style'], + }, + ], + 'vue/component-name-in-template-casing': ['error', 'PascalCase'], + 'vue/component-options-name-casing': ['error', 'PascalCase'], + 'vue/custom-event-name-casing': ['error', 'camelCase'], + 'vue/define-macros-order': [ + 'error', + { + order: [ + 'defineOptions', + 'defineProps', + 'defineEmits', + 'defineSlots', + ], + }, + ], + 'vue/dot-location': ['error', 'property'], + 'vue/dot-notation': ['error', { allowKeywords: true }], + 'vue/eqeqeq': ['error', 'smart'], + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-indent': 'off', + // 'vue/html-indent': ['error', 2], + 'vue/html-quotes': ['error', 'double'], + 'vue/html-self-closing': [ + 'error', + { + html: { + component: 'always', + normal: 'never', + void: 'always', + }, + math: 'always', + svg: 'always', + }, + ], + 'vue/max-attributes-per-line': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/no-empty-pattern': 'error', + 'vue/no-extra-parens': ['error', 'functions'], + 'vue/no-irregular-whitespace': 'error', + 'vue/no-loss-of-precision': 'error', + 'vue/no-reserved-component-names': 'off', + 'vue/no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'vue/no-restricted-v-bind': ['error', '/^v-/'], + 'vue/no-sparse-arrays': 'error', + 'vue/no-unused-refs': 'error', + 'vue/no-useless-v-bind': 'error', + 'vue/object-shorthand': [ + 'error', + 'always', + { + avoidQuotes: true, + ignoreConstructors: false, + }, + ], + 'vue/one-component-per-file': 'error', + 'vue/prefer-import-from-vue': 'error', + 'vue/prefer-separate-static-class': 'error', + 'vue/prefer-template': 'error', + 'vue/prop-name-casing': ['error', 'camelCase'], + 'vue/require-default-prop': 'error', + 'vue/require-explicit-emits': 'error', + 'vue/require-prop-types': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/space-infix-ops': 'error', + 'vue/space-unary-ops': ['error', { nonwords: false, words: true }], + 'vue/v-on-event-hyphenation': [ + 'error', + 'always', + { + autofix: true, + ignore: [], + }, + ], + }, + }, + ]; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/custom-config.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/custom-config.ts new file mode 100644 index 0000000..9596854 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/custom-config.ts @@ -0,0 +1,156 @@ +import type { Linter } from 'eslint'; + +const restrictedImportIgnores = [ + '**/vite.config.mts', + '**/tailwind.config.mjs', + '**/postcss.config.mjs', +]; + +const customConfig: Linter.Config[] = [ + // shadcn-ui 内部组件是自动生成的,不做太多限制 + { + files: ['packages/@core/ui-kit/shadcn-ui/**/**'], + rules: { + 'vue/require-default-prop': 'off', + }, + }, + { + files: [ + 'app/**', + 'packages/effects/**/**', + 'packages/utils/**/**', + 'packages/types/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-interfaces': 'off', + 'perfectionist/sort-objects': 'off', + }, + }, + { + files: ['**/**.vue'], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-objects': 'off', + }, + }, + { + // apps内部的一些基础规则 + files: ['app/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['#/api/*'], + message: + 'The #/api package cannot be imported, please use the @core package itself', + }, + { + group: ['#/layouts/*'], + message: + 'The #/layouts package cannot be imported, please use the @core package itself', + }, + { + group: ['#/locales/*'], + message: + 'The #/locales package cannot be imported, please use the @core package itself', + }, + { + group: ['#/stores/*'], + message: + 'The #/stores package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + 'perfectionist/sort-interfaces': 'off', + }, + }, + { + // @core内部组件,不能引入@easyflow/* 里面的包 + files: ['packages/@core/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@easyflow/*'], + message: + 'The @core package cannot import the @easyflow package, please use the @core package itself', + }, + ], + }, + ], + }, + }, + { + // @core/shared内部组件,不能引入@easyflow/* 或者 @easyflow-core/* 里面的包 + files: ['packages/@core/base/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@easyflow/*', '@easyflow-core/*'], + message: + 'The @easyflow-core/shared package cannot import the @easyflow package, please use the @core/shared package itself', + }, + ], + }, + ], + }, + }, + + { + // 不能引入@easyflow/*里面的包 + files: [ + 'packages/types/**/**', + 'packages/utils/**/**', + 'packages/icons/**/**', + 'packages/constants/**/**', + 'packages/styles/**/**', + 'packages/stores/**/**', + 'packages/preferences/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@easyflow/*'], + message: + 'The @easyflow package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + }, + }, + { + files: ['**/**/playwright.config.ts'], + rules: { + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + }, + }, + { + files: ['internal/**/**', 'scripts/**/**'], + rules: { + 'no-console': 'off', + }, + }, +]; + +export { customConfig }; diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/index.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/index.ts new file mode 100644 index 0000000..c9f08bd --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/index.ts @@ -0,0 +1,60 @@ +import type { Linter } from 'eslint'; + +import { + command, + comments, + disableds, + ignores, + importPluginConfig, + javascript, + jsdoc, + jsonc, + node, + perfectionist, + prettier, + regexp, + test, + turbo, + typescript, + unicorn, + vue, +} from './configs'; +import { customConfig } from './custom-config'; + +type FlatConfig = Linter.Config; + +type FlatConfigPromise = + | FlatConfig + | FlatConfig[] + | Promise + | Promise; + +async function defineConfig(config: FlatConfig[] = []) { + const configs: FlatConfigPromise[] = [ + vue(), + javascript(), + ignores(), + prettier(), + typescript(), + jsonc(), + disableds(), + importPluginConfig(), + node(), + perfectionist(), + comments(), + jsdoc(), + unicorn(), + test(), + regexp(), + command(), + turbo(), + ...customConfig, + ...config, + ]; + + const resolved = await Promise.all(configs); + + return resolved.flat(); +} + +export { defineConfig }; diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/src/util.ts b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/util.ts new file mode 100644 index 0000000..d1a10ad --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/src/util.ts @@ -0,0 +1,8 @@ +export type Awaitable = Promise | T; + +export async function interopDefault( + m: Awaitable, +): Promise { + const resolved = await m; + return (resolved as any).default || resolved; +} diff --git a/easyflow-ui-admin/internal/lint-configs/eslint-config/tsconfig.json b/easyflow-ui-admin/internal/lint-configs/eslint-config/tsconfig.json new file mode 100644 index 0000000..67470c9 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/eslint-config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/easyflow-ui-admin/internal/lint-configs/prettier-config/index.mjs b/easyflow-ui-admin/internal/lint-configs/prettier-config/index.mjs new file mode 100644 index 0000000..f6a20c8 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/prettier-config/index.mjs @@ -0,0 +1,18 @@ +export default { + endOfLine: 'auto', + overrides: [ + { + files: ['*.json5'], + options: { + quoteProps: 'preserve', + singleQuote: false, + }, + }, + ], + plugins: ['prettier-plugin-tailwindcss'], + printWidth: 80, + proseWrap: 'never', + semi: true, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/easyflow-ui-admin/internal/lint-configs/prettier-config/package.json b/easyflow-ui-admin/internal/lint-configs/prettier-config/package.json new file mode 100644 index 0000000..8aadae7 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/prettier-config/package.json @@ -0,0 +1,21 @@ +{ + "name": "@easyflow/prettier-config", + "version": "5.0.0", + "private": true, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "default": "./index.mjs" + } + }, + "dependencies": { + "prettier": "catalog:", + "prettier-plugin-tailwindcss": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/lint-configs/stylelint-config/index.mjs b/easyflow-ui-admin/internal/lint-configs/stylelint-config/index.mjs new file mode 100644 index 0000000..08ac823 --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/stylelint-config/index.mjs @@ -0,0 +1,141 @@ +export default { + extends: ['stylelint-config-standard', 'stylelint-config-recess-order'], + ignoreFiles: [ + '**/*.js', + '**/*.jsx', + '**/*.tsx', + '**/*.ts', + '**/*.json', + '**/*.md', + ], + overrides: [ + { + customSyntax: 'postcss-html', + files: ['*.(html|vue)', '**/*.(html|vue)'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'], + }, + ], + }, + }, + { + customSyntax: 'postcss-scss', + extends: [ + 'stylelint-config-recommended-scss', + 'stylelint-config-recommended-vue/scss', + ], + files: ['*.scss', '**/*.scss'], + }, + ], + plugins: [ + 'stylelint-order', + '@stylistic/stylelint-plugin', + 'stylelint-prettier', + 'stylelint-scss', + ], + rules: { + 'at-rule-no-deprecated': null, + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'font-family-no-missing-generic-family-keyword': null, + 'function-no-unknown': null, + 'import-notation': null, + 'media-feature-range-notation': null, + 'named-grid-areas-no-invalid': null, + 'no-descending-specificity': null, + 'no-empty-source': null, + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + name: 'supports', + type: 'at-rule', + }, + { + name: 'media', + type: 'at-rule', + }, + { + name: 'include', + type: 'at-rule', + }, + 'rules', + ], + { severity: 'error' }, + ], + 'prettier/prettier': true, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'scss/at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'scss/operator-no-newline-after': null, + 'selector-class-pattern': + '^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$', + + 'selector-not-notation': null, + }, +}; diff --git a/easyflow-ui-admin/internal/lint-configs/stylelint-config/package.json b/easyflow-ui-admin/internal/lint-configs/stylelint-config/package.json new file mode 100644 index 0000000..6d11b7e --- /dev/null +++ b/easyflow-ui-admin/internal/lint-configs/stylelint-config/package.json @@ -0,0 +1,36 @@ +{ + "name": "@easyflow/stylelint-config", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@stylistic/stylelint-plugin": "catalog:", + "stylelint-config-recess-order": "catalog:", + "stylelint-scss": "catalog:" + }, + "devDependencies": { + "postcss": "catalog:", + "postcss-html": "catalog:", + "postcss-scss": "catalog:", + "prettier": "catalog:", + "stylelint": "catalog:", + "stylelint-config-recommended": "catalog:", + "stylelint-config-recommended-scss": "catalog:", + "stylelint-config-recommended-vue": "catalog:", + "stylelint-config-standard": "catalog:", + "stylelint-order": "catalog:", + "stylelint-prettier": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/node-utils/build.config.ts b/easyflow-ui-admin/internal/node-utils/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/easyflow-ui-admin/internal/node-utils/package.json b/easyflow-ui-admin/internal/node-utils/package.json new file mode 100644 index 0000000..cfca07b --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/package.json @@ -0,0 +1,36 @@ +{ + "name": "@easyflow/node-utils", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@changesets/git": "catalog:", + "@manypkg/get-packages": "catalog:", + "chalk": "catalog:", + "consola": "catalog:", + "dayjs": "catalog:", + "execa": "catalog:", + "find-up": "catalog:", + "ora": "catalog:", + "pkg-types": "catalog:", + "prettier": "catalog:", + "rimraf": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/node-utils/src/__tests__/hash.test.ts b/easyflow-ui-admin/internal/node-utils/src/__tests__/hash.test.ts new file mode 100644 index 0000000..3851306 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/__tests__/hash.test.ts @@ -0,0 +1,52 @@ +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { generatorContentHash } from '../hash'; + +describe('generatorContentHash', () => { + it('should generate an MD5 hash for the content', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should generate an MD5 hash with specified length', () => { + const content = 'example content'; + const hashLength = 10; + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toHaveLength(hashLength); + }); + + it('should correctly generate the hash with specified length', () => { + const content = 'example content'; + const hashLength = 8; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex') + .slice(0, hashLength); + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toBe(expectedHash); + }); + + it('should return full hash if hash length parameter is not provided', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should handle empty content', () => { + const content = ''; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); +}); diff --git a/easyflow-ui-admin/internal/node-utils/src/__tests__/path.test.ts b/easyflow-ui-admin/internal/node-utils/src/__tests__/path.test.ts new file mode 100644 index 0000000..3bab5a1 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/__tests__/path.test.ts @@ -0,0 +1,67 @@ +// pathUtils.test.ts + +import { describe, expect, it } from 'vitest'; + +import { toPosixPath } from '../path'; + +describe('toPosixPath', () => { + // 测试 Windows 风格路径到 POSIX 风格路径的转换 + it('converts Windows-style paths to POSIX paths', () => { + const windowsPath = String.raw`C:\Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(windowsPath)).toBe(expectedPosixPath); + }); + + // 确认 POSIX 风格路径不会被改变 + it('leaves POSIX-style paths unchanged', () => { + const posixPath = '/home/user/file.txt'; + expect(toPosixPath(posixPath)).toBe(posixPath); + }); + + // 测试带有多个分隔符的路径 + it('converts paths with mixed separators', () => { + const mixedPath = String.raw`C:/Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(mixedPath)).toBe(expectedPosixPath); + }); + + // 测试空字符串 + it('handles empty strings', () => { + const emptyPath = ''; + expect(toPosixPath(emptyPath)).toBe(''); + }); + + // 测试仅包含分隔符的路径 + it('handles path with only separators', () => { + const separatorsPath = '\\\\\\'; + const expectedPosixPath = '///'; + expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath); + }); + + // 测试不包含任何分隔符的路径 + it('handles path without separators', () => { + const noSeparatorPath = 'file.txt'; + expect(toPosixPath(noSeparatorPath)).toBe('file.txt'); + }); + + // 测试以分隔符结尾的路径 + it('handles path ending with a separator', () => { + const endingSeparatorPath = 'C:\\Users\\Example\\'; + const expectedPosixPath = 'C:/Users/Example/'; + expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试以分隔符开头的路径 + it('handles path starting with a separator', () => { + const startingSeparatorPath = String.raw`\Users\Example`; + const expectedPosixPath = '/Users/Example'; + expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试包含非法字符的路径 + it('handles path with invalid characters', () => { + const invalidCharsPath = String.raw`C:\Us*?ers\Ex|file.txt`; + const expectedPosixPath = 'C:/Us*?ers/Ex|file.txt'; + expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath); + }); +}); diff --git a/easyflow-ui-admin/internal/node-utils/src/constants.ts b/easyflow-ui-admin/internal/node-utils/src/constants.ts new file mode 100644 index 0000000..71d8a6c --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/constants.ts @@ -0,0 +1,6 @@ +enum UNICODE { + FAILURE = '\u2716', // ✖ + SUCCESS = '\u2714', // ✔ +} + +export { UNICODE }; diff --git a/easyflow-ui-admin/internal/node-utils/src/date.ts b/easyflow-ui-admin/internal/node-utils/src/date.ts new file mode 100644 index 0000000..d36572d --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/date.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +dayjs.tz.setDefault('Asia/Shanghai'); + +const dateUtil = dayjs; + +export { dateUtil }; diff --git a/easyflow-ui-admin/internal/node-utils/src/fs.ts b/easyflow-ui-admin/internal/node-utils/src/fs.ts new file mode 100644 index 0000000..8eec357 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/fs.ts @@ -0,0 +1,39 @@ +import { promises as fs } from 'node:fs'; +import { dirname } from 'node:path'; + +export async function outputJSON( + filePath: string, + data: any, + spaces: number = 2, +) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + const jsonData = JSON.stringify(data, null, spaces); + await fs.writeFile(filePath, jsonData, 'utf8'); + } catch (error) { + console.error('Error writing JSON file:', error); + throw error; + } +} + +export async function ensureFile(filePath: string) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(filePath, '', { flag: 'a' }); + } catch (error) { + console.error('Error ensuring file:', error); + throw error; + } +} + +export async function readJSON(filePath: string) { + try { + const data = await fs.readFile(filePath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading JSON file:', error); + throw error; + } +} diff --git a/easyflow-ui-admin/internal/node-utils/src/git.ts b/easyflow-ui-admin/internal/node-utils/src/git.ts new file mode 100644 index 0000000..88f159c --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/git.ts @@ -0,0 +1,34 @@ +import path from 'node:path'; + +import { execa } from 'execa'; + +export * from '@changesets/git'; + +/** + * 获取暂存区文件 + */ +async function getStagedFiles(): Promise { + try { + const { stdout } = await execa('git', [ + '-c', + 'submodule.recurse=false', + 'diff', + '--staged', + '--diff-filter=ACMR', + '--name-only', + '--ignore-submodules', + '-z', + ]); + + let changedList = stdout ? stdout.replace(/\0$/, '').split('\0') : []; + changedList = changedList.map((item) => path.resolve(process.cwd(), item)); + const changedSet = new Set(changedList); + changedSet.delete(''); + return [...changedSet]; + } catch (error) { + console.error('Failed to get staged files:', error); + return []; + } +} + +export { getStagedFiles }; diff --git a/easyflow-ui-admin/internal/node-utils/src/hash.ts b/easyflow-ui-admin/internal/node-utils/src/hash.ts new file mode 100644 index 0000000..81f6b05 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/hash.ts @@ -0,0 +1,18 @@ +import { createHash } from 'node:crypto'; + +/** + * 生产基于内容的 hash,可自定义长度 + * @param content + * @param hashLSize + */ +function generatorContentHash(content: string, hashLSize?: number) { + const hash = createHash('md5').update(content, 'utf8').digest('hex'); + + if (hashLSize) { + return hash.slice(0, hashLSize); + } + + return hash; +} + +export { generatorContentHash }; diff --git a/easyflow-ui-admin/internal/node-utils/src/index.ts b/easyflow-ui-admin/internal/node-utils/src/index.ts new file mode 100644 index 0000000..963cb87 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/index.ts @@ -0,0 +1,19 @@ +export * from './constants'; +export * from './date'; +export * from './fs'; +export * from './git'; +export { getStagedFiles, add as gitAdd } from './git'; +export { generatorContentHash } from './hash'; +export * from './monorepo'; +export { toPosixPath } from './path'; +export { prettierFormat } from './prettier'; +export * from './spinner'; +export type { Package } from '@manypkg/get-packages'; +export { default as colors } from 'chalk'; +export { consola } from 'consola'; +export * from 'execa'; + +export { default as fs } from 'node:fs/promises'; + +export { type PackageJson, readPackageJSON } from 'pkg-types'; +export { rimraf } from 'rimraf'; diff --git a/easyflow-ui-admin/internal/node-utils/src/monorepo.ts b/easyflow-ui-admin/internal/node-utils/src/monorepo.ts new file mode 100644 index 0000000..b6373e7 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/monorepo.ts @@ -0,0 +1,46 @@ +import { dirname } from 'node:path'; + +import { + getPackages as getPackagesFunc, + getPackagesSync as getPackagesSyncFunc, +} from '@manypkg/get-packages'; +import { findUpSync } from 'find-up'; + +/** + * 查找大仓的根目录 + * @param cwd + */ +function findMonorepoRoot(cwd: string = process.cwd()) { + const lockFile = findUpSync('pnpm-lock.yaml', { + cwd, + type: 'file', + }); + return dirname(lockFile || ''); +} + +/** + * 获取大仓的所有包 + */ +function getPackagesSync() { + const root = findMonorepoRoot(); + return getPackagesSyncFunc(root); +} + +/** + * 获取大仓的所有包 + */ +async function getPackages() { + const root = findMonorepoRoot(); + + return await getPackagesFunc(root); +} + +/** + * 获取大仓指定的包 + */ +async function getPackage(pkgName: string) { + const { packages } = await getPackages(); + return packages.find((pkg) => pkg.packageJson.name === pkgName); +} + +export { findMonorepoRoot, getPackage, getPackages, getPackagesSync }; diff --git a/easyflow-ui-admin/internal/node-utils/src/path.ts b/easyflow-ui-admin/internal/node-utils/src/path.ts new file mode 100644 index 0000000..e625fd2 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/path.ts @@ -0,0 +1,11 @@ +import { posix } from 'node:path'; + +/** + * 将给定的文件路径转换为 POSIX 风格。 + * @param {string} pathname - 原始文件路径。 + */ +function toPosixPath(pathname: string) { + return pathname.split(`\\`).join(posix.sep); +} + +export { toPosixPath }; diff --git a/easyflow-ui-admin/internal/node-utils/src/prettier.ts b/easyflow-ui-admin/internal/node-utils/src/prettier.ts new file mode 100644 index 0000000..1e1525d --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/prettier.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs/promises'; + +import { format, getFileInfo, resolveConfig } from 'prettier'; + +async function prettierFormat(filepath: string) { + const prettierOptions = await resolveConfig(filepath, {}); + + const fileInfo = await getFileInfo(filepath); + + const input = await fs.readFile(filepath, 'utf8'); + const output = await format(input, { + ...prettierOptions, + parser: fileInfo.inferredParser as any, + }); + if (output !== input) { + await fs.writeFile(filepath, output, 'utf8'); + } + return output; +} + +export { prettierFormat }; diff --git a/easyflow-ui-admin/internal/node-utils/src/spinner.ts b/easyflow-ui-admin/internal/node-utils/src/spinner.ts new file mode 100644 index 0000000..13ad6a4 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/src/spinner.ts @@ -0,0 +1,26 @@ +import type { Ora } from 'ora'; + +import ora from 'ora'; + +interface SpinnerOptions { + failedText?: string; + successText?: string; + title: string; +} +export async function spinner( + { failedText, successText, title }: SpinnerOptions, + callback: () => Promise, +): Promise { + const loading: Ora = ora(title).start(); + + try { + const result = await callback(); + loading.succeed(successText || 'Success!'); + return result; + } catch (error) { + loading.fail(failedText || 'Failed!'); + throw error; + } finally { + loading.stop(); + } +} diff --git a/easyflow-ui-admin/internal/node-utils/tsconfig.json b/easyflow-ui-admin/internal/node-utils/tsconfig.json new file mode 100644 index 0000000..67470c9 --- /dev/null +++ b/easyflow-ui-admin/internal/node-utils/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/easyflow-ui-admin/internal/tailwind-config/build.config.ts b/easyflow-ui-admin/internal/tailwind-config/build.config.ts new file mode 100644 index 0000000..1f3c3c2 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/build.config.ts @@ -0,0 +1,10 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index', './src/postcss.config'], + rollup: { + emitCJS: true, + }, +}); diff --git a/easyflow-ui-admin/internal/tailwind-config/package.json b/easyflow-ui-admin/internal/tailwind-config/package.json new file mode 100644 index 0000000..cdf85a1 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/package.json @@ -0,0 +1,59 @@ +{ + "name": "@easyflow/tailwind-config", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "./dist/*", + "./*" + ] + } + }, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "./postcss": { + "types": "./src/postcss.config.ts", + "import": "./dist/postcss.config.mjs", + "require": "./dist/postcss.config.cjs", + "default": "./dist/postcss.config.mjs" + }, + "./*": "./*" + }, + "peerDependencies": { + "tailwindcss": "^3.4.3" + }, + "dependencies": { + "@iconify/json": "catalog:", + "@iconify/tailwind": "catalog:", + "@manypkg/get-packages": "catalog:", + "@tailwindcss/nesting": "catalog:", + "@tailwindcss/typography": "catalog:", + "autoprefixer": "catalog:", + "cssnano": "catalog:", + "postcss": "catalog:", + "postcss-antd-fixes": "catalog:", + "postcss-import": "catalog:", + "postcss-preset-env": "catalog:", + "tailwindcss": "catalog:", + "tailwindcss-animate": "catalog:" + }, + "devDependencies": { + "@types/postcss-import": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/tailwind-config/src/index.ts b/easyflow-ui-admin/internal/tailwind-config/src/index.ts new file mode 100644 index 0000000..ddab957 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/src/index.ts @@ -0,0 +1,266 @@ +import type { Config } from 'tailwindcss'; + +import path from 'node:path'; + +import { addDynamicIconSelectors } from '@iconify/tailwind'; +import { getPackagesSync } from '@manypkg/get-packages'; +import typographyPlugin from '@tailwindcss/typography'; +import animate from 'tailwindcss-animate'; + +import { enterAnimationPlugin } from './plugins/entry'; + +// import defaultTheme from 'tailwindcss/defaultTheme'; + +const { packages } = getPackagesSync(process.cwd()); + +const tailwindPackages: string[] = []; + +packages.forEach((pkg) => { + // apps目录下和 @easyflow-core/tailwind-ui 包需要使用到 tailwindcss ui + // if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) { + tailwindPackages.push(pkg.dir); + // } +}); + +const shadcnUiColors = { + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + hover: 'hsl(var(--accent-hover))', + lighter: 'has(val(--accent-lighter))', + }, + background: { + deep: 'hsl(var(--background-deep))', + DEFAULT: 'hsl(var(--background))', + }, + border: { + DEFAULT: 'hsl(var(--border))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + destructive: { + ...createColorsPalette('destructive'), + DEFAULT: 'hsl(var(--destructive))', + }, + + foreground: { + DEFAULT: 'hsl(var(--foreground))', + }, + + input: { + background: 'hsl(var(--input-background))', + DEFAULT: 'hsl(var(--input))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + primary: { + ...createColorsPalette('primary'), + DEFAULT: 'hsl(var(--primary))', + }, + + ring: 'hsl(var(--ring))', + secondary: { + DEFAULT: 'hsl(var(--secondary))', + desc: 'hsl(var(--secondary-desc))', + foreground: 'hsl(var(--secondary-foreground))', + }, +}; + +const customColors = { + green: { + ...createColorsPalette('green'), + foreground: 'hsl(var(--success-foreground))', + }, + header: { + DEFAULT: 'hsl(var(--header))', + }, + heavy: { + DEFAULT: 'hsl(var(--heavy))', + foreground: 'hsl(var(--heavy-foreground))', + }, + main: { + DEFAULT: 'hsl(var(--main))', + }, + overlay: { + content: 'hsl(var(--overlay-content))', + DEFAULT: 'hsl(var(--overlay))', + }, + red: { + ...createColorsPalette('red'), + foreground: 'hsl(var(--destructive-foreground))', + }, + sidebar: { + deep: 'hsl(var(--sidebar-deep))', + DEFAULT: 'hsl(var(--sidebar))', + }, + success: { + ...createColorsPalette('success'), + DEFAULT: 'hsl(var(--success))', + }, + warning: { + ...createColorsPalette('warning'), + DEFAULT: 'hsl(var(--warning))', + }, + yellow: { + ...createColorsPalette('yellow'), + foreground: 'hsl(var(--warning-foreground))', + }, +}; + +export default { + content: [ + './index.html', + ...tailwindPackages.map((item) => + path.join(item, 'src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}'), + ), + ], + darkMode: 'selector', + plugins: [ + animate, + typographyPlugin, + addDynamicIconSelectors(), + enterAnimationPlugin, + ], + prefix: '', + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, + extend: { + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'collapsible-down': 'collapsible-down 0.2s ease-in-out', + 'collapsible-up': 'collapsible-up 0.2s ease-in-out', + float: 'float 5s linear 0ms infinite', + }, + + animationDuration: { + '2000': '2000ms', + '3000': '3000ms', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + xl: 'calc(var(--radius) + 4px)', + }, + boxShadow: { + float: `0 6px 16px 0 rgb(0 0 0 / 8%), + 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%)`, + }, + colors: { + ...customColors, + ...shadcnUiColors, + }, + fontFamily: { + sans: [ + 'var(--font-family)', + // ...defaultTheme.fontFamily.sans + ], + }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--reka-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--reka-accordion-content-height)' }, + to: { height: '0' }, + }, + 'collapsible-down': { + from: { height: '0' }, + to: { height: 'var(--reka-collapsible-content-height)' }, + }, + 'collapsible-up': { + from: { height: 'var(--reka-collapsible-content-height)' }, + to: { height: '0' }, + }, + float: { + '0%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-20px)' }, + '100%': { transform: 'translateY(0)' }, + }, + }, + zIndex: { + '100': '100', + '1000': '1000', + }, + }, + }, + safelist: ['dark'], +} as Config; + +function createColorsPalette(name: string) { + // backgroundLightest: '#EFF6FF', // Tailwind CSS 默认的 `blue-50` + // backgroundLighter: '#DBEAFE', // Tailwind CSS 默认的 `blue-100` + // backgroundLight: '#BFDBFE', // Tailwind CSS 默认的 `blue-200` + // borderLight: '#93C5FD', // Tailwind CSS 默认的 `blue-300` + // border: '#60A5FA', // Tailwind CSS 默认的 `blue-400` + // main: '#3B82F6', // Tailwind CSS 默认的 `blue-500` + // hover: '#2563EB', // Tailwind CSS 默认的 `blue-600` + // active: '#1D4ED8', // Tailwind CSS 默认的 `blue-700` + // backgroundDark: '#1E40AF', // Tailwind CSS 默认的 `blue-800` + // backgroundDarker: '#1E3A8A', // Tailwind CSS 默认的 `blue-900` + // backgroundDarkest: '#172554', // Tailwind CSS 默认的 `blue-950` + + // • backgroundLightest (#EFF6FF): 适用于最浅的背景色,可能用于非常轻微的阴影或卡片的背景。 + // • backgroundLighter (#DBEAFE): 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + // • backgroundLight (#BFDBFE): 适用于浅色背景,可能用于输入框或表单区域的背景。 + // • borderLight (#93C5FD): 适用于浅色边框,可能用于输入框或卡片的边框。 + // • border (#60A5FA): 适用于普通边框,可能用于按钮或卡片的边框。 + // • main (#3B82F6): 适用于主要的主题色,通常用于按钮、链接或主要的强调色。 + // • hover (#2563EB): 适用于鼠标悬停状态下的颜色,例如按钮悬停时的背景色或边框色。 + // • active (#1D4ED8): 适用于激活状态下的颜色,例如按钮按下时的背景色或边框色。 + // • backgroundDark (#1E40AF): 适用于深色背景,可能用于主要按钮或深色卡片背景。 + // • backgroundDarker (#1E3A8A): 适用于更深的背景,通常用于头部导航栏或页脚。 + // • backgroundDarkest (#172554): 适用于最深的背景,可能用于非常深色的区域或极端对比色。 + + return { + 50: `hsl(var(--${name}-50))`, + 100: `hsl(var(--${name}-100))`, + 200: `hsl(var(--${name}-200))`, + 300: `hsl(var(--${name}-300))`, + 400: `hsl(var(--${name}-400))`, + 500: `hsl(var(--${name}-500))`, + 600: `hsl(var(--${name}-600))`, + 700: `hsl(var(--${name}-700))`, + // 800: `hsl(var(--${name}-800))`, + // 900: `hsl(var(--${name}-900))`, + // 950: `hsl(var(--${name}-950))`, + // 激活状态下的颜色,适用于按钮按下时的背景色或边框色。 + active: `hsl(var(--${name}-700))`, + // 浅色背景,适用于输入框或表单区域的背景。 + 'background-light': `hsl(var(--${name}-200))`, + // 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + 'background-lighter': `hsl(var(--${name}-100))`, + // 最浅的背景色,适用于非常轻微的阴影或卡片的背景。 + 'background-lightest': `hsl(var(--${name}-50))`, + // 适用于普通边框,可能用于按钮或卡片的边框。 + border: `hsl(var(--${name}-400))`, + // 浅色边框,适用于输入框或卡片的边框。 + 'border-light': `hsl(var(--${name}-300))`, + foreground: `hsl(var(--${name}-foreground))`, + // 鼠标悬停状态下的颜色,适用于按钮悬停时的背景色或边框色。 + hover: `hsl(var(--${name}-600))`, + // 主色文本 + text: `hsl(var(--${name}-500))`, + // 主色文本激活态 + 'text-active': `hsl(var(--${name}-700))`, + // 主色文本悬浮态 + 'text-hover': `hsl(var(--${name}-600))`, + }; +} diff --git a/easyflow-ui-admin/internal/tailwind-config/src/module.d.ts b/easyflow-ui-admin/internal/tailwind-config/src/module.d.ts new file mode 100644 index 0000000..a399653 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/src/module.d.ts @@ -0,0 +1,3 @@ +declare module '@tailwindcss/nesting' { + export default any; +} diff --git a/easyflow-ui-admin/internal/tailwind-config/src/plugins/entry.ts b/easyflow-ui-admin/internal/tailwind-config/src/plugins/entry.ts new file mode 100644 index 0000000..0d8e8ec --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/src/plugins/entry.ts @@ -0,0 +1,53 @@ +import plugin from 'tailwindcss/plugin.js'; + +const enterAnimationPlugin = plugin(({ addUtilities }) => { + const maxChild = 5; + const utilities: Record = {}; + for (let i = 1; i <= maxChild; i++) { + const baseDelay = 0.1; + const delay = `${baseDelay * i}s`; + + utilities[`.enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(50px)`, + }; + + utilities[`.enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(50px)`, + }; + + utilities[`.-enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(-50px)`, + }; + + utilities[`.-enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(-50px)`, + }; + } + + // 添加动画关键帧 + addUtilities(utilities); + addUtilities({ + '@keyframes enter-x-animation': { + to: { + opacity: '1', + transform: 'translateX(0)', + }, + }, + '@keyframes enter-y-animation': { + to: { + opacity: '1', + transform: 'translateY(0)', + }, + }, + }); +}); + +export { enterAnimationPlugin }; diff --git a/easyflow-ui-admin/internal/tailwind-config/src/postcss.config.ts b/easyflow-ui-admin/internal/tailwind-config/src/postcss.config.ts new file mode 100644 index 0000000..43b30b3 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/src/postcss.config.ts @@ -0,0 +1,15 @@ +import config from '.'; + +export default { + plugins: { + ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}), + // Specifying the config is not necessary in most cases, but it is included + autoprefixer: {}, + // 修复 element-plus 和 ant-design-vue 的样式和tailwindcss冲突问题 + 'postcss-antd-fixes': { prefixes: ['ant', 'el'] }, + 'postcss-import': {}, + 'postcss-preset-env': {}, + tailwindcss: { config }, + 'tailwindcss/nesting': {}, + }, +}; diff --git a/easyflow-ui-admin/internal/tailwind-config/tsconfig.json b/easyflow-ui-admin/internal/tailwind-config/tsconfig.json new file mode 100644 index 0000000..6edd220 --- /dev/null +++ b/easyflow-ui-admin/internal/tailwind-config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/node.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/easyflow-ui-admin/internal/tsconfig/base.json b/easyflow-ui-admin/internal/tsconfig/base.json new file mode 100644 index 0000000..7149378 --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/base.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "composite": false, + "target": "ESNext", + + "moduleDetection": "force", + "experimentalDecorators": true, + + "baseUrl": ".", + "module": "ESNext", + + "moduleResolution": "bundler", + "resolveJsonModule": true, + + "strict": true, + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + + "inlineSources": false, + "noEmit": true, + "removeComments": true, + "sourceMap": false, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "preserveWatchOutput": true + }, + "exclude": ["**/node_modules/**", "**/dist/**", "**/.turbo/**"] +} diff --git a/easyflow-ui-admin/internal/tsconfig/library.json b/easyflow-ui-admin/internal/tsconfig/library.json new file mode 100644 index 0000000..7a976f0 --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/library.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "declaration": true, + "noEmit": false + } +} diff --git a/easyflow-ui-admin/internal/tsconfig/node.json b/easyflow-ui-admin/internal/tsconfig/node.json new file mode 100644 index 0000000..31ce8f1 --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/node.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node Config", + "extends": "./base.json", + "compilerOptions": { + "composite": false, + "lib": ["ESNext"], + "baseUrl": "./", + "types": ["node"], + "noImplicitAny": true + } +} diff --git a/easyflow-ui-admin/internal/tsconfig/package.json b/easyflow-ui-admin/internal/tsconfig/package.json new file mode 100644 index 0000000..36942f3 --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/package.json @@ -0,0 +1,18 @@ +{ + "name": "@easyflow/tsconfig", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "files": [ + "base.json", + "library.json", + "node.json", + "web-app.json", + "web.json" + ], + "dependencies": { + "@easyflow/types": "workspace:*", + "vite": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/tsconfig/web-app.json b/easyflow-ui-admin/internal/tsconfig/web-app.json new file mode 100644 index 0000000..2a74f81 --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/web-app.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./web.json", + "compilerOptions": { + "types": ["vite/client", "@easyflow/types/global"] + } +} diff --git a/easyflow-ui-admin/internal/tsconfig/web.json b/easyflow-ui-admin/internal/tsconfig/web.json new file mode 100644 index 0000000..a4b60ce --- /dev/null +++ b/easyflow-ui-admin/internal/tsconfig/web.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Package", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "types": ["vite/client"], + "declaration": false + } +} diff --git a/easyflow-ui-admin/internal/vite-config/build.config.ts b/easyflow-ui-admin/internal/vite-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/easyflow-ui-admin/internal/vite-config/package.json b/easyflow-ui-admin/internal/vite-config/package.json new file mode 100644 index 0000000..623755f --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/package.json @@ -0,0 +1,52 @@ +{ + "name": "@easyflow/vite-config", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@intlify/unplugin-vue-i18n": "catalog:", + "@jspm/generator": "catalog:", + "archiver": "catalog:", + "cheerio": "catalog:", + "get-port": "catalog:", + "html-minifier-terser": "catalog:", + "nitropack": "catalog:", + "resolve.exports": "catalog:", + "vite-plugin-pwa": "catalog:", + "vite-plugin-vue-devtools": "catalog:" + }, + "devDependencies": { + "@pnpm/workspace.read-manifest": "catalog:", + "@types/archiver": "catalog:", + "@types/html-minifier-terser": "catalog:", + "@easyflow/node-utils": "workspace:*", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "dayjs": "catalog:", + "dotenv": "catalog:", + "rollup": "catalog:", + "rollup-plugin-visualizer": "catalog:", + "sass": "catalog:", + "vite": "catalog:", + "vite-plugin-compression": "catalog:", + "vite-plugin-dts": "catalog:", + "vite-plugin-html": "catalog:", + "vite-plugin-lazy-import": "catalog:" + } +} diff --git a/easyflow-ui-admin/internal/vite-config/src/config/application.ts b/easyflow-ui-admin/internal/vite-config/src/config/application.ts new file mode 100644 index 0000000..2e0e3fc --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/config/application.ts @@ -0,0 +1,126 @@ +import type { CSSOptions, UserConfig } from 'vite'; + +import type { DefineApplicationOptions } from '../typing'; + +import path, { relative } from 'node:path'; + +import { findMonorepoRoot } from '@easyflow/node-utils'; + +import { NodePackageImporter } from 'sass'; +import { defineConfig, loadEnv, mergeConfig } from 'vite'; + +import { defaultImportmapOptions, getDefaultPwaOptions } from '../options'; +import { loadApplicationPlugins } from '../plugins'; +import { loadAndConvertEnv } from '../utils/env'; +import { getCommonConfig } from './common'; + +function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { + return defineConfig(async (config) => { + const options = await userConfigPromise?.(config); + const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv(); + const { command, mode } = config; + const { application = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + const env = loadEnv(mode, root); + + const plugins = await loadApplicationPlugins({ + archiver: true, + archiverPluginOptions: {}, + compress: false, + compressTypes: ['brotli', 'gzip'], + devtools: true, + env, + extraAppConfig: true, + html: true, + i18n: true, + importmapOptions: defaultImportmapOptions, + injectAppLoading: true, + injectMetadata: true, + isBuild, + license: true, + mode, + nitroMock: !isBuild, + nitroMockOptions: {}, + print: !isBuild, + printInfoMap: { + // 品牌外链已隐藏 + 'EasyFlow Docs': '', + }, + pwa: true, + pwaOptions: getDefaultPwaOptions(appTitle), + vxeTableLazyImport: true, + ...envConfig, + ...application, + }); + + const { injectGlobalScss = true } = application; + + const applicationConfig: UserConfig = { + base, + build: { + rollupOptions: { + output: { + assetFileNames: '[ext]/[name]-[hash].[ext]', + chunkFileNames: 'js/[name]-[hash].js', + entryFileNames: 'jse/index-[name]-[hash].js', + }, + }, + target: 'es2015', + }, + css: createCssOptions(injectGlobalScss), + esbuild: { + drop: isBuild + ? [ + // 'console', + 'debugger', + ] + : [], + legalComments: 'none', + }, + plugins, + server: { + host: true, + port, + warmup: { + // 预热文件 + clientFiles: [ + './index.html', + './src/bootstrap.ts', + './src/{views,layouts,router,store,api,adapter}/*', + ], + }, + }, + }; + + const mergedCommonConfig = mergeConfig( + await getCommonConfig(), + applicationConfig, + ); + return mergeConfig(mergedCommonConfig, vite); + }); +} + +function createCssOptions(injectGlobalScss = true): CSSOptions { + const root = findMonorepoRoot(); + return { + preprocessorOptions: injectGlobalScss + ? { + scss: { + additionalData: (content: string, filepath: string) => { + const relativePath = relative(root, filepath); + // apps下的包注入全局样式 + if (relativePath.startsWith(`apps${path.sep}`)) { + return `@use "@easyflow/styles/global" as *;\n${content}`; + } + return content; + }, + api: 'modern', + importers: [new NodePackageImporter()], + }, + } + : {}, + }; +} + +export { defineApplicationConfig }; diff --git a/easyflow-ui-admin/internal/vite-config/src/config/common.ts b/easyflow-ui-admin/internal/vite-config/src/config/common.ts new file mode 100644 index 0000000..653f210 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/config/common.ts @@ -0,0 +1,13 @@ +import type { UserConfig } from 'vite'; + +async function getCommonConfig(): Promise { + return { + build: { + chunkSizeWarningLimit: 2000, + reportCompressedSize: false, + sourcemap: false, + }, + }; +} + +export { getCommonConfig }; diff --git a/easyflow-ui-admin/internal/vite-config/src/config/index.ts b/easyflow-ui-admin/internal/vite-config/src/config/index.ts new file mode 100644 index 0000000..d04a84a --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/config/index.ts @@ -0,0 +1,37 @@ +import type { DefineConfig } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { defineApplicationConfig } from './application'; +import { defineLibraryConfig } from './library'; + +export * from './application'; +export * from './library'; + +function defineConfig( + userConfigPromise?: DefineConfig, + type: 'application' | 'auto' | 'library' = 'auto', +) { + let projectType = type; + + // 根据包是否存在 index.html,自动判断类型 + if (projectType === 'auto') { + const htmlPath = join(process.cwd(), 'index.html'); + projectType = existsSync(htmlPath) ? 'application' : 'library'; + } + + switch (projectType) { + case 'application': { + return defineApplicationConfig(userConfigPromise); + } + case 'library': { + return defineLibraryConfig(userConfigPromise); + } + default: { + throw new Error(`Unsupported project type: ${projectType}`); + } + } +} + +export { defineConfig }; diff --git a/easyflow-ui-admin/internal/vite-config/src/config/library.ts b/easyflow-ui-admin/internal/vite-config/src/config/library.ts new file mode 100644 index 0000000..417df02 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/config/library.ts @@ -0,0 +1,59 @@ +import type { ConfigEnv, UserConfig } from 'vite'; + +import type { DefineLibraryOptions } from '../typing'; + +import { readPackageJSON } from '@easyflow/node-utils'; + +import { defineConfig, mergeConfig } from 'vite'; + +import { loadLibraryPlugins } from '../plugins'; +import { getCommonConfig } from './common'; + +function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) { + return defineConfig(async (config: ConfigEnv) => { + const options = await userConfigPromise?.(config); + const { command, mode } = config; + const { library = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + + const plugins = await loadLibraryPlugins({ + dts: false, + injectMetadata: true, + isBuild, + mode, + ...library, + }); + + const { dependencies = {}, peerDependencies = {} } = + await readPackageJSON(root); + + const externalPackages = [ + ...Object.keys(dependencies), + ...Object.keys(peerDependencies), + ]; + + const packageConfig: UserConfig = { + build: { + lib: { + entry: 'src/index.ts', + fileName: () => 'index.mjs', + formats: ['es'], + }, + rollupOptions: { + external: (id) => { + return externalPackages.some( + (pkg) => id === pkg || id.startsWith(`${pkg}/`), + ); + }, + }, + }, + plugins, + }; + const commonConfig = await getCommonConfig(); + const mergedConmonConfig = mergeConfig(commonConfig, packageConfig); + return mergeConfig(mergedConmonConfig, vite); + }); +} + +export { defineLibraryConfig }; diff --git a/easyflow-ui-admin/internal/vite-config/src/index.ts b/easyflow-ui-admin/internal/vite-config/src/index.ts new file mode 100644 index 0000000..352a323 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/index.ts @@ -0,0 +1,4 @@ +export * from './config'; +export * from './options'; +export * from './plugins'; +export { loadAndConvertEnv } from './utils/env'; diff --git a/easyflow-ui-admin/internal/vite-config/src/options.ts b/easyflow-ui-admin/internal/vite-config/src/options.ts new file mode 100644 index 0000000..a7ea588 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/options.ts @@ -0,0 +1,45 @@ +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +import type { ImportmapPluginOptions } from './typing'; + +const isDevelopment = process.env.NODE_ENV === 'development'; + +const getDefaultPwaOptions = (name: string): Partial => ({ + manifest: { + description: + 'EasyFlow Admin is a modern admin dashboard template based on Vue 3. ', + icons: [ + { + sizes: '192x192', + src: 'https://unpkg.com/@easyflow/static-source@0.1.7/source/pwa-icon-192.png', + type: 'image/png', + }, + { + sizes: '512x512', + src: 'https://unpkg.com/@easyflow/static-source@0.1.7/source/pwa-icon-512.png', + type: 'image/png', + }, + ], + name: `${name}${isDevelopment ? ' dev' : ''}`, + short_name: `${name}${isDevelopment ? ' dev' : ''}`, + }, +}); + +/** + * importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定 + */ +const defaultImportmapOptions: ImportmapPluginOptions = { + // 通过 Importmap CDN 方式引入, + // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 + defaultProvider: 'esm.sh', + importmap: [ + { name: 'vue' }, + { name: 'pinia' }, + { name: 'vue-router' }, + // { name: 'vue-i18n' }, + { name: 'dayjs' }, + { name: 'vue-demi' }, + ], +}; + +export { defaultImportmapOptions, getDefaultPwaOptions }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/archiver.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/archiver.ts new file mode 100644 index 0000000..8eec8a0 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/archiver.ts @@ -0,0 +1,75 @@ +import type { PluginOption } from 'vite'; + +import type { ArchiverPluginOptions } from '../typing'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; + +import archiver from 'archiver'; + +export const viteArchiverPlugin = ( + options: ArchiverPluginOptions = {}, +): PluginOption => { + return { + apply: 'build', + closeBundle: { + handler() { + const { name = 'dist', outputDir = '.' } = options; + + setTimeout(async () => { + const folderToZip = 'dist'; + + const zipOutputDir = join(process.cwd(), outputDir); + const zipOutputPath = join(zipOutputDir, `${name}.zip`); + try { + await fsp.mkdir(zipOutputDir, { recursive: true }); + } catch { + // ignore + } + + try { + await zipFolder(folderToZip, zipOutputPath); + console.log(`Folder has been zipped to: ${zipOutputPath}`); + } catch (error) { + console.error('Error zipping folder:', error); + } + }, 0); + }, + order: 'post', + }, + enforce: 'post', + name: 'vite:archiver', + }; +}; + +async function zipFolder( + folderPath: string, + outputPath: string, +): Promise { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(outputPath); + const archive = archiver('zip', { + zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率 + }); + + output.on('close', () => { + console.log( + `ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`, + ); + resolve(); + }); + + archive.on('error', (err) => { + reject(err); + }); + + archive.pipe(output); + + // 使用 directory 方法以流的方式压缩文件夹,减少内存消耗 + archive.directory(folderPath, false); + + // 流式处理完成 + archive.finalize(); + }); +} diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/extra-app-config.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/extra-app-config.ts new file mode 100644 index 0000000..694e1d0 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/extra-app-config.ts @@ -0,0 +1,92 @@ +import type { PluginOption } from 'vite'; + +import { + colors, + generatorContentHash, + readPackageJSON, +} from '@easyflow/node-utils'; + +import { loadEnv } from '../utils/env'; + +interface PluginOptions { + isBuild: boolean; + root: string; +} + +const GLOBAL_CONFIG_FILE_NAME = '_app.config.js'; +const APP_ADMIN_PRO_APP_CONF = '_APP_ADMIN_PRO_APP_CONF_'; + +/** + * 用于将配置文件抽离出来并注入到项目中 + * @returns + */ + +async function viteExtraAppConfigPlugin({ + isBuild, + root, +}: PluginOptions): Promise { + let publicPath: string; + let source: string; + + if (!isBuild) { + return; + } + + const { version = '' } = await readPackageJSON(root); + + return { + async configResolved(config) { + publicPath = ensureTrailingSlash(config.base); + source = await getConfigSource(); + }, + async generateBundle() { + try { + this.emitFile({ + fileName: GLOBAL_CONFIG_FILE_NAME, + source, + type: 'asset', + }); + + console.log(colors.cyan(`✨configuration file is build successfully!`)); + } catch (error) { + console.log( + colors.red( + `configuration file configuration file failed to package:\n${error}`, + ), + ); + } + }, + name: 'vite:extra-app-config', + async transformIndexHtml(html) { + const hash = `v=${version}-${generatorContentHash(source, 8)}`; + + const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`; + + return { + html, + tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }], + }; + }, + }; +} + +async function getConfigSource() { + const config = await loadEnv(); + const windowVariable = `window.${APP_ADMIN_PRO_APP_CONF}`; + // 确保变量不会被修改 + let source = `${windowVariable}=${JSON.stringify(config)};`; + source += ` + Object.freeze(${windowVariable}); + Object.defineProperty(window, "${APP_ADMIN_PRO_APP_CONF}", { + configurable: false, + writable: false, + }); + `.replaceAll(/\s/g, ''); + return source; +} + +function ensureTrailingSlash(path: string) { + return path.endsWith('/') ? path : `${path}/`; +} + +export { viteExtraAppConfigPlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/importmap.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/importmap.ts new file mode 100644 index 0000000..0ccda99 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/importmap.ts @@ -0,0 +1,245 @@ +/** + * 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能 + */ +import type { GeneratorOptions } from '@jspm/generator'; +import type { Plugin } from 'vite'; + +import { Generator } from '@jspm/generator'; +import { load } from 'cheerio'; +import { minify } from 'html-minifier-terser'; + +const DEFAULT_PROVIDER = 'jspm.io'; + +type pluginOptions = GeneratorOptions & { + debug?: boolean; + defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io'; + importmap?: Array<{ name: string; range?: string }>; +}; + +// async function getLatestVersionOfShims() { +// const result = await fetch('https://ga.jspm.io/npm:es-module-shims'); +// const version = result.text(); +// return version; +// } + +async function getShimsUrl(provide: string) { + // const version = await getLatestVersionOfShims(); + const version = '1.10.0'; + + const shimsSubpath = `dist/es-module-shims.js`; + const providerShimsMap: Record = { + 'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`, + // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`, + jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`, + + // 下面两个CDN不稳定,暂时不用 + 'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`, + }; + + return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER]; +} + +let generator: Generator; + +async function viteImportMapPlugin( + pluginOptions?: pluginOptions, +): Promise { + const { importmap } = pluginOptions || {}; + + let isSSR = false; + let isBuild = false; + let installed = false; + let installError: Error | null = null; + + const options: pluginOptions = Object.assign( + {}, + { + debug: false, + defaultProvider: 'jspm.io', + env: ['production', 'browser', 'module'], + importmap: [], + }, + pluginOptions, + ); + + generator = new Generator({ + ...options, + baseUrl: process.cwd(), + }); + + if (options?.debug) { + (async () => { + for await (const { message, type } of generator.logStream()) { + console.log(`${type}: ${message}`); + } + })(); + } + + const imports = options.inputMap?.imports ?? {}; + const scopes = options.inputMap?.scopes ?? {}; + const firstLayerKeys = Object.keys(scopes); + const inputMapScopes: string[] = []; + firstLayerKeys.forEach((key) => { + inputMapScopes.push(...Object.keys(scopes[key] || {})); + }); + const inputMapImports = Object.keys(imports); + + const allDepNames: string[] = [ + ...(importmap?.map((item) => item.name) || []), + ...inputMapImports, + ...inputMapScopes, + ]; + const depNames = new Set(allDepNames); + + const installDeps = importmap?.map((item) => ({ + range: item.range, + target: item.name, + })); + + return [ + { + async config(_, { command, isSsrBuild }) { + isBuild = command === 'build'; + isSSR = !!isSsrBuild; + }, + enforce: 'pre', + name: 'importmap:external', + resolveId(id) { + if (isSSR || !isBuild) { + return null; + } + + if (!depNames.has(id)) { + return null; + } + return { external: true, id }; + }, + }, + { + enforce: 'post', + name: 'importmap:install', + async resolveId() { + if (isSSR || !isBuild || installed) { + return null; + } + try { + installed = true; + await Promise.allSettled( + (installDeps || []).map((dep) => generator.install(dep)), + ); + } catch (error: any) { + installError = error; + installed = false; + } + return null; + }, + }, + { + buildEnd() { + // 未生成importmap时,抛出错误,防止被turbo缓存 + if (!installed && !isSSR) { + installError && console.error(installError); + throw new Error('Importmap installation failed.'); + } + }, + enforce: 'post', + name: 'importmap:html', + transformIndexHtml: { + async handler(html) { + if (isSSR || !isBuild) { + return html; + } + + const importmapJson = generator.getMap(); + + if (!importmapJson) { + return html; + } + + const esModuleShimsSrc = await getShimsUrl( + options.defaultProvider || DEFAULT_PROVIDER, + ); + + const resultHtml = await injectShimsToHtml( + html, + esModuleShimsSrc || '', + ); + html = await minify(resultHtml || html, { + collapseWhitespace: true, + minifyCSS: true, + minifyJS: true, + removeComments: false, + }); + + return { + html, + tags: [ + { + attrs: { + type: 'importmap', + }, + injectTo: 'head-prepend', + tag: 'script', + children: `${JSON.stringify(importmapJson)}`, + }, + ], + }; + }, + order: 'post', + }, + }, + ]; +} + +async function injectShimsToHtml(html: string, esModuleShimUrl: string) { + const $ = load(html); + + const $script = $(`script[type='module']`); + + if (!$script) { + return; + } + + const entry = $script.attr('src'); + + $script.removeAttr('type'); + $script.removeAttr('crossorigin'); + $script.removeAttr('src'); + $script.html(` +if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) { + self.importShim = function () { + const promise = new Promise((resolve, reject) => { + document.head.appendChild( + Object.assign(document.createElement('script'), { + src: '${esModuleShimUrl}', + crossorigin: 'anonymous', + async: true, + onload() { + if (!importShim.$proxy) { + resolve(importShim); + } else { + reject(new Error('No globalThis.importShim found:' + esModuleShimUrl)); + } + }, + onerror(error) { + reject(error); + }, + }), + ); + }); + importShim.$proxy = true; + return promise.then((importShim) => importShim(...arguments)); + }; +} + +var modules = ['${entry}']; +typeof importShim === 'function' + ? modules.forEach((moduleName) => importShim(moduleName)) + : modules.forEach((moduleName) => import(moduleName)); + `); + $('body').after($script); + $('head').remove(`script[type='module']`); + return $.html(); +} + +export { viteImportMapPlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/index.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/index.ts new file mode 100644 index 0000000..da08db4 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/index.ts @@ -0,0 +1,247 @@ +import type { PluginOption } from 'vite'; + +import type { + ApplicationPluginOptions, + CommonPluginOptions, + ConditionPlugin, + LibraryPluginOptions, +} from '../typing'; + +import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; +import viteVue from '@vitejs/plugin-vue'; +import viteVueJsx from '@vitejs/plugin-vue-jsx'; +import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer'; +import viteCompressPlugin from 'vite-plugin-compression'; +import viteDtsPlugin from 'vite-plugin-dts'; +import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html'; +import { VitePWA } from 'vite-plugin-pwa'; +import viteVueDevTools from 'vite-plugin-vue-devtools'; + +import { viteArchiverPlugin } from './archiver'; +import { viteExtraAppConfigPlugin } from './extra-app-config'; +import { viteImportMapPlugin } from './importmap'; +import { viteInjectAppLoadingPlugin } from './inject-app-loading'; +import { viteMetadataPlugin } from './inject-metadata'; +import { viteLicensePlugin } from './license'; +import { viteNitroMockPlugin } from './nitro-mock'; +import { vitePrintPlugin } from './print'; +import { viteVxeTableImportsPlugin } from './vxe-table'; + +/** + * 获取条件成立的 vite 插件 + * @param conditionPlugins + */ +async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) { + const plugins: PluginOption[] = []; + for (const conditionPlugin of conditionPlugins) { + if (conditionPlugin.condition) { + const realPlugins = await conditionPlugin.plugins(); + plugins.push(...realPlugins); + } + } + return plugins.flat(); +} + +/** + * 根据条件获取通用的vite插件 + */ +async function loadCommonPlugins( + options: CommonPluginOptions, +): Promise { + const { devtools, injectMetadata, isBuild, visualizer } = options; + return [ + { + condition: true, + plugins: () => [ + viteVue({ + script: { + defineModel: true, + // propsDestructure: true, + }, + }), + viteVueJsx(), + ], + }, + + { + condition: !isBuild && devtools, + plugins: () => [viteVueDevTools()], + }, + { + condition: injectMetadata, + plugins: async () => [await viteMetadataPlugin()], + }, + { + condition: isBuild && !!visualizer, + plugins: () => [viteVisualizerPlugin({ + filename: './node_modules/.cache/visualizer/stats.html', + gzipSize: true, + open: true, + })], + }, + ]; +} + +/** + * 根据条件获取应用类型的vite插件 + */ +async function loadApplicationPlugins( + options: ApplicationPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const env = options.env; + + const { + archiver, + archiverPluginOptions, + compress, + compressTypes, + extraAppConfig, + html, + i18n, + importmap, + importmapOptions, + injectAppLoading, + license, + nitroMock, + nitroMockOptions, + print, + printInfoMap, + pwa, + pwaOptions, + vxeTableLazyImport, + ...commonOptions + } = options; + + const commonPlugins = await loadCommonPlugins(commonOptions); + + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: i18n, + plugins: async () => { + return [ + viteVueI18nPlugin({ + compositionOnly: true, + fullInstall: true, + runtimeOnly: true, + }), + ]; + }, + }, + { + condition: print, + plugins: async () => { + return [await vitePrintPlugin({ infoMap: printInfoMap })]; + }, + }, + { + condition: vxeTableLazyImport, + plugins: async () => { + return [await viteVxeTableImportsPlugin()]; + }, + }, + { + condition: nitroMock, + plugins: async () => { + return [await viteNitroMockPlugin(nitroMockOptions)]; + }, + }, + + { + condition: injectAppLoading, + plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)], + }, + { + condition: license, + plugins: async () => [await viteLicensePlugin()], + }, + { + condition: pwa, + plugins: () => + VitePWA({ + injectRegister: false, + workbox: { + globPatterns: [], + }, + ...pwaOptions, + manifest: { + display: 'standalone', + start_url: '/', + theme_color: '#ffffff', + ...pwaOptions?.manifest, + }, + }), + }, + { + condition: isBuild && !!compress, + plugins: () => { + const compressPlugins: PluginOption[] = []; + if (compressTypes?.includes('brotli')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }), + ); + } + if (compressTypes?.includes('gzip')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }), + ); + } + return compressPlugins; + }, + }, + { + condition: !!html, + plugins: () => [viteHtmlPlugin({ minify: true })], + }, + { + condition: isBuild && importmap, + plugins: () => { + return [viteImportMapPlugin(importmapOptions)]; + }, + }, + { + condition: isBuild && extraAppConfig, + plugins: async () => [ + await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }), + ], + }, + { + condition: archiver, + plugins: async () => { + return [await viteArchiverPlugin(archiverPluginOptions)]; + }, + }, + ]); +} + +/** + * 根据条件获取库类型的vite插件 + */ +async function loadLibraryPlugins( + options: LibraryPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const { dts, ...commonOptions } = options; + const commonPlugins = await loadCommonPlugins(commonOptions); + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: isBuild && !!dts, + plugins: () => [viteDtsPlugin({ logLevel: 'error' })], + }, + ]); +} + +export { + loadApplicationPlugins, + loadLibraryPlugins, + viteArchiverPlugin, + viteCompressPlugin, + viteDtsPlugin, + viteHtmlPlugin, + viteVisualizerPlugin, + viteVxeTableImportsPlugin, +}; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/README.md b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/README.md new file mode 100644 index 0000000..8d2358f --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/README.md @@ -0,0 +1,3 @@ +# inject-app-loading + +用于在应用加载时显示加载动画的插件,可自行选择加载动画的样式。 diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html new file mode 100644 index 0000000..20a21fb --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html @@ -0,0 +1,107 @@ + +
+ +
<%= VITE_APP_TITLE %>
+
diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-progress.html b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-progress.html new file mode 100644 index 0000000..7dec81b --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading-progress.html @@ -0,0 +1,137 @@ + +
+
+
+ +
+ + + +
+
+

正在启动您的 AI 平台...

+
+
+
+
+
diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading.html b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading.html new file mode 100644 index 0000000..2895705 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/default-loading.html @@ -0,0 +1,113 @@ + +
+
+
<%= VITE_APP_TITLE %>
+
diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/index.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/index.ts new file mode 100644 index 0000000..6aa8862 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -0,0 +1,71 @@ +import type { PluginOption } from 'vite'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { readPackageJSON } from '@easyflow/node-utils'; + +/** + * 用于生成将loading样式注入到项目中 + * 为多app提供loading样式,无需在每个 app -> index.html单独引入 + */ +async function viteInjectAppLoadingPlugin( + isBuild: boolean, + env: Record = {}, + loadingTemplate = 'loading.html', +): Promise { + const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate); + const { version } = await readPackageJSON(process.cwd()); + const envRaw = isBuild ? 'prod' : 'dev'; + const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`; + + // 获取缓存的主题 + // 保证黑暗主题下,刷新页面时,loading也是黑暗主题 + const injectScript = ` + +`; + + if (!loadingHtml) { + return; + } + + return { + enforce: 'pre', + name: 'vite:inject-app-loading', + transformIndexHtml: { + handler(html) { + const re = //; + html = html.replace(re, `${injectScript}${loadingHtml}`); + return html; + }, + order: 'pre', + }, + }; +} + +/** + * 用于获取loading的html模板 + */ +async function getLoadingRawByHtmlTemplate(loadingTemplate: string) { + // 支持在app内自定义loading模板,模版参考default-loading.html即可 + let appLoadingPath = join(process.cwd(), loadingTemplate); + + if (!fs.existsSync(appLoadingPath)) { + const __dirname = fileURLToPath(new URL('.', import.meta.url)); + appLoadingPath = join(__dirname, './default-loading-progress.html'); + } + + return await fsp.readFile(appLoadingPath, 'utf8'); +} + +export { viteInjectAppLoadingPlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/inject-metadata.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-metadata.ts new file mode 100644 index 0000000..f2b66bc --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/inject-metadata.ts @@ -0,0 +1,111 @@ +import type { PluginOption } from 'vite'; + +import { + dateUtil, + findMonorepoRoot, + getPackages, + readPackageJSON, +} from '@easyflow/node-utils'; + +import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'; + +function resolvePackageVersion( + pkgsMeta: Record, + name: string, + value: string, + catalog: Record, +) { + if (value.includes('catalog:')) { + return catalog[name]; + } + + if (value.includes('workspace')) { + return pkgsMeta[name]; + } + + return value; +} + +async function resolveMonorepoDependencies() { + const { packages } = await getPackages(); + const manifest = await readWorkspaceManifest(findMonorepoRoot()); + const catalog = manifest?.catalog || {}; + + const resultDevDependencies: Record = {}; + const resultDependencies: Record = {}; + const pkgsMeta: Record = {}; + + for (const { packageJson } of packages) { + pkgsMeta[packageJson.name] = packageJson.version; + } + + for (const { packageJson } of packages) { + const { dependencies = {}, devDependencies = {} } = packageJson; + for (const [key, value] of Object.entries(dependencies)) { + resultDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + for (const [key, value] of Object.entries(devDependencies)) { + resultDevDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + } + return { + dependencies: resultDependencies, + devDependencies: resultDevDependencies, + }; +} + +/** + * 用于注入项目信息 + */ +async function viteMetadataPlugin( + root = process.cwd(), +): Promise { + const { author, description, homepage, license, version } = + await readPackageJSON(root); + + const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss'); + + return { + async config() { + const { dependencies, devDependencies } = + await resolveMonorepoDependencies(); + + const isAuthorObject = typeof author === 'object'; + const authorName = isAuthorObject ? author.name : author; + const authorEmail = isAuthorObject ? author.email : null; + const authorUrl = isAuthorObject ? author.url : null; + + return { + define: { + __APP_ADMIN_METADATA__: JSON.stringify({ + authorEmail, + authorName, + authorUrl, + buildTime, + dependencies, + description, + devDependencies, + homepage, + license, + version, + }), + 'import.meta.env.VITE_APP_VERSION': JSON.stringify(version), + }, + }; + }, + enforce: 'post', + name: 'vite:inject-metadata', + }; +} + +export { viteMetadataPlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/license.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/license.ts new file mode 100644 index 0000000..7a1939d --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/license.ts @@ -0,0 +1,63 @@ +import type { + NormalizedOutputOptions, + OutputBundle, + OutputChunk, +} from 'rollup'; +import type { PluginOption } from 'vite'; + +import { EOL } from 'node:os'; + +import { dateUtil, readPackageJSON } from '@easyflow/node-utils'; + +/** + * 用于注入版权信息 + * @returns + */ + +async function viteLicensePlugin( + root = process.cwd(), +): Promise { + const { + description = '', + homepage = '', + version = '', + } = await readPackageJSON(root); + + return { + apply: 'build', + enforce: 'post', + generateBundle: { + handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => { + const date = dateUtil().format('YYYY-MM-DD '); + const copyrightText = `/*! + * EasyFlow Admin + * Version: ${version} + * Author: easyflow + * Copyright (C) 2026 EasyFlow + * License: MIT License + * Description: ${description} + * Date Created: ${date} + * Homepage: ${homepage} + * Contact: fuhai999@gmail.com +*/ + `.trim(); + + for (const [, fileContent] of Object.entries(bundle)) { + if (fileContent.type === 'chunk' && fileContent.isEntry) { + const chunkContent = fileContent as OutputChunk; + // 插入版权信息 + const content = chunkContent.code; + const updatedContent = `${copyrightText}${EOL}${content}`; + + // 更新bundle + (fileContent as OutputChunk).code = updatedContent; + } + } + }, + order: 'post', + }, + name: 'vite:license', + }; +} + +export { viteLicensePlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/nitro-mock.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/nitro-mock.ts new file mode 100644 index 0000000..ae28f48 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/nitro-mock.ts @@ -0,0 +1,98 @@ +import type { PluginOption } from 'vite'; + +import type { NitroMockPluginOptions } from '../typing'; + +import { colors, consola, getPackage } from '@easyflow/node-utils'; + +import getPort from 'get-port'; +import { build, createDevServer, createNitro, prepare } from 'nitropack'; + +const hmrKeyRe = /^runtimeConfig\.|routeRules\./; + +export const viteNitroMockPlugin = ({ + mockServerPackage = '@easyflow/backend-mock', + port = 5320, + verbose = true, +}: NitroMockPluginOptions = {}): PluginOption => { + return { + async configureServer(server) { + const availablePort = await getPort({ port }); + if (availablePort !== port) { + return; + } + + const pkg = await getPackage(mockServerPackage); + if (!pkg) { + consola.log( + `Package ${mockServerPackage} not found. Skip mock server.`, + ); + return; + } + + runNitroServer(pkg.dir, port, verbose); + + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + consola.log( + ` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`, + ); + }; + }, + enforce: 'pre', + name: 'vite:mock-server', + }; +}; + +async function runNitroServer(rootDir: string, port: number, verbose: boolean) { + let nitro: any; + const reload = async () => { + if (nitro) { + consola.info('Restarting dev server...'); + if ('unwatch' in nitro.options._c12) { + await nitro.options._c12.unwatch(); + } + await nitro.close(); + } + nitro = await createNitro( + { + dev: true, + preset: 'nitro-dev', + rootDir, + }, + { + c12: { + async onUpdate({ getDiff, newConfig }) { + const diff = getDiff(); + if (diff.length === 0) { + return; + } + verbose && + consola.info( + `Nitro config updated:\n${diff + .map((entry) => ` ${entry.toString()}`) + .join('\n')}`, + ); + await (diff.every((e) => hmrKeyRe.test(e.key)) + ? nitro.updateConfig(newConfig.config) + : reload()); + }, + }, + watch: true, + }, + ); + nitro.hooks.hookOnce('restart', reload); + + const server = createDevServer(nitro); + await server.listen(port, { showURL: false }); + await prepare(nitro); + await build(nitro); + + if (verbose) { + console.log(''); + consola.success(colors.bold(colors.green('Nitro Mock Server started.'))); + } + }; + return await reload(); +} diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/print.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/print.ts new file mode 100644 index 0000000..20568e1 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/print.ts @@ -0,0 +1,28 @@ +import type { PluginOption } from 'vite'; + +import type { PrintPluginOptions } from '../typing'; + +import { colors } from '@easyflow/node-utils'; + +export const vitePrintPlugin = ( + options: PrintPluginOptions = {}, +): PluginOption => { + const { infoMap = {} } = options; + + return { + configureServer(server) { + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + for (const [key, value] of Object.entries(infoMap)) { + console.log( + ` ${colors.green('➜')} ${colors.bold(key)}: ${colors.cyan(value)}`, + ); + } + }; + }, + enforce: 'pre', + name: 'vite:print-info', + }; +}; diff --git a/easyflow-ui-admin/internal/vite-config/src/plugins/vxe-table.ts b/easyflow-ui-admin/internal/vite-config/src/plugins/vxe-table.ts new file mode 100644 index 0000000..3c107a7 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/plugins/vxe-table.ts @@ -0,0 +1,20 @@ +import type { PluginOption } from 'vite'; + +import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'; + +async function viteVxeTableImportsPlugin(): Promise { + return [ + lazyImport({ + resolvers: [ + VxeResolver({ + libraryName: 'vxe-table', + }), + VxeResolver({ + libraryName: 'vxe-pc-ui', + }), + ], + }), + ]; +} + +export { viteVxeTableImportsPlugin }; diff --git a/easyflow-ui-admin/internal/vite-config/src/typing.ts b/easyflow-ui-admin/internal/vite-config/src/typing.ts new file mode 100644 index 0000000..b459e64 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/typing.ts @@ -0,0 +1,343 @@ +import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; +import type { ConfigEnv, PluginOption, UserConfig } from 'vite'; +import type { PluginOptions } from 'vite-plugin-dts'; +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +/** + * ImportMap 配置接口 + * @description 用于配置模块导入映射,支持自定义导入路径和范围 + * @example + * ```typescript + * { + * imports: { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * }, + * scopes: { + * 'https://site.com/': { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * } + * } + * } + * ``` + */ +interface IImportMap { + /** 模块导入映射 */ + imports?: Record; + /** 作用域特定的导入映射 */ + scopes?: { + [scope: string]: Record; + }; +} + +/** + * 打印插件配置选项 + * @description 用于配置控制台打印信息 + */ +interface PrintPluginOptions { + /** + * 打印的数据映射 + * @description 键值对形式的数据,将在控制台打印 + * @example + * ```typescript + * { + * 'App Version': '1.0.0', + * 'Build Time': '2024-01-01' + * } + * ``` + */ + infoMap?: Record; +} + +/** + * Nitro Mock 插件配置选项 + * @description 用于配置 Nitro Mock 服务器的行为 + */ +interface NitroMockPluginOptions { + /** + * Mock 服务器包名 + * @default '@easyflow/nitro-mock' + */ + mockServerPackage?: string; + + /** + * Mock 服务端口 + * @default 3000 + */ + port?: number; + + /** + * 是否打印 Mock 日志 + * @default false + */ + verbose?: boolean; +} + +/** + * 归档插件配置选项 + * @description 用于配置构建产物的压缩归档 + */ +interface ArchiverPluginOptions { + /** + * 输出文件名 + * @default 'dist' + */ + name?: string; + /** + * 输出目录 + * @default '.' + */ + outputDir?: string; +} + +/** + * ImportMap 插件配置 + * @description 用于配置模块的 CDN 导入 + */ +interface ImportmapPluginOptions { + /** + * CDN 供应商 + * @default 'jspm.io' + * @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商 + */ + defaultProvider?: 'esm.sh' | 'jspm.io'; + /** + * ImportMap 配置数组 + * @description 配置需要从 CDN 导入的包 + * @example + * ```typescript + * [ + * { name: 'vue' }, + * { name: 'pinia', range: '^2.0.0' } + * ] + * ``` + */ + importmap?: Array<{ name: string; range?: string }>; + /** + * 手动配置 ImportMap + * @description 自定义 ImportMap 配置 + */ + inputMap?: IImportMap; +} + +/** + * 条件插件配置 + * @description 用于根据条件动态加载插件 + */ +interface ConditionPlugin { + /** + * 判断条件 + * @description 当条件为 true 时加载插件 + */ + condition?: boolean; + /** + * 插件对象 + * @description 返回插件数组或 Promise + */ + plugins: () => PluginOption[] | PromiseLike; +} + +/** + * 通用插件配置选项 + * @description 所有插件共用的基础配置 + */ +interface CommonPluginOptions { + /** + * 是否开启开发工具 + * @default false + */ + devtools?: boolean; + /** + * 环境变量 + * @description 自定义环境变量 + */ + env?: Record; + /** + * 是否注入元数据 + * @default true + */ + injectMetadata?: boolean; + /** + * 是否为构建模式 + * @default false + */ + isBuild?: boolean; + /** + * 构建模式 + * @default 'development' + */ + mode?: string; + /** + * 是否开启依赖分析 + * @default false + * @description 使用 rollup-plugin-visualizer 分析依赖 + */ + visualizer?: boolean | PluginVisualizerOptions; +} + +/** + * 应用插件配置选项 + * @description 用于配置应用构建时的插件选项 + */ +interface ApplicationPluginOptions extends CommonPluginOptions { + /** + * 是否开启压缩归档 + * @default false + * @description 开启后会在打包目录生成 zip 文件 + */ + archiver?: boolean; + /** + * 压缩归档插件配置 + * @description 配置压缩归档的行为 + */ + archiverPluginOptions?: ArchiverPluginOptions; + /** + * 是否开启压缩 + * @default false + * @description 支持 gzip 和 brotli 压缩 + */ + compress?: boolean; + /** + * 压缩类型 + * @default ['gzip'] + * @description 可选的压缩类型 + */ + compressTypes?: ('brotli' | 'gzip')[]; + /** + * 是否抽离配置文件 + * @default false + * @description 在构建时抽离配置文件 + */ + extraAppConfig?: boolean; + /** + * 是否开启 HTML 插件 + * @default true + */ + html?: boolean; + /** + * 是否开启国际化 + * @default false + */ + i18n?: boolean; + /** + * 是否开启 ImportMap CDN + * @default false + */ + importmap?: boolean; + /** + * ImportMap 插件配置 + */ + importmapOptions?: ImportmapPluginOptions; + /** + * 是否注入应用加载动画 + * @default true + */ + injectAppLoading?: boolean; + /** + * 是否注入全局 SCSS + * @default true + */ + injectGlobalScss?: boolean; + /** + * 是否注入版权信息 + * @default true + */ + license?: boolean; + /** + * 是否开启 Nitro Mock + * @default false + */ + nitroMock?: boolean; + /** + * Nitro Mock 插件配置 + */ + nitroMockOptions?: NitroMockPluginOptions; + /** + * 是否开启控制台打印 + * @default false + */ + print?: boolean; + /** + * 打印插件配置 + */ + printInfoMap?: PrintPluginOptions['infoMap']; + /** + * 是否开启 PWA + * @default false + */ + pwa?: boolean; + /** + * PWA 插件配置 + */ + pwaOptions?: Partial; + /** + * 是否开启 VXE Table 懒加载 + * @default false + */ + vxeTableLazyImport?: boolean; +} + +/** + * 库插件配置选项 + * @description 用于配置库构建时的插件选项 + */ +interface LibraryPluginOptions extends CommonPluginOptions { + /** + * 是否开启 DTS 输出 + * @default true + * @description 生成 TypeScript 类型声明文件 + */ + dts?: boolean | PluginOptions; +} + +/** + * 应用配置选项类型 + */ +type ApplicationOptions = ApplicationPluginOptions; + +/** + * 库配置选项类型 + */ +type LibraryOptions = LibraryPluginOptions; + +/** + * 应用配置定义函数类型 + * @description 用于定义应用构建配置 + */ +type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{ + /** 应用插件配置 */ + application?: ApplicationOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 库配置定义函数类型 + * @description 用于定义库构建配置 + */ +type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{ + /** 库插件配置 */ + library?: LibraryOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 配置定义类型 + * @description 应用或库的配置定义 + */ +type DefineConfig = DefineApplicationOptions | DefineLibraryOptions; + +export type { + ApplicationPluginOptions, + ArchiverPluginOptions, + CommonPluginOptions, + ConditionPlugin, + DefineApplicationOptions, + DefineConfig, + DefineLibraryOptions, + IImportMap, + ImportmapPluginOptions, + LibraryPluginOptions, + NitroMockPluginOptions, + PrintPluginOptions, +}; diff --git a/easyflow-ui-admin/internal/vite-config/src/utils/env.ts b/easyflow-ui-admin/internal/vite-config/src/utils/env.ts new file mode 100644 index 0000000..fbd91a6 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/src/utils/env.ts @@ -0,0 +1,110 @@ +import type { ApplicationPluginOptions } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { fs } from '@easyflow/node-utils'; + +import dotenv from 'dotenv'; + +const getBoolean = (value: string | undefined) => value === 'true'; + +const getString = (value: string | undefined, fallback: string) => + value ?? fallback; + +const getNumber = (value: string | undefined, fallback: number) => + Number(value) || fallback; + +/** + * 获取当前环境下生效的配置文件名 + */ +function getConfFiles() { + const script = process.env.npm_lifecycle_script as string; + const reg = /--mode ([\d_a-z]+)/; + const result = reg.exec(script); + let mode = 'production'; + if (result) { + mode = result[1] as string; + } + return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]; +} + +/** + * Get the environment variables starting with the specified prefix + * @param match prefix + * @param confFiles ext + */ +async function loadEnv>( + match = 'VITE_GLOB_', + confFiles = getConfFiles(), +) { + let envConfig = {}; + + for (const confFile of confFiles) { + try { + const confFilePath = join(process.cwd(), confFile); + if (existsSync(confFilePath)) { + const envPath = await fs.readFile(confFilePath, { + encoding: 'utf8', + }); + const env = dotenv.parse(envPath); + envConfig = { ...envConfig, ...env }; + } + } catch (error) { + console.error(`Error while parsing ${confFile}`, error); + } + } + const reg = new RegExp(`^(${match})`); + Object.keys(envConfig).forEach((key) => { + if (!reg.test(key)) { + Reflect.deleteProperty(envConfig, key); + } + }); + return envConfig as T; +} + +async function loadAndConvertEnv( + match = 'VITE_', + confFiles = getConfFiles(), +): Promise< + Partial & { + appTitle: string; + base: string; + port: number; + } +> { + const envConfig = await loadEnv(match, confFiles); + + const { + VITE_APP_TITLE, + VITE_ARCHIVER, + VITE_BASE, + VITE_COMPRESS, + VITE_DEVTOOLS, + VITE_INJECT_APP_LOADING, + VITE_NITRO_MOCK, + VITE_PORT, + VITE_PWA, + VITE_VISUALIZER, + } = envConfig; + + const compressTypes = (VITE_COMPRESS ?? '') + .split(',') + .filter((item) => item === 'brotli' || item === 'gzip'); + + return { + appTitle: getString(VITE_APP_TITLE, 'EasyFlow Admin'), + archiver: getBoolean(VITE_ARCHIVER), + base: getString(VITE_BASE, '/'), + compress: compressTypes.length > 0, + compressTypes, + devtools: getBoolean(VITE_DEVTOOLS), + injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING), + nitroMock: getBoolean(VITE_NITRO_MOCK), + port: getNumber(VITE_PORT, 5173), + pwa: getBoolean(VITE_PWA), + visualizer: getBoolean(VITE_VISUALIZER), + }; +} + +export { loadAndConvertEnv, loadEnv }; diff --git a/easyflow-ui-admin/internal/vite-config/tsconfig.json b/easyflow-ui-admin/internal/vite-config/tsconfig.json new file mode 100644 index 0000000..67470c9 --- /dev/null +++ b/easyflow-ui-admin/internal/vite-config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@easyflow/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/easyflow-ui-admin/lefthook.yml b/easyflow-ui-admin/lefthook.yml new file mode 100644 index 0000000..7280174 --- /dev/null +++ b/easyflow-ui-admin/lefthook.yml @@ -0,0 +1,76 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://lefthook.dev/configuration/ +# +# pre-push: +# jobs: +# - name: packages audit +# tags: +# - frontend +# - security +# run: yarn audit +# +# - name: gems audit +# tags: +# - backend +# - security +# run: bundle audit +# +# pre-commit: +# parallel: true +# jobs: +# - run: yarn eslint {staged_files} +# glob: "*.{js,ts,jsx,tsx}" +# +# - name: rubocop +# glob: "*.rb" +# exclude: +# - config/application.rb +# - config/routes.rb +# run: bundle exec rubocop --force-exclusion {all_files} +# +# - name: govet +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# +# - script: "hello.js" +# runner: node +# +# - script: "hello.go" +# runner: go run + +pre-commit: + parallel: true + commands: + code-workspace: + run: pnpm vsh code-workspace --auto-commit + lint-md: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} + glob: '*.md' + lint-vue: + run: pnpm prettier --write {staged_files} && pnpm eslint --cache --fix {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files} + glob: '*.vue' + lint-js: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm eslint --cache --fix {staged_files} + glob: '*.{js,jsx,ts,tsx}' + lint-style: + run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files} + glob: '*.{scss,less,styl,html,vue,css}' + lint-package: + run: pnpm prettier --cache --write {staged_files} + glob: 'package.json' + lint-json: + run: pnpm prettier --cache --write --parser json {staged_files} + glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}' + +post-merge: + commands: + install: + run: pnpm install + +commit-msg: + commands: + commitlint: + run: pnpm exec commitlint --edit $1 diff --git a/easyflow-ui-admin/package.json b/easyflow-ui-admin/package.json new file mode 100644 index 0000000..731959f --- /dev/null +++ b/easyflow-ui-admin/package.json @@ -0,0 +1,94 @@ +{ + "name": "easyflow-admin", + "version": "1.0.0", + "private": true, + "repository": "aiflowy/aiflowy.git", + "license": "MIT", + "type": "module", + "scripts": { + "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", + "build:analyze": "turbo build:analyze", + "build:docker": "./scripts/deploy/build-local-docker-image.sh", + "build:app": "pnpm run build --filter=@easyflow/app", + "changeset": "pnpm exec changeset", + "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell", + "check:circular": "vsh check-circular", + "check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress", + "check:dep": "vsh check-dep", + "check:type": "turbo run typecheck", + "clean": "node ./scripts/clean.mjs", + "commit": "czg", + "dev": "turbo-run dev", + "dev:app": "pnpm -F @easyflow/app run dev", + "format": "vsh lint --format", + "lint": "vsh lint", + "postinstall": "pnpm -r run stub --if-present", + "preinstall": "npx only-allow pnpm", + "preview": "turbo-run preview", + "publint": "vsh publint", + "reinstall": "pnpm clean --del-lock && pnpm install", + "test:unit": "vitest run --dom", + "test:e2e": "turbo run test:e2e", + "update:deps": "npx taze -r -w", + "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile", + "catalog": "pnpx codemod pnpm/catalog" + }, + "devDependencies": { + "@easyflow/commitlint-config": "workspace:*", + "@easyflow/eslint-config": "workspace:*", + "@easyflow/prettier-config": "workspace:*", + "@easyflow/stylelint-config": "workspace:*", + "@easyflow/tailwind-config": "workspace:*", + "@easyflow/tsconfig": "workspace:*", + "@easyflow/turbo-run": "workspace:*", + "@easyflow/vite-config": "workspace:*", + "@easyflow/vsh": "workspace:*", + "@changesets/changelog-github": "catalog:", + "@changesets/cli": "catalog:", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "@vue/test-utils": "catalog:", + "autoprefixer": "catalog:", + "cross-env": "catalog:", + "cspell": "catalog:", + "happy-dom": "catalog:", + "is-ci": "catalog:", + "lefthook": "catalog:", + "playwright": "catalog:", + "rimraf": "catalog:", + "tailwindcss": "catalog:", + "turbo": "catalog:", + "typescript": "catalog:", + "unbuild": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", + "vue": "^3.5.24", + "vue-tsc": "catalog:" + }, + "engines": { + "node": ">=20.10.0", + "pnpm": ">=9.12.0" + }, + "packageManager": "pnpm@10.14.0", + "pnpm": { + "peerDependencyRules": { + "allowedVersions": { + "eslint": "*" + } + }, + "overrides": { + "@ast-grep/napi": "catalog:", + "@ctrl/tinycolor": "catalog:", + "clsx": "catalog:", + "esbuild": "0.25.3", + "pinia": "catalog:", + "vue": "catalog:" + }, + "neverBuiltDependencies": [ + "canvas", + "node-gyp" + ] + } +} diff --git a/easyflow-ui-admin/packages/@core/README.md b/easyflow-ui-admin/packages/@core/README.md new file mode 100644 index 0000000..f5ac1f4 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/README.md @@ -0,0 +1,3 @@ +# @easyflow-core + +系统一些比较基础的SDK和UI组件库,该目录后续完善后,可能会迁移出去或者发布到npm,请勿将任何业务逻辑和业务包放在该目录。 diff --git a/easyflow-ui-admin/packages/@core/base/README.md b/easyflow-ui-admin/packages/@core/base/README.md new file mode 100644 index 0000000..cc745b4 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/README.md @@ -0,0 +1,5 @@ +# base + +基础共享包,请勿引入 workspace 依赖 + +- diff --git a/easyflow-ui-admin/packages/@core/base/design/package.json b/easyflow-ui-admin/packages/@core/base/design/package.json new file mode 100644 index 0000000..01a1f54 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/package.json @@ -0,0 +1,34 @@ +{ + "name": "@easyflow-core/design", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "scripts": { + "build": "pnpm vite build", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "src" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "exports": { + "./bem": { + "development": "./src/scss-bem/bem.scss", + "default": "./dist/bem.scss" + }, + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts", + "default": "./dist/design.css" + } + }, + "publishConfig": { + "exports": { + ".": { + "default": "./dist/index.mjs" + } + } + } +} diff --git a/easyflow-ui-admin/packages/@core/base/design/src/css/global.css b/easyflow-ui-admin/packages/@core/base/design/src/css/global.css new file mode 100644 index 0000000..fb32b21 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/src/css/global.css @@ -0,0 +1,170 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + *, + ::after, + ::before { + @apply border-border; + + box-sizing: border-box; + border-style: solid; + border-width: 0; + } + + html { + @apply text-foreground bg-background font-sans text-[100%]; + + font-variation-settings: normal; + line-height: 1.15; + text-size-adjust: 100%; + font-synthesis-weight: none; + scroll-behavior: smooth; + text-rendering: optimizelegibility; + -webkit-tap-highlight-color: transparent; + + /* -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; */ + } + + #app, + body, + html { + @apply size-full; + + /* scrollbar-gutter: stable; */ + } + + body { + min-height: 100vh; + + /* pointer-events: auto !important; */ + + /* overflow: overlay; */ + + /* -webkit-font-smoothing: antialiased; */ + + /* -moz-osx-font-smoothing: grayscale; */ + } + + a, + a:active, + a:hover, + a:link, + a:visited { + @apply no-underline; + } + + ::view-transition-new(root), + ::view-transition-old(root) { + @apply animate-none mix-blend-normal; + } + + ::view-transition-old(root) { + @apply z-[1]; + } + + ::view-transition-new(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-old(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-new(root) { + @apply z-[1]; + } + + input::placeholder, + textarea::placeholder { + @apply opacity-100; + } + + /* input:-webkit-autofill { + @apply border-none; + + box-shadow: 0 0 0 1000px transparent inset; + } */ + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + @apply m-0 appearance-none; + } + + /* 只有非mac下才进行调整,mac下使用默认滚动条 */ + html:not([data-platform='macOs']) { + ::-webkit-scrollbar { + @apply h-[10px] w-[10px]; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-sm border-none; + } + + ::-webkit-scrollbar-track { + @apply rounded-sm border-none bg-transparent shadow-none; + } + + ::-webkit-scrollbar-button { + @apply hidden; + } + } +} + +@layer components { + .flex-center { + @apply flex items-center justify-center; + } + + .flex-col-center { + @apply flex flex-col items-center justify-center; + } + + .outline-box { + @apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1; + } + + .outline-box::after { + @apply absolute left-1/2 top-1/2 z-20 h-0 w-[1px] rounded-sm opacity-0 outline outline-2 outline-transparent transition-all duration-300 content-[""]; + } + + .outline-box.outline-box-active { + @apply outline-primary outline outline-2; + } + + .outline-box.outline-box-active::after { + display: none; + } + + .outline-box:not(.outline-box-active):hover::after { + @apply outline-primary left-0 top-0 h-full w-full p-1 opacity-100; + } + + .easyflow-link { + @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer; + } + + .card-box { + @apply bg-card text-card-foreground border-border rounded-xl border; + } +} + +html.invert-mode { + @apply invert; +} + +html.grayscale-mode { + @apply grayscale; +} + +.page-container { + margin: 20px; + background-color: var(--el-bg-color); + border-radius: var(--el-border-radius-base); + padding: 20px; +} +.handle-div { + margin-bottom: 10px; +} diff --git a/easyflow-ui-admin/packages/@core/base/design/src/css/nprogress.css b/easyflow-ui-admin/packages/@core/base/design/src/css/nprogress.css new file mode 100644 index 0000000..3503dab --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/src/css/nprogress.css @@ -0,0 +1,59 @@ +/* Make clicks pass-through */ +#nprogress { + @apply pointer-events-none; +} + +#nprogress .bar { + @apply bg-primary fixed left-0 top-0 z-[1031] h-[2px] w-full; +} + +/* Fancy blur effect */ +#nprogress .peg { + @apply absolute right-0 block h-full w-[100px]; + + box-shadow: + 0 0 10px hsl(var(--primary)), + 0 0 5px hsl(var(--primary)); + opacity: 1; + transform: rotate(3deg) translate(0, -4px); +} + +/* Remove these to get rid of the spinner */ +#nprogress .spinner { + @apply fixed right-4 top-4 z-[1031] block; +} + +#nprogress .spinner-icon { + @apply border-t-primary border-l-primary size-4 rounded-full border-[2px] border-solid border-transparent; + + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + @apply relative overflow-hidden; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + @apply absolute; +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/easyflow-ui-admin/packages/@core/base/design/src/css/transition.css b/easyflow-ui-admin/packages/@core/base/design/src/css/transition.css new file mode 100644 index 0000000..c1cb0e4 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/src/css/transition.css @@ -0,0 +1,236 @@ +.slide-up-enter-active, +.slide-up-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-up-move { + transition: transform 0.3s; +} + +.slide-up-enter-from, +.slide-up-leave-to { + opacity: 0; + transform: translateY(-15px); +} + +.slide-down-enter-active, +.slide-down-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-down-move { + transition: transform 0.3s; +} + +.slide-down-enter-from, +.slide-down-leave-to { + opacity: 0; + transform: translateY(15px); +} + +.slide-left-enter-active, +.slide-left-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-left-move { + transition: transform 0.3s; +} + +.slide-left-enter-from, +.slide-left-leave-to { + opacity: 0; + transform: translate(-15px); +} + +.slide-right-enter-active, +.slide-right-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-right-move { + transition: transform 0.3s; +} + +.slide-right-enter-from, +.slide-right-leave-to { + opacity: 0; + transform: translate(15px); +} + +.fade-transition-enter-active, +.fade-transition-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-transition-enter-from, +.fade-transition-leave-to { + opacity: 0; +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translate(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translate(30px); +} + +.fade-down-enter-active, +.fade-down-leave-active { + transition: + opacity 0.25s, + transform 0.3s; +} + +.fade-down-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.fade-down-leave-to { + opacity: 0; + transform: translateY(10%); +} + +.fade-scale-leave-active, +.fade-scale-enter-active { + transition: all 0.28s; +} + +.fade-scale-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.fade-scale-leave-to { + opacity: 0; + transform: scale(0.8); +} + +.fade-up-enter-active, +.fade-up-leave-active { + transition: + opacity 0.2s, + transform 0.25s; +} + +.fade-up-enter-from { + opacity: 0; + transform: translateY(10%); +} + +.fade-up-leave-to { + opacity: 0; + transform: translateY(-10%); +} + +@keyframes fade-slide { + 0% { + opacity: 0; + transform: translate(-30px); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translate(30px); + } +} + +@keyframes fade { + 0% { + opacity: 0; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes fade-up { + 0% { + opacity: 0; + transform: translateY(10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(-10%); + } +} + +@keyframes fade-down { + 0% { + opacity: 0; + transform: translateY(-10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(10%); + } +} + +.fade-slow { + animation: fade 3s infinite; +} + +.fade-slide-slow { + animation: fade-slide 3s infinite; +} + +.fade-up-slow { + animation: fade-up 3s infinite; +} + +.fade-down-slow { + animation: fade-down 3s infinite; +} + +.collapse-transition { + transition: + 0.2s height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s padding-bottom ease-in-out; +} + +.collapse-transition-leave-active, +.collapse-transition-enter-active { + transition: + 0.2s max-height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s margin-top ease-in-out; +} diff --git a/easyflow-ui-admin/packages/@core/base/design/src/css/ui.css b/easyflow-ui-admin/packages/@core/base/design/src/css/ui.css new file mode 100644 index 0000000..a1bf024 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/src/css/ui.css @@ -0,0 +1,101 @@ +.side-content { + animation-duration: 0.3s; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); +} + +.side-content[data-side='top'] { + animation-name: slide-up; +} + +.side-content[data-side='bottom'] { + animation-name: slide-down; +} + +.side-content[data-side='left'] { + animation-name: slide-left; +} + +.side-content[data-side='right'] { + animation-name: slide-right; +} + +.breadcrumb-transition-enter-active { + transition: + transform 0.4s cubic-bezier(0.76, 0, 0.24, 1), + opacity 0.4s cubic-bezier(0.76, 0, 0.24, 1); +} + +.breadcrumb-transition-leave-active { + display: none; +} + +.breadcrumb-transition-enter-from { + opacity: 0; + transform: translateX(30px) skewX(-30deg); +} + +@keyframes slide-down { + from { + opacity: 0; + transform: translateY(50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-left { + from { + opacity: 0; + transform: translateX(-50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-right { + from { + opacity: 0; + transform: translateX(50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-up { + from { + opacity: 0; + transform: translateY(-50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.z-popup { + z-index: var(--popup-z-index); +} + +@keyframes shrink { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } +} diff --git a/easyflow-ui-admin/packages/@core/base/design/src/design-tokens/dark.css b/easyflow-ui-admin/packages/@core/base/design/src/design-tokens/dark.css new file mode 100644 index 0000000..792b2e1 --- /dev/null +++ b/easyflow-ui-admin/packages/@core/base/design/src/design-tokens/dark.css @@ -0,0 +1,454 @@ +.dark, +.dark[data-theme='custom'], +.dark[data-theme='default'] { + /* Default background color of ...etc */ + --background: 222.34deg 10.43% 12.27%; + + /* 主体区域背景色 */ + --background-deep: 220deg 13.06% 9%; + --foreground: 0 0% 95%; + + /* Background color for */ + --card: 222.34deg 10.43% 12.27%; + + /* --card: 222.2 84% 4.9%; */ + --card-foreground: 210 40% 98%; + + /* Background color for popovers such as , , */ + + /* --popover: 222.82deg 8.43% 12.27%; */ + + /* 弹出层的背景色与主题区域背景色太过接近 */ + --popover: 0 0% 14.2%; + --popover-foreground: 210 40% 98%; + + /* Muted backgrounds such as , and */ + + /* --muted: 220deg 6.82% 17.25%; */ + + /* --muted-foreground: 215 20.2% 65.1%; */ + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + /* 主题颜色 */ + + /* --primary: 245 82% 67%; */ + --primary-foreground: 0 0% 98%; + + /* Used for destructive actions such as + +

+ Click on the Vite and TypeScript logos to learn more +

+ +` + +setupCounter(document.querySelector('#counter')!) diff --git a/easyflow-ui-websdk/src/style.css b/easyflow-ui-websdk/src/style.css new file mode 100644 index 0000000..3bcdbd0 --- /dev/null +++ b/easyflow-ui-websdk/src/style.css @@ -0,0 +1,96 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/easyflow-ui-websdk/src/typescript.svg b/easyflow-ui-websdk/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/easyflow-ui-websdk/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easyflow-ui-websdk/tsconfig.json b/easyflow-ui-websdk/tsconfig.json new file mode 100644 index 0000000..4ba8dd9 --- /dev/null +++ b/easyflow-ui-websdk/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..3ac5730 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,42 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://lefthook.dev/configuration/ +# +# pre-push: +# jobs: +# - name: packages audit +# tags: +# - frontend +# - security +# run: yarn audit +# +# - name: gems audit +# tags: +# - backend +# - security +# run: bundle audit +# +# pre-commit: +# parallel: true +# jobs: +# - run: yarn eslint {staged_files} +# glob: "*.{js,ts,jsx,tsx}" +# +# - name: rubocop +# glob: "*.rb" +# exclude: +# - config/application.rb +# - config/routes.rb +# run: bundle exec rubocop --force-exclusion {all_files} +# +# - name: govet +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# +# - script: "hello.js" +# runner: node +# +# - script: "hello.go" +# runner: go run diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..41c0f0c --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..8611571 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/oss-licenses.md b/oss-licenses.md new file mode 100644 index 0000000..f80ff89 --- /dev/null +++ b/oss-licenses.md @@ -0,0 +1,36 @@ +## Open Source Components Used + +### Vue (MIT License) +The MIT License (MIT) + +Copyright (c) 2013-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +### Vben (MIT License) + +MIT License + +Copyright (c) 2024-present, Vben + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..85ed8a3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,475 @@ + + 4.0.0 + tech.easyflow + easyflow + ${revision} + pom + + + easyflow-api + easyflow-commons + easyflow-modules + easyflow-starter + + + + 17 + 17 + 17 + UTF-8 + 1.3.0 + 0.0.1 + 1.11.6 + 0.0.1 + 4.9.3 + 3.5.9 + 2.0.17 + 4.0.3 + 2.0.57 + 4.13.2 + 1.40.0 + 0.18.0 + 1.16.1 + 2.18.0 + 1.2.0 + 5.8.36 + 1.5.3 + 2.4 + 2.2.1 + 3.16.1 + 4.1.130.Final + + + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + compile + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.6 + + + cloud.tianai.captcha + tianai-captcha-springboot-starter + 1.5.3 + + + com.alibaba.nls + nls-sdk-tts + 2.2.14 + + + com.alibaba.nls + nls-sdk-common + 2.2.14 + + + + org.springframework.boot + spring-boot-starter-quartz + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-websocket + ${spring-boot.version} + + + + cn.hutool + hutool-core + ${hutool.version} + + + cn.hutool + hutool-crypto + ${hutool.version} + + + cn.hutool + hutool-extra + ${hutool.version} + + + cn.hutool + hutool-http + ${hutool.version} + + + cn.hutool + hutool-json + ${hutool.version} + + + + com.google.zxing + core + 3.5.3 + + + + cn.dev33 + sa-token-spring-boot3-starter + ${sa-token.version} + + + + org.java-websocket + Java-WebSocket + ${websocket.version} + + + + com.mysql + mysql-connector-j + 8.3.0 + + + + com.mybatis-flex + mybatis-flex-codegen + ${mybatis-flex.version} + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + ${mybatis-flex.version} + + + com.mybatis-flex + mybatis-flex-core + ${mybatis-flex.version} + + + + com.easyagents + easy-agents-bom + ${easy-agents.version} + + + com.easyagents + easy-agents-flow + ${easy-agents.version} + + + com.easyagents + easy-agents-support + ${easy-agents.version} + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.squareup.okhttp3 + okhttp-sse + ${okhttp.version} + + + com.zaxxer + HikariCP + ${HikariCP.version} + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-aop + ${spring-boot.version} + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-validation + ${spring-boot.version} + + + + com.alibaba + fastjson + ${fastjson.version} + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.commonmark + commonmark + ${commonmark.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + cn.idev.excel + fastexcel + ${fastexcel.version} + + + + + tech.easyflow + easyflow-common-ai + ${revision} + + + tech.easyflow + easyflow-common-all + ${revision} + + + tech.easyflow + easyflow-common-base + ${revision} + + + tech.easyflow + easyflow-common-cache + ${revision} + + + tech.easyflow + easyflow-common-file-storage + ${revision} + + + tech.easyflow + easyflow-common-options + ${revision} + + + tech.easyflow + easyflow-common-satoken + ${revision} + + + tech.easyflow + easyflow-common-captcha + ${revision} + + + tech.easyflow + easyflow-common-web + ${revision} + + + tech.easyflow + easyflow-common-audio + ${revision} + + + tech.easyflow + easyflow-common-chat-protocol + ${revision} + + + + + tech.easyflow + easyflow-module-ai + ${revision} + + + tech.easyflow + easyflow-module-auth + ${revision} + + + tech.easyflow + easyflow-module-autoconfig + ${revision} + + + tech.easyflow + easyflow-module-log + ${revision} + + + tech.easyflow + easyflow-module-system + ${revision} + + + tech.easyflow + easyflow-module-job + ${revision} + + + tech.easyflow + easyflow-module-datacenter + ${revision} + + + + + tech.easyflow + easyflow-starter + ${revision} + + + tech.easyflow + easyflow-starter-admin + ${revision} + + + tech.easyflow + easyflow-starter-all + ${revision} + + + tech.easyflow + easyflow-starter-public + ${revision} + + + tech.easyflow + easyflow-starter-usercenter + ${revision} + + + + + tech.easyflow + easyflow-api + ${revision} + + + tech.easyflow + easyflow-api-admin + ${revision} + + + tech.easyflow + easyflow-api-public + ${revision} + + + tech.easyflow + easyflow-api-mcp + ${revision} + + + tech.easyflow + easyflow-api-usercenter + ${revision} + + + + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + + org.dromara.x-file-storage + x-file-storage-spring + ${x-file-storage.version} + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-oss.version} + + + + com.easyagents + easy-agents-mcp + ${easy-agents.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + UTF-8 + + -parameters + + + + + org.codehaus.mojo + flatten-maven-plugin + ${maven-flatten.version} + + true + oss + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + diff --git a/sql/00-quartz.sql b/sql/00-quartz.sql new file mode 100644 index 0000000..5f95b30 --- /dev/null +++ b/sql/00-quartz.sql @@ -0,0 +1,173 @@ +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS TB_QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS TB_QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS TB_QRTZ_LOCKS; +DROP TABLE IF EXISTS TB_QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_TRIGGERS; +DROP TABLE IF EXISTS TB_QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS TB_QRTZ_CALENDARS; + +CREATE TABLE TB_QRTZ_JOB_DETAILS( +SCHED_NAME VARCHAR(120) NOT NULL, +JOB_NAME VARCHAR(190) NOT NULL, +JOB_GROUP VARCHAR(190) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +JOB_CLASS_NAME VARCHAR(250) NOT NULL, +IS_DURABLE VARCHAR(1) NOT NULL, +IS_NONCONCURRENT VARCHAR(1) NOT NULL, +IS_UPDATE_DATA VARCHAR(1) NOT NULL, +REQUESTS_RECOVERY VARCHAR(1) NOT NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(190) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +JOB_NAME VARCHAR(190) NOT NULL, +JOB_GROUP VARCHAR(190) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +NEXT_FIRE_TIME BIGINT(13) NULL, +PREV_FIRE_TIME BIGINT(13) NULL, +PRIORITY INTEGER NULL, +TRIGGER_STATE VARCHAR(16) NOT NULL, +TRIGGER_TYPE VARCHAR(8) NOT NULL, +START_TIME BIGINT(13) NOT NULL, +END_TIME BIGINT(13) NULL, +CALENDAR_NAME VARCHAR(190) NULL, +MISFIRE_INSTR SMALLINT(2) NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +CONSTRAINT FK_TB_QRTZ_TRIGGERS_JOB_DETAILS FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +REFERENCES TB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_SIMPLE_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(190) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +REPEAT_COUNT BIGINT(7) NOT NULL, +REPEAT_INTERVAL BIGINT(12) NOT NULL, +TIMES_TRIGGERED BIGINT(10) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +CONSTRAINT FK_TB_QRTZ_SIMPLE_TRIGGERS_TRIGGERS FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_CRON_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(190) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +CRON_EXPRESSION VARCHAR(120) NOT NULL, +TIME_ZONE_ID VARCHAR(80), +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +CONSTRAINT FK_TB_QRTZ_CRON_TRIGGERS_TRIGGERS FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_SIMPROP_TRIGGERS + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(190) NOT NULL, + TRIGGER_GROUP VARCHAR(190) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 VARCHAR(1) NULL, + BOOL_PROP_2 VARCHAR(1) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + CONSTRAINT FK_TB_QRTZ_SIMPROP_TRIGGERS_TRIGGERS FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_BLOB_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(190) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +BLOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), +CONSTRAINT FK_TB_QRTZ_BLOB_TRIGGERS_TRIGGERS FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_CALENDARS ( +SCHED_NAME VARCHAR(120) NOT NULL, +CALENDAR_NAME VARCHAR(190) NOT NULL, +CALENDAR BLOB NOT NULL, +PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_PAUSED_TRIGGER_GRPS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_FIRED_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +ENTRY_ID VARCHAR(95) NOT NULL, +TRIGGER_NAME VARCHAR(190) NOT NULL, +TRIGGER_GROUP VARCHAR(190) NOT NULL, +INSTANCE_NAME VARCHAR(190) NOT NULL, +FIRED_TIME BIGINT(13) NOT NULL, +SCHED_TIME BIGINT(13) NOT NULL, +PRIORITY INTEGER NOT NULL, +STATE VARCHAR(16) NOT NULL, +JOB_NAME VARCHAR(190) NULL, +JOB_GROUP VARCHAR(190) NULL, +IS_NONCONCURRENT VARCHAR(1) NULL, +REQUESTS_RECOVERY VARCHAR(1) NULL, +PRIMARY KEY (SCHED_NAME,ENTRY_ID)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_SCHEDULER_STATE ( +SCHED_NAME VARCHAR(120) NOT NULL, +INSTANCE_NAME VARCHAR(190) NOT NULL, +LAST_CHECKIN_TIME BIGINT(13) NOT NULL, +CHECKIN_INTERVAL BIGINT(13) NOT NULL, +PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) +ENGINE=InnoDB; + +CREATE TABLE TB_QRTZ_LOCKS ( +SCHED_NAME VARCHAR(120) NOT NULL, +LOCK_NAME VARCHAR(40) NOT NULL, +PRIMARY KEY (SCHED_NAME,LOCK_NAME)) +ENGINE=InnoDB; + +CREATE INDEX IDX_TB_QRTZ_J_REQ_RECOVERY ON TB_QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_TB_QRTZ_J_GRP ON TB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); + +CREATE INDEX IDX_TB_QRTZ_T_J ON TB_QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_TB_QRTZ_T_JG ON TB_QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_TB_QRTZ_T_C ON TB_QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); +CREATE INDEX IDX_TB_QRTZ_T_G ON TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_TB_QRTZ_T_STATE ON TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); +CREATE INDEX IDX_TB_QRTZ_T_N_STATE ON TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_TB_QRTZ_T_N_G_STATE ON TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_TB_QRTZ_T_NEXT_FIRE_TIME ON TB_QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); +CREATE INDEX IDX_TB_QRTZ_T_NFT_ST ON TB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); +CREATE INDEX IDX_TB_QRTZ_T_NFT_MISFIRE ON TB_QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); +CREATE INDEX IDX_TB_QRTZ_T_NFT_ST_MISFIRE ON TB_QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); +CREATE INDEX IDX_TB_QRTZ_T_NFT_ST_MISFIRE_GRP ON TB_QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); + +CREATE INDEX IDX_TB_QRTZ_FT_TRIG_INST_NAME ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); +CREATE INDEX IDX_TB_QRTZ_FT_INST_JOB_REQ_RCVRY ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_TB_QRTZ_FT_J_G ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_TB_QRTZ_FT_JG ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_TB_QRTZ_FT_T_G ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_TB_QRTZ_FT_TG ON TB_QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/01-easyflow-v2.ddl.sql b/sql/01-easyflow-v2.ddl.sql new file mode 100644 index 0000000..20730f7 --- /dev/null +++ b/sql/01-easyflow-v2.ddl.sql @@ -0,0 +1,1147 @@ +SET NAMES utf8mb4; +SET +FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for tb_bot +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot`; +CREATE TABLE `tb_bot` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键ID', + `alias` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `category_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '分类ID', + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题', + `description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `icon` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图标', + `model_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '模型 ID', + `model_options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '模型配置', + `status` int NULL DEFAULT 0 COMMENT '数据状态', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '选项', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建者ID', + `modified` datetime NULL DEFAULT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '修改者ID', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `tb_ai_bot_alias_uindex`(`alias`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_category +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_category`; +CREATE TABLE `tb_bot_category` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `category_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot分类' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_bot_conversation +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_conversation`; +CREATE TABLE `tb_bot_conversation` +( + `id` bigint UNSIGNED NOT NULL COMMENT '会话id', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '会话标题', + `bot_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT 'botid', + `account_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '账户 id', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL, + `modified` datetime NULL DEFAULT NULL, + `modified_by` bigint UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'bot对话' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_document_collection +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_document_collection`; +CREATE TABLE `tb_bot_document_collection` +( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `bot_id` bigint UNSIGNED NULL DEFAULT NULL, + `document_collection_id` bigint UNSIGNED NULL DEFAULT NULL, + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的知识库' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_message +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_message`; +CREATE TABLE `tb_bot_message` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'ID', + `bot_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT 'botId', + `account_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联的账户ID', + `conversation_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '会话ID', + `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色[user|assistant]', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容', + `image` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图片', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '选项', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `modified` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `bot_id`(`bot_id`) USING BTREE, + INDEX `account_id`(`account_id`) USING BTREE, + INDEX `session_id`(`conversation_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot消息记录表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_model +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_model`; +CREATE TABLE `tb_bot_model` +( + `id` bigint UNSIGNED NOT NULL, + `bot_id` bigint UNSIGNED NULL DEFAULT NULL, + `model_id` bigint UNSIGNED NULL DEFAULT NULL, + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的大模型' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_plugin +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_plugin`; +CREATE TABLE `tb_bot_plugin` +( + `id` bigint UNSIGNED NOT NULL, + `bot_id` bigint UNSIGNED NULL DEFAULT NULL, + `plugin_item_id` bigint UNSIGNED NULL DEFAULT NULL, + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的插件' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_bot_recently_used +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_recently_used`; +CREATE TABLE `tb_bot_recently_used` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT '主键', + `bot_id` bigint(0) UNSIGNED NOT NULL COMMENT 'botId', + `created` datetime(0) NOT NULL COMMENT '创建时间', + `created_by` bigint(0) UNSIGNED NOT NULL COMMENT '创建者', + `sort_no` int(0) NULL DEFAULT 0 COMMENT '排序', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '最近使用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_bot_workflow +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_workflow`; +CREATE TABLE `tb_bot_workflow` +( + `id` bigint UNSIGNED NOT NULL, + `bot_id` bigint UNSIGNED NULL DEFAULT NULL, + `workflow_id` bigint UNSIGNED NULL DEFAULT NULL, + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的工作流' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_datacenter_table +-- ---------------------------- +DROP TABLE IF EXISTS `tb_datacenter_table`; +CREATE TABLE `tb_datacenter_table` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `table_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '数据表名', + `table_desc` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '数据表描述', + `actual_table` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '物理表名', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展项', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数据中枢表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_datacenter_table_field +-- ---------------------------- +DROP TABLE IF EXISTS `tb_datacenter_table_field`; +CREATE TABLE `tb_datacenter_table_field` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `table_id` bigint UNSIGNED NOT NULL COMMENT '数据表ID', + `field_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字段名称', + `field_desc` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '字段描述', + `field_type` int NOT NULL COMMENT '字段类型', + `required` int NOT NULL DEFAULT 0 COMMENT '是否必填', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展项', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数据中枢字段表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_document +-- ---------------------------- +DROP TABLE IF EXISTS `tb_document`; +CREATE TABLE `tb_document` +( + `id` bigint UNSIGNED NOT NULL, + `collection_id` bigint UNSIGNED NOT NULL COMMENT '知识库ID', + `document_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档类型 pdf/word/aieditor 等', + `document_path` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档路径', + `title` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容', + `content_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '内容类型', + `slug` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'URL 别名', + `order_no` int NULL DEFAULT NULL COMMENT '排序序号', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '其他配置项', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建人ID', + `modified` datetime NULL DEFAULT NULL COMMENT '最后的修改时间', + `modified_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '最后的修改人的ID', + PRIMARY KEY (`id`) USING BTREE, + INDEX `knowledge_id`(`collection_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_document_chunk +-- ---------------------------- +DROP TABLE IF EXISTS `tb_document_chunk`; +CREATE TABLE `tb_document_chunk` +( + `id` bigint UNSIGNED NOT NULL, + `document_id` bigint UNSIGNED NOT NULL COMMENT '文档ID', + `document_collection_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '知识库ID', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '分块内容', + `sorting` int NULL DEFAULT NULL COMMENT '分割顺序', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档分块表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_document_collection +-- ---------------------------- +DROP TABLE IF EXISTS `tb_document_collection`; +CREATE TABLE `tb_document_collection` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT 'Id', + `alias` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名', + `dept_id` bigint(0) UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint(0) UNSIGNED NOT NULL COMMENT '租户ID', + `icon` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ICON', + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `slug` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'URL 别名', + `vector_store_enable` tinyint(1) NULL DEFAULT NULL COMMENT '是否启用向量存储', + `vector_store_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '向量数据库类型', + `vector_store_collection` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '向量数据库集合', + `vector_store_config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '向量数据库配置', + `vector_embed_model_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT 'Embedding 模型ID', + `dimension_of_vector_model` int(0) NULL DEFAULT NULL COMMENT '向量模型维度', + `created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '创建用户ID', + `modified` datetime(0) NULL DEFAULT NULL COMMENT '最后一次修改时间', + `modified_by` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '最后一次修改用户ID', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '其他配置', + `rerank_model_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '重排模型id', + `search_engine_enable` tinyint(1) NULL DEFAULT NULL COMMENT '是否启用搜索引擎', + `english_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '英文名称', + `category_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '分类ID', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `tb_ai_knowledge_alias_uindex`(`alias`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_document_collection_category +-- ---------------------------- +DROP TABLE IF EXISTS `tb_document_collection_category`; +CREATE TABLE `tb_document_collection_category` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT '主键', + `category_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称', + `sort_no` int(0) NULL DEFAULT 0 COMMENT '排序', + `status` int(0) NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime(0) NOT NULL COMMENT '创建时间', + `created_by` bigint(0) UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime(0) NOT NULL COMMENT '修改时间', + `modified_by` bigint(0) UNSIGNED NOT NULL COMMENT '修改者', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_document_history +-- ---------------------------- +DROP TABLE IF EXISTS `tb_document_history`; +CREATE TABLE `tb_document_history` +( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `document_id` bigint NULL DEFAULT NULL COMMENT '修改的文档ID', + `old_title` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '旧标题', + `new_title` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '新标题', + `old_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '旧内容', + `new_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '新内容', + `old_document_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '旧的文档类型', + `new_document_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '新的额文档类型', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint NULL DEFAULT NULL COMMENT '创建人ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档历史记录' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_model +-- ---------------------------- +DROP TABLE IF EXISTS `tb_model`; +CREATE TABLE `tb_model` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT 'ID', + `dept_id` bigint(0) UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint(0) UNSIGNED NOT NULL COMMENT '租户ID', + `provider_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '供应商id', + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题或名称', + `icon` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ICON', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `endpoint` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '大模型请求地址', + `request_path` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求路径', + `model_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '大模型名称', + `api_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '大模型 API KEY', + `extra_config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '大模型其他属性配置', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '其他配置内容', + `group_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分组名称', + `model_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型类型: chatModel/embeddingModel/rerankModel/orc..', + `with_used` tinyint(1) NULL DEFAULT NULL COMMENT '是否使用', + `support_thinking` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持推理', + `support_tool` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持工具', + `support_image` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持图片', + `support_image_b64_only` tinyint(1) NULL DEFAULT NULL COMMENT '仅支持 base64 的图片类型', + `support_video` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持视频', + `support_audio` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持音频', + `support_free` tinyint(1) NULL DEFAULT NULL COMMENT '是否免费', + `support_tool_message` tinyint(1) NULL DEFAULT NULL COMMENT '是否支持tool消息', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '大模型管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_model_provider +-- ---------------------------- +DROP TABLE IF EXISTS `tb_model_provider`; +CREATE TABLE `tb_model_provider` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'id', + `provider_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '供应商名称', + `provider_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '不同的 client 实现,默认为 openai', + `icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图标', + `api_key` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'apiKey', + `endpoint` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'endPoint', + `chat_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '对话地址', + `embed_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '向量地址', + `rerank_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '重排路径', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '大模型供应商,比如 Aliyun/Gitee/火山引擎 等' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_plugin +-- ---------------------------- +DROP TABLE IF EXISTS `tb_plugin`; +CREATE TABLE `tb_plugin` +( + `id` bigint UNSIGNED NOT NULL COMMENT '插件id', + `alias` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名称', + `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `type` int NULL DEFAULT NULL COMMENT '类型', + `base_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '基础URL', + `auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '认证方式 【apiKey/none】', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图标地址', + `position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '认证参数位置 【headers, query】', + `headers` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求头', + `token_key` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'token键', + `token_value` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'token值', + `dept_id` bigint NULL DEFAULT NULL COMMENT '部门id', + `tenant_id` bigint NULL DEFAULT NULL COMMENT '租户id', + `created_by` bigint NULL DEFAULT NULL COMMENT '创建人', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `tb_ai_plugin_alias_uindex`(`alias`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '插件表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_plugin_category +-- ---------------------------- +DROP TABLE IF EXISTS `tb_plugin_category`; +CREATE TABLE `tb_plugin_category` +( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `created_at` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '插件分类' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_plugin_category_mapping +-- ---------------------------- +DROP TABLE IF EXISTS `tb_plugin_category_mapping`; +CREATE TABLE `tb_plugin_category_mapping` +( + `category_id` bigint UNSIGNED NOT NULL, + `plugin_id` bigint UNSIGNED NOT NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '插件分类关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_plugin_item +-- ---------------------------- +DROP TABLE IF EXISTS `tb_plugin_item`; +CREATE TABLE `tb_plugin_item` +( + `id` bigint UNSIGNED NOT NULL COMMENT '插件工具id', + `plugin_id` bigint UNSIGNED NOT NULL COMMENT '插件id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名称', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `base_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '基础路径', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `status` int NULL DEFAULT 0 COMMENT '是否启用', + `input_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输入参数', + `output_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输出参数', + `request_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求方式【Post, Get, Put, Delete】', + `service_status` int NULL DEFAULT NULL COMMENT '服务状态[0 下线 1 上线]', + `debug_status` int NULL DEFAULT NULL COMMENT '调试状态【0失败 1成功】', + `english_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '英文名称', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '插件工具表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_blob_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_blob_triggers`; +CREATE TABLE `tb_qrtz_blob_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `BLOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `SCHED_NAME`(`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `TB_QRTZ_BLOB_TRIGGERS_IBFK_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `tb_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_calendars +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_calendars`; +CREATE TABLE `tb_qrtz_calendars` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `CALENDAR_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `CALENDAR` blob NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `CALENDAR_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_cron_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_cron_triggers`; +CREATE TABLE `tb_qrtz_cron_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `CRON_EXPRESSION` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TIME_ZONE_ID` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `TB_QRTZ_CRON_TRIGGERS_IBFK_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `tb_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_fired_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_fired_triggers`; +CREATE TABLE `tb_qrtz_fired_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `ENTRY_ID` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `INSTANCE_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `FIRED_TIME` bigint NOT NULL, + `SCHED_TIME` bigint NOT NULL, + `PRIORITY` int NOT NULL, + `STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `ENTRY_ID`) USING BTREE, + INDEX `IDX_QRTZ_FT_TRIG_INST_NAME`(`SCHED_NAME`, `INSTANCE_NAME`) USING BTREE, + INDEX `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY`(`SCHED_NAME`, `INSTANCE_NAME`, `REQUESTS_RECOVERY`) USING BTREE, + INDEX `IDX_QRTZ_FT_J_G`(`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_FT_JG`(`SCHED_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_FT_T_G`(`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_FT_TG`(`SCHED_NAME`, `TRIGGER_GROUP`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_job_details +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_job_details`; +CREATE TABLE `tb_qrtz_job_details` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `JOB_CLASS_NAME` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `IS_DURABLE` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `IS_UPDATE_DATA` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_J_REQ_RECOVERY`(`SCHED_NAME`, `REQUESTS_RECOVERY`) USING BTREE, + INDEX `IDX_QRTZ_J_GRP`(`SCHED_NAME`, `JOB_GROUP`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_locks +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_locks`; +CREATE TABLE `tb_qrtz_locks` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `LOCK_NAME` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `LOCK_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_paused_trigger_grps +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_paused_trigger_grps`; +CREATE TABLE `tb_qrtz_paused_trigger_grps` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_GROUP`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_scheduler_state +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_scheduler_state`; +CREATE TABLE `tb_qrtz_scheduler_state` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `INSTANCE_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `LAST_CHECKIN_TIME` bigint NOT NULL, + `CHECKIN_INTERVAL` bigint NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `INSTANCE_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_simple_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_simple_triggers`; +CREATE TABLE `tb_qrtz_simple_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `REPEAT_COUNT` bigint NOT NULL, + `REPEAT_INTERVAL` bigint NOT NULL, + `TIMES_TRIGGERED` bigint NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `TB_QRTZ_SIMPLE_TRIGGERS_IBFK_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `tb_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_simprop_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_simprop_triggers`; +CREATE TABLE `tb_qrtz_simprop_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `STR_PROP_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `STR_PROP_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `STR_PROP_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `INT_PROP_1` int NULL DEFAULT NULL, + `INT_PROP_2` int NULL DEFAULT NULL, + `LONG_PROP_1` bigint NULL DEFAULT NULL, + `LONG_PROP_2` bigint NULL DEFAULT NULL, + `DEC_PROP_1` decimal(13, 4) NULL DEFAULT NULL, + `DEC_PROP_2` decimal(13, 4) NULL DEFAULT NULL, + `BOOL_PROP_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `BOOL_PROP_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `TB_QRTZ_SIMPROP_TRIGGERS_IBFK_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `tb_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_qrtz_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `tb_qrtz_triggers`; +CREATE TABLE `tb_qrtz_triggers` +( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `NEXT_FIRE_TIME` bigint NULL DEFAULT NULL, + `PREV_FIRE_TIME` bigint NULL DEFAULT NULL, + `PRIORITY` int NULL DEFAULT NULL, + `TRIGGER_STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `TRIGGER_TYPE` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `START_TIME` bigint NOT NULL, + `END_TIME` bigint NULL DEFAULT NULL, + `CALENDAR_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `MISFIRE_INSTR` smallint NULL DEFAULT NULL, + `JOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_T_J`(`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_T_JG`(`SCHED_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_T_C`(`SCHED_NAME`, `CALENDAR_NAME`) USING BTREE, + INDEX `IDX_QRTZ_T_G`(`SCHED_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_T_STATE`(`SCHED_NAME`, `TRIGGER_STATE`) USING BTREE, + INDEX `IDX_QRTZ_T_N_STATE`(`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `TRIGGER_STATE`) USING BTREE, + INDEX `IDX_QRTZ_T_N_G_STATE`(`SCHED_NAME`, `TRIGGER_GROUP`, `TRIGGER_STATE`) USING BTREE, + INDEX `IDX_QRTZ_T_NEXT_FIRE_TIME`(`SCHED_NAME`, `NEXT_FIRE_TIME`) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST`(`SCHED_NAME`, `TRIGGER_STATE`, `NEXT_FIRE_TIME`) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_MISFIRE`(`SCHED_NAME`, `MISFIRE_INSTR`, `NEXT_FIRE_TIME`) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST_MISFIRE`(`SCHED_NAME`, `MISFIRE_INSTR`, `NEXT_FIRE_TIME`, `TRIGGER_STATE`) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP`(`SCHED_NAME`, `MISFIRE_INSTR`, `NEXT_FIRE_TIME`, `TRIGGER_GROUP`, `TRIGGER_STATE`) USING BTREE, + CONSTRAINT `TB_QRTZ_TRIGGERS_IBFK_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `tb_qrtz_job_details` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_resource +-- ---------------------------- +DROP TABLE IF EXISTS `tb_resource`; +CREATE TABLE `tb_resource` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `resource_type` int NOT NULL COMMENT '素材类型', + `resource_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '素材名称', + `suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '后缀', + `resource_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '素材地址', + `origin` int NOT NULL DEFAULT 0 COMMENT '素材来源', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展项', + `file_size` bigint UNSIGNED NULL DEFAULT NULL COMMENT '文件大小', + `category_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '分类ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '素材库' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_resource_category +-- ---------------------------- +DROP TABLE IF EXISTS `tb_resource_category`; +CREATE TABLE `tb_resource_category` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `category_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '素材分类' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_sys_account +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_account`; +CREATE TABLE `tb_sys_account` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `login_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '登录账号', + `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码', + `account_type` tinyint NOT NULL DEFAULT 0 COMMENT '账户类型', + `nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '昵称', + `mobile` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '手机电话', + `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '邮件', + `avatar` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '账户头像', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_login_name`(`login_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_account_position +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_account_position`; +CREATE TABLE `tb_sys_account_position` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID', + `position_id` bigint UNSIGNED NOT NULL COMMENT '职位ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-职位表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_account_role +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_account_role`; +CREATE TABLE `tb_sys_account_role` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID', + `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-角色表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_api_key +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_api_key`; +CREATE TABLE `tb_sys_api_key` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'id', + `api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'apiKey', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `dept_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '部门id', + `tenant_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '租户id', + `expired_at` datetime NULL DEFAULT NULL COMMENT '失效时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'apikey表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_api_key_resource +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_api_key_resource`; +CREATE TABLE `tb_sys_api_key_resource` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'id', + `request_interface` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求接口', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uni_api` (`request_interface`) +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '请求接口表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_sys_api_key_resource_mapping +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_api_key_resource_mapping`; +CREATE TABLE `tb_sys_api_key_resource_mapping` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'id', + `api_key_id` bigint UNSIGNED NOT NULL COMMENT 'api_key_id', + `api_key_resource_id` bigint UNSIGNED NOT NULL COMMENT '请求接口资源访问id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'apikey-请求接口表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_dept`; +CREATE TABLE `tb_sys_dept` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `parent_id` bigint UNSIGNED NOT NULL COMMENT '父级ID', + `ancestors` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '父级部门ID集合', + `dept_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '部门名称', + `dept_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '部门编码', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '部门表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_dict +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_dict`; +CREATE TABLE `tb_sys_dict` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '数据字典名称', + `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '字典编码', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '字典描述或备注', + `dict_type` tinyint NULL DEFAULT NULL COMMENT '字典类型 1 自定义字典、2 数据表字典、 3 枚举类字典、 4 系统字典(自定义 DictLoader)', + `sort_no` int NULL DEFAULT NULL COMMENT '排序编号', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '扩展字典 存放 json', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `modified` datetime NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `key`(`code`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统字典表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_dict_item +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_dict_item`; +CREATE TABLE `tb_sys_dict_item` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `dict_id` bigint UNSIGNED NOT NULL COMMENT '归属哪个字典', + `text` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '名称或内容', + `value` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '值', + `description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `sort_no` int NOT NULL DEFAULT 0 COMMENT '排序', + `css_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'css样式内容', + `css_class` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'css样式类名', + `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `modified` datetime NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数据字典内容' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_job`; +CREATE TABLE `tb_sys_job` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `job_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务名称', + `job_type` int NOT NULL COMMENT '任务类型', + `job_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '任务参数', + `cron_expression` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'cron表达式', + `allow_concurrent` int NOT NULL DEFAULT 0 COMMENT '是否并发执行', + `misfire_policy` int NOT NULL DEFAULT 3 COMMENT '错过策略', + `options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '其他配置', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统任务表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_job_log +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_job_log`; +CREATE TABLE `tb_sys_job_log` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `job_id` bigint UNSIGNED NOT NULL COMMENT '任务ID', + `job_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务名称', + `job_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '任务参数', + `job_result` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '执行结果', + `error_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `start_time` datetime NOT NULL COMMENT '开始时间', + `end_time` datetime NOT NULL COMMENT '结束时间', + `created` datetime NOT NULL COMMENT '创建时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统任务日志' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_log +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_log`; +CREATE TABLE `tb_sys_log` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'ID', + `account_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '操作人', + `action_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作名称', + `action_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作的类型', + `action_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作涉及的类', + `action_method` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作涉及的方法', + `action_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作涉及的 URL 地址', + `action_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作涉及的用户 IP 地址', + `action_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '操作请求参数', + `action_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '操作请求body', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NULL DEFAULT NULL COMMENT '操作时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_menu`; +CREATE TABLE `tb_sys_menu` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `parent_id` bigint UNSIGNED NOT NULL COMMENT '父菜单id', + `menu_type` int NOT NULL COMMENT '菜单类型', + `menu_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜单标题', + `menu_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '菜单url', + `component` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '组件路径', + `menu_icon` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '图标/图片地址', + `is_show` int NOT NULL DEFAULT 1 COMMENT '是否显示', + `permission_tag` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '权限标识', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_option +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_option`; +CREATE TABLE `tb_sys_option` +( + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置KEY', + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '配置内容', + INDEX `uni_key`(`tenant_id`, `key`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统配置信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_position +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_position`; +CREATE TABLE `tb_sys_position` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `position_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '岗位名称', + `position_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '岗位编码', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '职位表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_role`; +CREATE TABLE `tb_sys_role` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色标识', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注', + `data_scope` int NULL DEFAULT 1 COMMENT '数据权限(EnumDataScope)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_tenant_role`(`tenant_id`, `role_key`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统角色' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_role_dept`; +CREATE TABLE `tb_sys_role_dept` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-部门表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_role_menu`; +CREATE TABLE `tb_sys_role_menu` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', + `menu_id` bigint UNSIGNED NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-菜单表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_workflow +-- ---------------------------- +DROP TABLE IF EXISTS `tb_workflow`; +CREATE TABLE `tb_workflow` +( + `id` bigint UNSIGNED NOT NULL COMMENT 'ID 主键', + `alias` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名', + `dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID', + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标题', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `icon` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ICON', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '工作流设计的 JSON 内容', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建人', + `modified` datetime NULL DEFAULT NULL COMMENT '最后修改时间', + `modified_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '最后修改的人', + `english_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '英文名称', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `category_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '分类ID', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `tb_ai_workflow_alias_uindex`(`alias`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_workflow_category +-- ---------------------------- +DROP TABLE IF EXISTS `tb_workflow_category`; +CREATE TABLE `tb_workflow_category` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `category_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称', + `sort_no` int NULL DEFAULT 0 COMMENT '排序', + `created` datetime NOT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NOT NULL COMMENT '创建者', + `modified` datetime NOT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NOT NULL COMMENT '修改者', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流分类' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_workflow_exec_result +-- ---------------------------- +DROP TABLE IF EXISTS `tb_workflow_exec_result`; +CREATE TABLE `tb_workflow_exec_result` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `exec_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '执行标识', + `workflow_id` bigint UNSIGNED NOT NULL COMMENT '工作流ID', + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `input` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输入', + `output` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输出', + `workflow_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '工作流执行时的配置', + `start_time` datetime(3) NOT NULL COMMENT '开始时间', + `end_time` datetime(3) NULL DEFAULT NULL COMMENT '结束时间', + `tokens` bigint UNSIGNED NULL DEFAULT NULL COMMENT '消耗总token', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `created_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '执行人标识[有可能是用户|外部|定时任务等情况]', + `created_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '执行人', + `error_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_exec_key`(`exec_key`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流执行记录' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_workflow_exec_step +-- ---------------------------- +DROP TABLE IF EXISTS `tb_workflow_exec_step`; +CREATE TABLE `tb_workflow_exec_step` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `record_id` bigint UNSIGNED NOT NULL COMMENT '执行记录ID', + `exec_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '执行标识', + `node_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '节点ID', + `node_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '节点名称', + `input` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输入', + `output` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输出', + `node_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '节点信息', + `start_time` datetime(3) NOT NULL COMMENT '开始时间', + `end_time` datetime(3) NULL DEFAULT NULL COMMENT '结束时间', + `tokens` bigint UNSIGNED NULL DEFAULT NULL COMMENT '消耗总token', + `status` int NOT NULL DEFAULT 0 COMMENT '数据状态', + `error_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_exec`(`exec_key`) USING BTREE, + INDEX `idx_record_id`(`record_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '执行记录步骤' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Table structure for tb_sys_user_feedback +-- ---------------------------- +DROP TABLE IF EXISTS `tb_sys_user_feedback`; +CREATE TABLE `tb_sys_user_feedback` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT '主键id', + `feedback_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '问题摘要', + `feedback_type` int(0) NOT NULL COMMENT '问题类型(1-功能故障 2-优化建议 3-账号问题 4-其他)', + `contact_info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '联系方式【手机号/邮箱】', + `attachment_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '附件url', + `status` int(0) NULL DEFAULT NULL COMMENT '反馈处理状态(0-未查看 1-已查看 2-已处理)', + `handler_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '处理人id', + `handle_time` datetime(0) NULL DEFAULT NULL COMMENT '处理时间', + `dept_id` bigint(0) UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint(0) UNSIGNED NOT NULL COMMENT '租户ID', + `created` datetime(0) NOT NULL COMMENT '创建时间', + `created_by` bigint(0) UNSIGNED NOT NULL COMMENT '创建人', + `modified` datetime(0) NULL DEFAULT NULL COMMENT '最后修改时间', + `modified_by` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '最后修改的人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_mcp +-- ---------------------------- +DROP TABLE IF EXISTS `tb_mcp`; +CREATE TABLE `tb_mcp` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT 'id', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标题', + `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', + `config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '完整MCP配置JSON', + `dept_id` bigint(0) UNSIGNED NOT NULL COMMENT '部门ID', + `tenant_id` bigint(0) UNSIGNED NOT NULL COMMENT '租户ID', + `created` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '创建者ID', + `modified` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `modified_by` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT '修改者ID', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'mcp表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for tb_bot_mcp +-- ---------------------------- +DROP TABLE IF EXISTS `tb_bot_mcp`; +CREATE TABLE `tb_bot_mcp` +( + `id` bigint(0) UNSIGNED NOT NULL COMMENT 'id', + `bot_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT 'botId', + `mcp_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT 'mcpId', + `mcp_tool_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具名称', + `mcp_tool_description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具描述', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +SET +FOREIGN_KEY_CHECKS = 1; diff --git a/sql/02-easyflow-v2.data.sql b/sql/02-easyflow-v2.data.sql new file mode 100644 index 0000000..9b016c0 --- /dev/null +++ b/sql/02-easyflow-v2.data.sql @@ -0,0 +1,265 @@ +SET NAMES utf8mb4; + +-- ---------------------------- +-- Records of tb_sys_account +-- ---------------------------- +INSERT INTO `tb_sys_account` (`id`, `dept_id`, `tenant_id`, `login_name`, `password`, `account_type`, `nickname`, `mobile`, `email`, `avatar`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (1, 1, 1000000, 'admin', '$2a$10$mni2UdHMUwomVzvZtdECAOwYJevZ2z48ApO.JSVhyEaQ/AOKr4VP2', 99, '超级管理员', '15555555555', 'bbb@qq.com', 'https://static.agentscenter.cn/public/1/2025/12/17/684ea528-8e42-489c-b254-4e52e0679431/b.jpeg', 1, '2025-06-06 11:32:21', 1, '2025-12-17 17:51:16', 1, ''); + +-- ---------------------------- +-- Records of tb_sys_account_role +-- ---------------------------- +INSERT INTO `tb_sys_account_role` (`id`, `account_id`, `role_id`) VALUES (302654483522224128, 1, 1); + +-- ---------------------------- +-- Records of tb_sys_dept +-- ---------------------------- +INSERT INTO `tb_sys_dept` (`id`, `tenant_id`, `parent_id`, `ancestors`, `dept_name`, `dept_code`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (1, 1000000, 0, '0', '总公司', 'root_dept', 0, 1, '2025-03-17 09:09:57', 1, '2025-03-17 09:10:00', 1, ''); + +-- ---------------------------- +-- Records of tb_sys_menu +-- ---------------------------- +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (183724390000000001, 258052082618335232, 0, 'menus.system.sysPosition', '/sys/sysPosition', '/system/sysPosition/SysPositionList', 'svg:position', 1, '', 300, 1, '2026-01-05 09:07:06', 0, '2026-01-05 09:12:37', 1, '岗位管理菜单'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (183724390000000002, 183724390000000001, 1, '查询', '', '', '', 0, '/api/v1/sysPosition/query', 100, 1, '2026-01-05 09:07:06', 0, '2026-01-05 09:07:06', 0, '岗位管理-查询'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (183724390000000003, 183724390000000001, 1, '保存', '', '', '', 0, '/api/v1/sysPosition/save', 101, 1, '2026-01-05 09:07:06', 0, '2026-01-05 09:07:06', 0, '岗位管理-保存'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (183724390000000004, 183724390000000001, 1, '删除', '', '', '', 0, '/api/v1/sysPosition/remove', 102, 1, '2026-01-05 09:07:06', 0, '2026-01-05 09:07:06', 0, '岗位管理-删除'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (258052082618335232, 0, 0, 'menus.system.title', '/sys', '', 'ant-design:appstore-outlined', 1, '', 200, 0, '2025-03-14 15:07:51', 1, '2025-12-02 13:39:37', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (258052774330368000, 258052082618335232, 0, 'menus.system.sysAccount', '/sys/sysAccount', '/system/sysAccount/SysAccountList', 'svg:account', 1, '', 100, 0, '2025-03-14 15:10:36', 1, '2025-12-25 16:49:03', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (258075705244676096, 258052082618335232, 0, 'menus.system.sysRole', '/sys/sysRole', '/system/sysRole/SysRoleList', 'svg:role', 1, '', 400, 0, '2025-03-14 16:41:43', 1, '2025-12-25 16:49:10', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (258075850434703360, 258052082618335232, 0, 'menus.system.sysMenu', '/sys/sysMenu', '/system/sysMenu/SysMenuList', 'svg:menu', 1, '', 500, 0, '2025-03-14 16:42:18', 1, '2025-12-25 16:49:23', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259048038847483904, 258052082618335232, 0, 'menus.system.sysDept', '/sys/sysDept', '/system/sysDept/SysDeptList', 'svg:department', 1, '', 200, 0, '2025-03-17 09:05:25', 1, '2025-12-25 16:49:41', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259168916721754112, 258052082618335232, 0, 'menus.settings.settingsConfig', '/sys/settings', '/config/settings/Settings', 'svg:setting', 1, '', 1000, 0, '2025-03-17 17:05:45', 1, '2025-12-26 11:17:36', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259169318720626688, 258052082618335232, 0, 'menus.system.sysLog', '/sys/logs', '/system/sysLog/SysLogList', 'svg:log', 1, '', 600, 0, '2025-03-17 17:07:21', 1, '2025-12-25 16:49:59', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259169837824466944, 259169540360232960, 0, 'menus.ai.bots', '/ai/bots', '/ai/bots/index', 'svg:talk', 1, '', 11, 0, '2025-03-17 17:09:24', 1, '2026-01-06 10:46:41', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259169982154661888, 0, 0, 'menus.ai.plugin', '/ai/plugin', '/ai/plugin/Plugin', 'svg:plugin', 1, '', 21, 0, '2025-03-17 17:09:59', 1, '2025-12-26 11:14:29', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259170117110587392, 0, 0, 'menus.ai.workflow', '/ai/workflow', '/ai/workflow/WorkflowList', 'svg:workflow', 1, '', 31, 0, '2025-03-17 17:10:31', 1, '2025-12-26 11:35:41', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259170422338478080, 0, 0, 'menus.ai.documentCollection', '/ai/documentCollection', '/ai/documentCollection/DocumentCollection', 'svg:knowledge', 1, '', 51, 0, '2025-03-17 17:11:44', 1, '2025-12-26 11:14:42', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (259170538264846336, 0, 0, 'menus.ai.model', '/ai/model', '/ai/model/Model', 'svg:llm', 1, '', 61, 0, '2025-03-17 17:12:11', 1, '2025-12-26 11:15:16', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (270761213536096256, 258052082618335232, 0, 'menus.settings.apiKey', '/sys/sysApiKey', '/config/apikey/SysApiKey', 'svg:api', 1, '', 800, 0, '2025-04-18 16:49:24', 1, '2025-12-26 11:17:49', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (282254669269082112, 258052082618335232, 0, 'menus.system.sysJob', '/sys/sysJob', '/system/sysJob/SysJobList', 'svg:time', 1, '', 700, 0, '2025-05-20 10:00:17', 1, '2025-12-25 16:49:50', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243919118950400, 258052774330368000, 1, '查询', '', '', '', 0, '/api/v1/sysAccount/query', 1, 0, '2025-07-03 12:55:51', 1, '2025-07-03 12:55:51', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243919727124480, 258052774330368000, 1, '保存', '', '', '', 0, '/api/v1/sysAccount/save', 1, 0, '2025-07-03 12:55:51', 1, '2025-07-03 12:55:51', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243920205275136, 258052774330368000, 1, '删除', '', '', '', 0, '/api/v1/sysAccount/remove', 1, 0, '2025-07-03 12:55:51', 1, '2025-07-03 12:55:51', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243920679231488, 258075705244676096, 1, '查询', '', '', '', 0, '/api/v1/sysRole/query', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243921161576448, 258075705244676096, 1, '保存', '', '', '', 0, '/api/v1/sysRole/save', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243921639727104, 258075705244676096, 1, '删除', '', '', '', 0, '/api/v1/sysRole/remove', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243922155626496, 258075850434703360, 1, '查询', '', '', '', 0, '/api/v1/sysMenu/query', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243922637971456, 258075850434703360, 1, '保存', '', '', '', 0, '/api/v1/sysMenu/save', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243923116122112, 258075850434703360, 1, '删除', '', '', '', 0, '/api/v1/sysMenu/remove', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243923627827200, 259048038847483904, 1, '查询', '', '', '', 0, '/api/v1/sysDept/query', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243924105977856, 259048038847483904, 1, '保存', '', '', '', 0, '/api/v1/sysDept/save', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243924571545600, 259048038847483904, 1, '删除', '', '', '', 0, '/api/v1/sysDept/remove', 1, 0, '2025-07-03 12:55:52', 1, '2025-07-03 12:55:52', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243926450593792, 259168916721754112, 1, '查询', '', '', '', 0, '/api/v1/sysOption/query', 1, 0, '2025-07-03 12:55:53', 1, '2025-07-03 12:55:53', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243926920355840, 259168916721754112, 1, '保存', '', '', '', 0, '/api/v1/sysOption/save', 1, 0, '2025-07-03 12:55:53', 1, '2025-07-03 12:55:53', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243927385923584, 259168916721754112, 1, '删除', '', '', '', 0, '/api/v1/sysOption/remove', 1, 0, '2025-07-03 12:55:53', 1, '2025-07-03 12:55:53', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243929294331904, 259169318720626688, 1, '查询', '', '', '', 0, '/api/v1/sysLog/query', 1, 0, '2025-07-03 12:55:54', 1, '2025-07-03 12:55:54', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243929755705344, 259169318720626688, 1, '保存', '', '', '', 0, '/api/v1/sysLog/save', 1, 0, '2025-07-03 12:55:54', 1, '2025-07-03 12:55:54', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243930225467392, 259169318720626688, 1, '删除', '', '', '', 0, '/api/v1/sysLog/remove', 1, 0, '2025-07-03 12:55:54', 1, '2025-07-03 12:55:54', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243932104515584, 259169837824466944, 1, '查询', '', '', '', 0, '/api/v1/bot/query', 1, 0, '2025-07-03 12:55:55', 1, '2025-11-20 16:10:04', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243932607832064, 259169837824466944, 1, '保存', '', '', '', 0, '/api/v1/bot/save', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243933077594112, 259169837824466944, 1, '删除', '', '', '', 0, '/api/v1/bot/remove', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243933547356160, 259169982154661888, 1, '查询', '', '', '', 0, '/api/v1/plugin/query', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243934025506816, 259169982154661888, 1, '保存', '', '', '', 0, '/api/v1/plugin/save', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243934495268864, 259169982154661888, 1, '删除', '', '', '', 0, '/api/v1/plugin/remove', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243934960836608, 259170117110587392, 1, '查询', '', '', '', 0, '/api/v1/workflow/query', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243935430598656, 259170117110587392, 1, '保存', '', '', '', 0, '/api/v1/workflow/save', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243935904555008, 259170117110587392, 1, '删除', '', '', '', 0, '/api/v1/workflow/remove', 1, 0, '2025-07-03 12:55:55', 1, '2025-07-03 12:55:55', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243936374317056, 259170422338478080, 1, '查询', '', '', '', 0, '/api/v1/documentCollection/query', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243936848273408, 259170422338478080, 1, '保存', '', '', '', 0, '/api/v1/documentCollection/save', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243937313841152, 259170422338478080, 1, '删除', '', '', '', 0, '/api/v1/documentCollection/remove', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243937783603200, 259170538264846336, 1, '查询', '', '', '', 0, '/api/v1/model/query', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243938240782336, 259170538264846336, 1, '保存', '', '', '', 0, '/api/v1/model/save', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243938706350080, 259170538264846336, 1, '删除', '', '', '', 0, '/api/v1/model/remove', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243939180306432, 270761213536096256, 1, '查询', '', '', '', 0, '/api/v1/sysApiKey/query', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243939650068480, 270761213536096256, 1, '保存', '', '', '', 0, '/api/v1/sysApiKey/save', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243940115636224, 270761213536096256, 1, '删除', '', '', '', 0, '/api/v1/sysApiKey/remove', 1, 0, '2025-07-03 12:55:56', 1, '2025-07-03 12:55:56', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243940614758400, 282254669269082112, 1, '查询', '', '', '', 0, '/api/v1/sysJob/query', 1, 0, '2025-07-03 12:55:57', 1, '2025-07-03 12:55:57', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243941080326144, 282254669269082112, 1, '保存', '', '', '', 0, '/api/v1/sysJob/save', 1, 0, '2025-07-03 12:55:57', 1, '2025-07-03 12:55:57', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (298243941545893888, 282254669269082112, 1, '删除', '', '', '', 0, '/api/v1/sysJob/remove', 1, 0, '2025-07-03 12:55:57', 1, '2025-07-03 12:55:57', 1, 'gen'); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300008008381800448, 0, 0, 'menus.ai.resources', '/ai/resource', '/ai/resource/ResourceList', 'svg:resource', 1, '', 52, 0, '2025-07-08 09:45:43', 1, '2025-12-26 11:37:42', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300008359986110464, 300008008381800448, 1, '查询', '', '', '', 0, '/api/v1/resource/query', 0, 0, '2025-07-08 09:47:07', 1, '2025-07-08 09:47:07', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300012644643815424, 300008008381800448, 1, '保存', '', '', '', 0, '/api/v1/resource/save', 0, 0, '2025-07-08 10:04:08', 1, '2025-07-08 10:04:08', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300013092268326912, 300008008381800448, 1, '删除', '', '', '', 1, '/api/v1/resource/remove', 0, 0, '2025-07-08 10:05:55', 1, '2025-07-08 10:05:55', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300817858217091072, 0, 0, 'menus.ai.datacenter', '/datacenter/table', '/datacenter/DatacenterTableList', 'svg:data-center', 1, '', 53, 0, '2025-07-10 15:23:46', 1, '2025-12-26 11:15:08', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300818298270883840, 300817858217091072, 1, '查询', '', '', '', 1, '/api/v1/datacenterTable/query', 0, 0, '2025-07-10 15:25:31', 1, '2025-07-10 15:25:31', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300818387710222336, 300817858217091072, 1, '保存', '', '', '', 1, '/api/v1/datacenterTable/save', 0, 0, '2025-07-10 15:25:53', 1, '2025-07-10 15:25:53', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (300818488214134784, 300817858217091072, 1, '删除', '', '', '', 1, '/api/v1/datacenterTable/remove', 0, 0, '2025-07-10 15:26:17', 1, '2025-07-10 15:26:17', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (363168956276838400, 258052082618335232, 0, 'menus.system.sysFeedback', '/sys/sysFeedback', '/system/sysFeedback/sysFeedbackList', 'svg:user-feedback', 1, '', 900, 0, '2025-12-29 16:44:47', 1, '2025-12-29 16:45:39', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (363435797225017344, 363168956276838400, 1, '删除', '', '', '', 0, '/api/v1/sysUserFeedback/remove', 3, 0, '2025-12-30 10:25:06', 1, '2025-12-30 10:26:15', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (363435949415337984, 363168956276838400, 1, '保存', '', '', '', 0, '/api/v1/sysUserFeedback/save', 2, 0, '2025-12-30 10:25:43', 1, '2025-12-30 10:26:10', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (363533849449447424, 363168956276838400, 1, '查询', '', '', '', 0, '/api/v1/sysUserFeedback/query', 1, 0, '2025-12-30 16:54:44', 1, '2025-12-30 16:54:44', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (365312682481553408, 0, 0, 'menus.ai.mcp', '/ai/mcp', '/ai/mcp/Mcp', 'svg:mcp', 1, '', 62, 0, '2026-01-04 14:43:11', 1, '2026-01-06 10:03:08', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (365314158356467712, 365312682481553408, 1, '查询', '', '', '', 0, '/api/v1/mcp/query', 1, 0, '2026-01-04 14:49:03', 1, '2026-01-04 14:49:03', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (365314258952654848, 365312682481553408, 1, '保存', '', '', '', 0, '/api/v1/mcp/save', 2, 0, '2026-01-04 14:49:27', 1, '2026-01-04 14:49:27', 1, ''); +INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES (365314364238073856, 365312682481553408, 1, '删除', '', '', '', 0, '/api/v1/mcp/remove', 3, 0, '2026-01-04 14:49:52', 1, '2026-01-04 14:49:52', 1, ''); + +-- ---------------------------- +-- Records of tb_sys_role +-- ---------------------------- +INSERT INTO `tb_sys_role` (`id`, `tenant_id`, `role_name`, `role_key`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`, `data_scope`, `menu_check_strictly`, `dept_check_strictly`) VALUES (1, 1000000, '超级管理员', 'super_admin', 1, '2025-03-14 14:52:37', 1, '2025-03-14 14:52:37', 1, '', 1, 0, 0); + +-- ---------------------------- +-- Records of tb_sys_role_menu +-- ---------------------------- +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (183724390000000005, 1, 183724390000000001); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (183724390000000006, 1, 183724390000000002); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (183724390000000007, 1, 183724390000000003); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (183724390000000008, 1, 183724390000000004); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259111372649250817, 1, 258052774330368000); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259111372649250818, 1, 258075705244676096); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259111372649250819, 1, 258075850434703360); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259111372649250822, 1, 259048038847483904); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259111372649250825, 1, 258052082618335232); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259168916826611712, 1, 259168916721754112); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259169318829678592, 1, 259169318720626688); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259169837941907456, 1, 259169837824466944); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259169982280491008, 1, 259169982154661888); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259170117223833600, 1, 259170117110587392); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259170422447529984, 1, 259170422338478080); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (259170538378092544, 1, 259170538264846336); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (270761213603205120, 1, 270761213536096256); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (282254669390716928, 1, 282254669269082112); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243919488049152, 1, 298243919118950400); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243919966199808, 1, 298243919727124480); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243920444350464, 1, 298243920205275136); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243920926695424, 1, 298243920679231488); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243921404846080, 1, 298243921161576448); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243921908162560, 1, 298243921639727104); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243922398896128, 1, 298243922155626496); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243922877046784, 1, 298243922637971456); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243923367780352, 1, 298243923116122112); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243923866902528, 1, 298243923627827200); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243924336664576, 1, 298243924105977856); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243924810620928, 1, 298243924571545600); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243926689669120, 1, 298243926450593792); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243927155236864, 1, 298243926920355840); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243927620804608, 1, 298243927385923584); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243929525018624, 1, 298243929294331904); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243929990586368, 1, 298243929755705344); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243930460348416, 1, 298243930225467392); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243932368756736, 1, 298243932104515584); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243932846907392, 1, 298243932607832064); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243933312475136, 1, 298243933077594112); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243933786431488, 1, 298243933547356160); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243934260387840, 1, 298243934025506816); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243934730149888, 1, 298243934495268864); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243935199911936, 1, 298243934960836608); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243935669673984, 1, 298243935430598656); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243936139436032, 1, 298243935904555008); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243936621780992, 1, 298243936374317056); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243937083154432, 1, 298243936848273408); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243937552916480, 1, 298243937313841152); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243938010095616, 1, 298243937783603200); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243938475663360, 1, 298243938240782336); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243938949619712, 1, 298243938706350080); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243939415187456, 1, 298243939180306432); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243939884949504, 1, 298243939650068480); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243940358905856, 1, 298243940115636224); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243940845445120, 1, 298243940614758400); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243941315207168, 1, 298243941080326144); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (298243941780774912, 1, 298243941545893888); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300008008490852352, 1, 300008008381800448); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300008360078385152, 1, 300008359986110464); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300012644702535680, 1, 300012644643815424); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300013092310269952, 1, 300013092268326912); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300817858284199936, 1, 300817858217091072); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300818298325409792, 1, 300818298270883840); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300818387789914112, 1, 300818387710222336); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (300818488344158208, 1, 300818488214134784); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (363168956335558656, 1, 363168956276838400); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (363533849537527808, 1, 363533849449447424); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (365312682603188224, 1, 365312682481553408); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (365314158469713920, 1, 365314158356467712); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (365314259057512448, 1, 365314258952654848); +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (365314364334542848, 1, 365314364238073856); + + +-- ---------------------------- +-- Records of tb_model_provider +-- ---------------------------- +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (358906187162456064, 'Gitee', 'gitee', '', '', 'https://ai.gitee.com', '/v1/chat/completions', '/v1/embeddings', '/v1/rerank', '2025-12-17 22:26:03', 1, '2025-12-17 22:26:03', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359109019710914560, '百度千帆', 'baidu', '', '', 'https://qianfan.baidubce.com', '/v2/chat/completions', '/v2/embeddings', '', '2025-12-18 11:52:02', 1, '2025-12-18 11:52:02', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359110565693620224, '火山引擎', 'volcengine', '', '', 'https://ark.cn-beijing.volces.com', '/api/v3/chat/completions', '/api/v3/embeddings', '', '2025-12-18 11:58:11', 1, '2025-12-18 11:58:11', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359110640402563072, '星火大模型', 'spark', '', '', 'https://spark-api-open.xf-yun.com', '/v1/chat/completions', NULL, '', '2025-12-18 11:58:29', 1, '2025-12-18 11:58:29', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359110667376132096, '硅基流动', 'siliconlow', '', '', 'https://api.siliconflow.cn', '/v1/chat/completions', '/v1/embeddings', '', '2025-12-18 11:58:35', 1, '2025-12-18 11:58:35', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359110690079899648, 'Ollama', 'ollama', '', '', NULL, NULL, NULL, '', '2025-12-18 11:58:40', 1, '2025-12-18 11:58:40', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359111120310632448, 'DeepSeek', 'deepseek', '', '', 'https://api.deepseek.com', '/compatible-mode/v1/chat/completions', '/compatible-mode/v1/embeddings', '', '2025-12-18 12:00:23', 1, '2025-12-18 12:00:23', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359111228158771200, 'Open AI', 'openai', '', '', 'https://api.openai.com', '/v1/chat/completions', '/v1/embeddings', '', '2025-12-18 12:00:49', 1, '2025-12-18 12:00:49', 1); +INSERT INTO `tb_model_provider` (`id`, `provider_name`, `provider_type`, `icon`, `api_key`, `endpoint`, `chat_path`, `embed_path`, `rerank_path`, `created`, `created_by`, `modified`, `modified_by`) VALUES (359111448204541952, '阿里百炼', 'aliyun', '', '', 'https://dashscope.aliyuncs.com', '/compatible-mode/v1/chat/completions', '/compatible-mode/v1/embeddings', '', '2025-12-18 12:01:41', 1, '2025-12-18 12:01:41', 1); + +-- ---------------------------- +-- Records of tb_model +-- ---------------------------- +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359159030960295936, 1, 1000000, 358906187162456064, 'DeepSeek-V3', NULL, NULL, NULL, NULL, 'DeepSeek-V3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188398742933504, 1, 1000000, 358906187162456064, 'kimi-k2-instruct', NULL, NULL, NULL, NULL, 'kimi-k2-instruct', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', '文心ERNIE', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188487121113088, 1, 1000000, 358906187162456064, 'ERNIE-X1-Turbo', NULL, NULL, NULL, NULL, 'ERNIE-X1-Turbo', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', '文心ERNIE', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188591383121920, 1, 1000000, 358906187162456064, 'ERNIE-4.5-Turbo', NULL, NULL, NULL, NULL, 'ERNIE-4.5-Turbo', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', '文心ERNIE', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188717564563456, 1, 1000000, 358906187162456064, 'DeepSeek-R1', NULL, NULL, NULL, NULL, 'DeepSeek-R1', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188823290384384, 1, 1000000, 358906187162456064, 'Qwen3-235B-A22B', NULL, NULL, NULL, NULL, 'Qwen3-235B-A22B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359188894316728320, 1, 1000000, 358906187162456064, 'Qwen3-30B-A3B', NULL, NULL, NULL, NULL, 'Qwen3-30B-A3B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189040752463872, 1, 1000000, 358906187162456064, 'Qwen3-32B', NULL, NULL, NULL, NULL, 'Qwen3-32B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189205492142080, 1, 1000000, 358906187162456064, 'ERNIE-4.5-Turbo-VL', NULL, NULL, NULL, NULL, 'ERNIE-4.5-Turbo-VL', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', '文心ERNIE', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189277827108864, 1, 1000000, 358906187162456064, 'Qwen2.5-VL-32B-Instruct', NULL, NULL, NULL, NULL, 'Qwen2.5-VL-32B-Instruct', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189415073124352, 1, 1000000, 358906187162456064, 'InternVL3-78B', NULL, NULL, NULL, NULL, 'InternVL3-78B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'InternVL3', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189479254364160, 1, 1000000, 358906187162456064, 'InternVL3-38B', NULL, NULL, NULL, NULL, 'InternVL3-38B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'InternVL3', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189562309971968, 1, 1000000, 358906187162456064, 'Qwen3-Embedding-8B', NULL, NULL, NULL, NULL, 'Qwen3-Embedding-8B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359189617381183488, 1, 1000000, 358906187162456064, 'Qwen3-Embedding-4B', NULL, NULL, NULL, NULL, 'Qwen3-Embedding-4B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359190580372410368, 1, 1000000, 359111120310632448, 'deepseek-chat', NULL, NULL, NULL, NULL, 'deepseek-chat', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359250814495248384, 1, 1000000, 359111228158771200, 'o4-mini', NULL, NULL, NULL, NULL, 'o4-mini', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'O系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359250916064514048, 1, 1000000, 359111228158771200, 'o3', NULL, NULL, NULL, NULL, 'o3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'O系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359250975883677696, 1, 1000000, 359111228158771200, 'o3-pro', NULL, NULL, NULL, NULL, 'o3-pro', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'O系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359251041788776448, 1, 1000000, 359111228158771200, 'o3-mini', NULL, NULL, NULL, NULL, 'o3-mini', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'O系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359251651388919808, 1, 1000000, 359111228158771200, 'GPT-4.1', NULL, NULL, NULL, NULL, 'GPT-4.1', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'GPT系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359251703859662848, 1, 1000000, 359111228158771200, 'GPT-4o', NULL, NULL, NULL, NULL, 'GPT-4o', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'GPT系列', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359251899029016576, 1, 1000000, 359111228158771200, 'text-embedding-3-small', NULL, NULL, NULL, NULL, 'text-embedding-3-small', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Embedding', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359251948844765184, 1, 1000000, 359111228158771200, 'text-embedding-3-large', NULL, NULL, NULL, NULL, 'text-embedding-3-large', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Embedding', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359252007019761664, 1, 1000000, 359111228158771200, 'text-embedding-ada-002', NULL, NULL, NULL, NULL, 'text-embedding-ada-002', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Embedding', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359252333504385024, 1, 1000000, 359110667376132096, 'DeepSeek-R1', NULL, NULL, NULL, NULL, 'deepseek-ai/DeepSeek-R1', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359252410297896960, 1, 1000000, 359110667376132096, 'DeepSeek-V3', NULL, NULL, NULL, NULL, 'deepseek-ai/DeepSeek-V3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359252901950017536, 1, 1000000, 359110667376132096, 'QwenLong-L1-32B', NULL, NULL, NULL, NULL, 'Tongyi-Zhiwen/QwenLong-L1-32B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253108129419264, 1, 1000000, 359110667376132096, 'Qwen3-30B-A3B', NULL, NULL, NULL, NULL, 'Qwen/Qwen3-30B-A3B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253178463703040, 1, 1000000, 359110667376132096, 'Qwen3-32B', NULL, NULL, NULL, NULL, 'Qwen/Qwen3-32B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253290258681856, 1, 1000000, 359110667376132096, 'Qwen2.5-VL-32B-Instruct', NULL, NULL, NULL, NULL, 'Qwen/Qwen2.5-VL-32B-Instruct', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253356990058496, 1, 1000000, 359110667376132096, 'Qwen2.5-VL-72B-Instruct', NULL, NULL, NULL, NULL, 'Qwen/Qwen2.5-VL-72B-Instruct', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253448664961024, 1, 1000000, 359110667376132096, 'deepseek-vl2', NULL, NULL, NULL, NULL, 'deepseek-ai/deepseek-vl2', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253538523729920, 1, 1000000, 359110667376132096, 'Qwen3-Embedding-8B', NULL, NULL, NULL, NULL, 'Qwen/Qwen3-Embedding-8B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253610657370112, 1, 1000000, 359110667376132096, 'Qwen3-Embedding-4B', NULL, NULL, NULL, NULL, 'Qwen/Qwen3-Embedding-4B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359253918280208384, 1, 1000000, 359110667376132096, 'bge-m3', NULL, NULL, NULL, NULL, 'BAAI/bge-m3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'BAAI', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359254052095283200, 1, 1000000, 359110667376132096, 'bce-embedding-base_v1', NULL, NULL, NULL, NULL, 'netease-youdao/bce-embedding-base_v1', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Netease-youdao', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359254268492009472, 1, 1000000, 359110565693620224, 'doubao-seed-1.6', NULL, NULL, NULL, NULL, 'doubao-seed-1-6-250615', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Doubao', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359254341498064896, 1, 1000000, 359110565693620224, 'doubao-seed-1.6-flash', NULL, NULL, NULL, NULL, 'doubao-seed-1-6-flash-250615', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Doubao', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359254465049677824, 1, 1000000, 359110565693620224, 'doubao-seed-1.6-thinking', NULL, NULL, NULL, NULL, 'doubao-seed-1-6-thinking-250715', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Doubao', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359254550235992064, 1, 1000000, 359110565693620224, 'deepseek-r1', NULL, NULL, NULL, NULL, 'deepseek-r1-250528', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'DeepSeek', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359255963729022976, 1, 1000000, 359110640402563072, 'Spark Max', NULL, NULL, NULL, NULL, 'generalv3.5', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Spark', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359256036009463808, 1, 1000000, 359110640402563072, 'Spark4.0 Ultra', NULL, NULL, NULL, NULL, '4.0Ultra', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Spark', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359256272148779008, 1, 1000000, 359110640402563072, 'Max-32K', NULL, NULL, NULL, NULL, 'max-32k', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Spark', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359256472095444992, 1, 1000000, 359110640402563072, 'Spark Pro', NULL, NULL, NULL, NULL, 'generalv3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Spark', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359256641138479104, 1, 1000000, 359110640402563072, 'Spark Pro-128k', NULL, NULL, NULL, NULL, 'pro-128k', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Spark', 'chatModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359498072033816576, 1, 1000000, 359111448204541952, 'text-embedding-v3', NULL, NULL, NULL, NULL, 'text-embedding-v3', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Embedding', 'embeddingModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); +INSERT INTO `tb_model` (`id`, `dept_id`, `tenant_id`, `provider_id`, `title`, `icon`, `description`, `endpoint`, `request_path`, `model_name`, `api_key`, `extra_config`, `options`, `group_name`, `model_type`, `with_used`, `support_thinking`, `support_tool`, `support_image`, `support_image_b64_only`, `support_video`, `support_audio`, `support_free`) VALUES (359536003377258496, 1, 1000000, 358906187162456064, 'Qwen3-Reranker-8B', NULL, NULL, NULL, NULL, 'Qwen3-Reranker-8B', NULL, NULL, '{\"rerankPath\":\"\",\"chatPath\":\"\",\"llmEndpoint\":\"\",\"embedPath\":\"\"}', 'Qwen', 'rerankModel', 1, 0, 0, NULL, NULL, NULL, NULL, 0); + +-- ---------------------------- +-- Records of tb_sys_option +-- ---------------------------- + +INSERT INTO `tb_mcp` (`id`, `title`, `description`, `config_json`, `dept_id`, `tenant_id`, `created`, `created_by`, `modified`, `modified_by`, `status`) VALUES (365597368948781056, '测试everything', 'MCP测试功能', '{ + \"mcpServers\": { + \"everything\": { + \"command\": \"npx\", + \"args\": [ + \"-y\", + \"@modelcontextprotocol/server-everything\" + ] + } + } +}', 1, 1000000, '2026-01-06 09:57:07', 1, '2026-01-06 09:57:07', 1, 0); +INSERT INTO `tb_mcp` (`id`, `title`, `description`, `config_json`, `dept_id`, `tenant_id`, `created`, `created_by`, `modified`, `modified_by`, `status`) VALUES (365956218142994432, '12306购票综合查询', '12306购票综合查询', '{ + \"mcpServers\": { + \"12306-mcp\": { + \"command\": \"npx\", + \"args\": [ + \"-y\", + \"12306-mcp\" + ] + } + } +}', 1, 1000000, '2026-01-06 09:56:44', 1, '2026-01-06 09:56:44', 1, 0);