初始化
This commit is contained in:
36
.dockerignore
Normal file
36
.dockerignore
Normal file
@@ -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
|
||||||
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -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
|
||||||
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
2
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -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
|
||||||
53
.workflow/branch-pipeline.yml
Normal file
53
.workflow/branch-pipeline.yml
Normal file
@@ -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:
|
||||||
|
- .*
|
||||||
51
.workflow/master-pipeline.yml
Normal file
51
.workflow/master-pipeline.yml
Normal file
@@ -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
|
||||||
24
.workflow/pipeline-sbom.yml
Normal file
24
.workflow/pipeline-sbom.yml
Normal file
@@ -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'
|
||||||
40
.workflow/pr-pipeline.yml
Normal file
40
.workflow/pr-pipeline.yml
Normal file
@@ -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
|
||||||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -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 <cennac@163.com>"
|
||||||
|
|
||||||
|
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
|
||||||
135
README.md
Normal file
135
README.md
Normal file
@@ -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`
|
||||||
|
|
||||||
80
docker-compose.yml
Normal file
80
docker-compose.yml
Normal file
@@ -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
|
||||||
32
easyflow-api/easyflow-api-admin/pom.xml
Normal file
32
easyflow-api/easyflow-api-admin/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easyflow-api-admin</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-auth</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-job</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-captcha</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -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<BotCategoryService, BotCategory> {
|
||||||
|
public BotCategoryController(BotCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotService, Bot> {
|
||||||
|
|
||||||
|
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<String, Object> 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<Long> generateConversationId() {
|
||||||
|
long nextId = new SnowFlakeIDKeyGenerator().nextId();
|
||||||
|
return Result.ok(nextId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("updateOptions")
|
||||||
|
@SaCheckPermission("/api/v1/bot/save")
|
||||||
|
public Result<Void> updateOptions(@JsonBody("id") BigInteger id,
|
||||||
|
@JsonBody("options") Map<String, Object> options) {
|
||||||
|
Bot aiBot = service.getById(id);
|
||||||
|
Map<String, Object> 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<Void> updateLlmOptions(@JsonBody("id")
|
||||||
|
BigInteger id, @JsonBody("llmOptions")
|
||||||
|
Map<String, Object> llmOptions) {
|
||||||
|
Bot aiBot = service.getById(id);
|
||||||
|
Map<String, Object> 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<String> 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<Map<String, String>> messages,
|
||||||
|
@JsonBody(value = "attachments") List<String> 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<Void> updateBotLlmId(@RequestBody
|
||||||
|
Bot aiBot) {
|
||||||
|
service.updateBotLlmId(aiBot);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getDetail")
|
||||||
|
@SaIgnore
|
||||||
|
public Result<Bot> getDetail(String id) {
|
||||||
|
return Result.ok(botService.getDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SaIgnore
|
||||||
|
public Result<Bot> detail(String id) {
|
||||||
|
Bot data = botService.getDetail(id);
|
||||||
|
if (data == null) {
|
||||||
|
return Result.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> getDefaultLlmOptions() {
|
||||||
|
Map<String, Object> 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<String, Object> errorRespnseMsg(int errorCode, String message) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
result.put("error", errorCode);
|
||||||
|
result.put("message", message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<?> onRemoveBefore(Collection<Serializable> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotDocumentCollectionService, BotDocumentCollection> {
|
||||||
|
public BotDocumentCollectionController(BotDocumentCollectionService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("list")
|
||||||
|
@Override
|
||||||
|
public Result<List<BotDocumentCollection>> list(BotDocumentCollection entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<BotDocumentCollection> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotMcpService, BotMcp> {
|
||||||
|
public BotMcpController(BotMcpService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("updateBotMcpToolIds")
|
||||||
|
public Result<?> save(@JsonBody("botId") BigInteger botId,
|
||||||
|
@JsonBody("mcpSelectedData") List<Map<String, List<List<String>>>> mcpSelectedData) {
|
||||||
|
service.updateBotMcpToolIds(botId, mcpSelectedData);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotMessageService, BotMessage> {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotModelService, BotModel> {
|
||||||
|
public BotModelController(BotModelService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotPluginService, BotPlugin> {
|
||||||
|
|
||||||
|
public BotPluginController(BotPluginService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BotPluginService botPluginService;
|
||||||
|
|
||||||
|
@GetMapping("list")
|
||||||
|
public Result<List<BotPlugin>> list(BotPlugin entity, Boolean asTree, String sortKey, String sortType){
|
||||||
|
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
|
||||||
|
List<BotPlugin> botPlugins = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||||
|
|
||||||
|
List<BotPlugin> list = Tree.tryToTree(botPlugins, asTree);
|
||||||
|
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/getList")
|
||||||
|
public Result<List<Plugin>> getList(@JsonBody(value = "botId", required = true) String botId){
|
||||||
|
return Result.ok(botPluginService.getList(botId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/getBotPluginToolIds")
|
||||||
|
public Result<List<BigInteger>> getBotPluginToolIds(@JsonBody(value = "botId", required = true) String botId){
|
||||||
|
return Result.ok(botPluginService.getBotPluginToolIds(botId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/doRemove")
|
||||||
|
public Result<Boolean> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<BotRecentlyUsedService, BotRecentlyUsed> {
|
||||||
|
public BotRecentlyUsedController(BotRecentlyUsedService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotWorkflowService, BotWorkflow> {
|
||||||
|
public BotWorkflowController(BotWorkflowService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("list")
|
||||||
|
@Override
|
||||||
|
public Result<List<BotWorkflow>> list(BotWorkflow entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<BotWorkflow> botWorkflows = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||||
|
List<BotWorkflow> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DocumentChunkService, DocumentChunk> {
|
||||||
|
|
||||||
|
@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<String, Object> 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<BigInteger> deleteList = new ArrayList<>();
|
||||||
|
deleteList.add(chunkId);
|
||||||
|
documentStore.delete(deleteList, options);
|
||||||
|
documentChunkService.removeChunk(knowledge, chunkId);
|
||||||
|
|
||||||
|
return super.remove(chunkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DocumentCollectionCategoryService, DocumentCollectionCategory> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocumentCollectionMapper documentCollectionMapper;
|
||||||
|
|
||||||
|
public DocumentCollectionCategoryController(DocumentCollectionCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
|
||||||
|
ids.forEach(id -> {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create().eq(DocumentCollection::getCategoryId, id);
|
||||||
|
List<DocumentCollection> documentCollections = documentCollectionMapper.selectListByQuery(queryWrapper);
|
||||||
|
if (!documentCollections.isEmpty()) {
|
||||||
|
throw new BusinessException("请先删除该分类下的所有知识库");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return super.onRemoveBefore(ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DocumentCollectionService, DocumentCollection> {
|
||||||
|
|
||||||
|
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<String, Object> 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<List<Document>> search(@RequestParam BigInteger knowledgeId, @RequestParam String keyword) {
|
||||||
|
return Result.ok(service.search(knowledgeId, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<Void> onRemoveBefore(Collection<Serializable> 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<DocumentCollection> detail(String id) {
|
||||||
|
return Result.ok(service.getDetail(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DocumentService, Document> {
|
||||||
|
|
||||||
|
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<Serializable> 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>> 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<Document> documents = service.list(queryWrapper);
|
||||||
|
List<Document> list = Tree.tryToTree(documents, asTree);
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("documentList")
|
||||||
|
@SaCheckPermission("/api/v1/documentCollection/query")
|
||||||
|
public Result<Page<Document>> 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<Document> 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<Boolean> 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<Document> 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<Document> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<DocumentHistoryService, DocumentHistory> {
|
||||||
|
public DocumentHistoryController(DocumentHistoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<byte[]> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<McpService, Mcp> {
|
||||||
|
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<Mcp>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
|
||||||
|
Result<Page<Mcp>> page = super.page(request, sortKey, sortType, pageNumber, pageSize);
|
||||||
|
return service.pageMcp(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/getMcpTools")
|
||||||
|
public Result<Mcp> getMcpTools(@JsonBody("id") String id) {
|
||||||
|
|
||||||
|
return Result.ok(service.getMcpTools(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("pageTools")
|
||||||
|
public Result<Page<Mcp>> 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<Mcp> mcpPage = queryPage(new Page<>(pageNumber, pageSize), queryWrapper);
|
||||||
|
|
||||||
|
return Result.ok(service.pageTools(mcpPage));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ModelService, Model> {
|
||||||
|
|
||||||
|
public ModelController(ModelService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ModelService modelService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
ModelMapper modelMapper;
|
||||||
|
|
||||||
|
@GetMapping("list")
|
||||||
|
@SaCheckPermission("/api/v1/model/query")
|
||||||
|
public Result<List<Model>> list(Model entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<Model> 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<Map<String, Map<String, List<Model>>>> getList(Model entity) {
|
||||||
|
return Result.ok(modelService.getList(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/addAiLlm")
|
||||||
|
@SaCheckPermission("/api/v1/model/save")
|
||||||
|
public Result<Boolean> 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<Map<String, List<Model>>> selectLlmByProviderCategory(Model entity, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
BaseMapper<Model> mapper = service.getMapper();
|
||||||
|
List<Model> totalList = mapper.selectListWithRelationsByQuery(queryWrapper);
|
||||||
|
Map<String, List<Model>> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName));
|
||||||
|
return Result.ok(groupList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/selectLlmByProviderAndModelType")
|
||||||
|
@SaCheckPermission("/api/v1/model/query")
|
||||||
|
public Result<Map<String, List<Model>>> 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<Model> totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||||
|
Map<String, List<Model>> 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<List<Model>> selectLlmList(Model entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<Model> 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<Serializable> ids = Collections.singletonList(id);
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create().in(Model::getId, ids);
|
||||||
|
service.remove(queryWrapper);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ModelProviderService, ModelProvider> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PluginCategoryService, PluginCategory> {
|
||||||
|
public PluginCategoryController(PluginCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PluginCategoryService pluginCategoryService;
|
||||||
|
|
||||||
|
@GetMapping("/doRemoveCategory")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/remove")
|
||||||
|
public Result<Boolean> doRemoveCategory(@RequestParam("id") BigInteger id){
|
||||||
|
|
||||||
|
return Result.ok(pluginCategoryService.doRemoveCategory(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PluginCategoryMappingService, PluginCategoryMapping> {
|
||||||
|
public PluginCategoryMappingController(PluginCategoryMappingService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PluginCategoryMappingService relationService;
|
||||||
|
|
||||||
|
@PostMapping("/updateRelation")
|
||||||
|
public Result<Boolean> updateRelation(
|
||||||
|
@JsonBody(value="pluginId") BigInteger pluginId,
|
||||||
|
@JsonBody(value="categoryIds") ArrayList<BigInteger> categoryIds
|
||||||
|
){
|
||||||
|
return Result.ok(relationService.updateRelation(pluginId, categoryIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getPluginCategories")
|
||||||
|
public Result<List<PluginCategory>> getPluginCategories(@RequestParam(value="pluginId") BigInteger pluginId
|
||||||
|
){
|
||||||
|
return Result.ok(relationService.getPluginCategories(pluginId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PluginService, Plugin> {
|
||||||
|
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<Boolean> savePlugin(@JsonBody Plugin plugin){
|
||||||
|
|
||||||
|
return Result.ok(pluginService.savePlugin(plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/plugin/update")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/save")
|
||||||
|
public Result<Boolean> updatePlugin(@JsonBody Plugin plugin){
|
||||||
|
|
||||||
|
return Result.ok(pluginService.updatePlugin(plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/plugin/remove")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/remove")
|
||||||
|
public Result<Boolean> removePlugin(@JsonBody(value = "id", required = true) String id){
|
||||||
|
|
||||||
|
return Result.ok(pluginService.removePlugin(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/getList")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/query")
|
||||||
|
public Result<List<Plugin>> getList(){
|
||||||
|
return Result.ok(pluginService.getList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pageByCategory")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/query")
|
||||||
|
public Result<Page<Plugin>> 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<Plugin> queryPage(Page<Plugin> page, QueryWrapper queryWrapper) {
|
||||||
|
return service.getMapper().paginateWithRelations(page, queryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PluginItemService, PluginItem> {
|
||||||
|
public PluginItemController(PluginItemService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PluginItemService pluginItemService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BotPluginService botPluginService;
|
||||||
|
|
||||||
|
@PostMapping("/tool/save")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/save")
|
||||||
|
public Result<Boolean> 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<List<PluginItem>> 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<Boolean> updatePlugin(@JsonBody PluginItem pluginItem){
|
||||||
|
return Result.ok(pluginItemService.updatePlugin(pluginItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tool/list")
|
||||||
|
@SaCheckPermission("/api/v1/plugin/query")
|
||||||
|
public Result<List<PluginItem>> 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<Serializable> ids) {
|
||||||
|
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||||
|
queryWrapper.in(BotPlugin::getPluginItemId, ids);
|
||||||
|
|
||||||
|
boolean exists = botPluginService.exists(queryWrapper);
|
||||||
|
if (exists){
|
||||||
|
return Result.fail(1, "此工具还关联着bot,请先取消关联!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ResourceCategoryService, ResourceCategory> {
|
||||||
|
|
||||||
|
public ResourceCategoryController(ResourceCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ResourceService, Resource> {
|
||||||
|
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<Resource> queryPage(Page<Resource> page, QueryWrapper queryWrapper) {
|
||||||
|
queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
|
||||||
|
return super.queryPage(page, queryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Node> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowCategoryService, WorkflowCategory> {
|
||||||
|
|
||||||
|
public WorkflowCategoryController(WorkflowCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<WorkflowService, Workflow> {
|
||||||
|
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<String, Object> variables) {
|
||||||
|
|
||||||
|
Workflow workflow = service.getById(workflowId);
|
||||||
|
if (workflow == null) {
|
||||||
|
return Result.fail(1, "工作流不存在");
|
||||||
|
}
|
||||||
|
Map<String, Object> res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行工作流 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/runAsync")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
|
||||||
|
@JsonBody("variables") Map<String, Object> 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<ChainInfo> getChainStatus(@JsonBody(value = "executeId") String executeId,
|
||||||
|
@JsonBody("nodes") List<NodeInfo> nodes) {
|
||||||
|
ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复工作流运行 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/resume")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
|
||||||
|
@JsonBody("confirmParams") Map<String, Object> confirmParams) {
|
||||||
|
chainExecutor.resumeAsync(executeId, confirmParams);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/importWorkFlow")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<Void> 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<String> 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<Parameter> chainParameters = definition.getStartParameters();
|
||||||
|
Map<String, Object> 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<Workflow> detail(String id) {
|
||||||
|
Workflow workflow = service.getDetail(id);
|
||||||
|
return Result.ok(workflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/copy")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<Void> 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<Serializable> ids) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||||
|
queryWrapper.in("workflow_id", ids);
|
||||||
|
boolean exists = botWorkflowService.exists(queryWrapper);
|
||||||
|
if (exists) {
|
||||||
|
return Result.fail(1, "此工作流还关联有bot,请先取消关联后再删除!");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowExecResultService, WorkflowExecResult> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowExecStepService recordStepService;
|
||||||
|
|
||||||
|
public WorkflowExecResultController(WorkflowExecResultService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/del")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@SaCheckPermission("/api/v1/workflow/remove")
|
||||||
|
public Result<Void> 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<WorkflowExecResult> queryPage(Page<WorkflowExecResult> page, QueryWrapper queryWrapper) {
|
||||||
|
queryWrapper.eq(WorkflowExecResult::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
|
||||||
|
Page<WorkflowExecResult> res = super.queryPage(page, queryWrapper);
|
||||||
|
for (WorkflowExecResult record : res.getRecords()) {
|
||||||
|
record.setWorkflowJson(WorkFlowUtil.removeSensitiveInfo(record.getWorkflowJson()));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowExecStepService, WorkflowExecStep> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowExecResultService execRecordService;
|
||||||
|
|
||||||
|
public WorkflowExecStepController(WorkflowExecStepService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getListByRecordId")
|
||||||
|
public Result<List<WorkflowExecStep>> 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<String, String> 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<WorkflowExecStep> list = service.list(w);
|
||||||
|
for (WorkflowExecStep step : list) {
|
||||||
|
step.setNodeData(null);
|
||||||
|
step.setNodeType(idTypeMap.get(step.getNodeId()));
|
||||||
|
}
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LoginVO> login(@JsonBody LoginDTO loginDTO) {
|
||||||
|
LoginVO res = authService.login(loginDTO);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("logout")
|
||||||
|
public Result<Void> logout() {
|
||||||
|
StpUtil.logout();
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getPermissions")
|
||||||
|
public Result<List<String>> getPermissions() {
|
||||||
|
List<String> permissionList = StpUtil.getPermissionList();
|
||||||
|
return Result.ok(permissionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<List<DictItem>> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) {
|
||||||
|
DictLoader loader = dictManager.getLoader(code);
|
||||||
|
if (loader == null) {
|
||||||
|
return Result.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||||
|
Dict dict = loader.load(keyword, parameterMap);
|
||||||
|
if (dict == null) {
|
||||||
|
return Result.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return Result.ok(dict.getItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ImageCaptchaVO> getCaptcha() {
|
||||||
|
return application.generateCaptcha(CaptchaTypeConstant.SLIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码校验
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/check", produces = "application/json")
|
||||||
|
public ApiResponse<String> checkCaptcha(@RequestBody CaptchaData data) {
|
||||||
|
ApiResponse<?> response = application.matching(data.getId(), data.getData());
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
return ApiResponse.ofError("验证码错误");
|
||||||
|
}
|
||||||
|
return ApiResponse.ofSuccess(data.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UploadResVo> 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<UploadResVo> 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<UploadResVo> uploadPrePath(MultipartFile file, String prePath) {
|
||||||
|
String path = storageService.save(file,prePath);
|
||||||
|
UploadResVo resVo = new UploadResVo();
|
||||||
|
resVo.setPath(path);
|
||||||
|
return Result.ok(resVo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DatacenterTableService, DatacenterTable> {
|
||||||
|
|
||||||
|
@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<Void> saveTable(@RequestBody DatacenterTable entity) {
|
||||||
|
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
|
||||||
|
List<DatacenterTableField> 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<DatacenterTable> detailInfo(BigInteger tableId) {
|
||||||
|
DatacenterTable table = service.getById(tableId);
|
||||||
|
QueryWrapper wrapper = QueryWrapper.create();
|
||||||
|
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||||
|
wrapper.orderBy("id");
|
||||||
|
List<DatacenterTableField> fields = fieldsService.list(wrapper);
|
||||||
|
table.setFields(fields);
|
||||||
|
return Result.ok(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/removeTable")
|
||||||
|
@SaCheckPermission("/api/v1/datacenterTable/remove")
|
||||||
|
public Result<Void> removeTable(BigInteger tableId) {
|
||||||
|
service.removeTable(tableId);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getHeaders")
|
||||||
|
@SaCheckPermission("/api/v1/datacenterTable/query")
|
||||||
|
public Result<List<HeaderVo>> getHeaders(BigInteger tableId) {
|
||||||
|
List<HeaderVo> res = service.getHeaders(tableId);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getPageData")
|
||||||
|
@SaCheckPermission("/api/v1/datacenterTable/query")
|
||||||
|
public Result<Page<Row>> getPageData(DatacenterQuery where) {
|
||||||
|
Page<Row> res = service.getPageData(where);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/saveValue")
|
||||||
|
@SaCheckPermission("/api/v1/datacenterTable/save")
|
||||||
|
public Result<Void> saveValue(@RequestParam Map<String, Object> 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<Void> removeValue(@RequestParam Map<String, Object> 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<ReadResVo> importData(MultipartFile file, @RequestParam Map<String, Object> 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<DatacenterTableField> 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<JSONObject> errorRows = listener.getErrorRows();
|
||||||
|
return Result.ok(new ReadResVo(successCount, errorCount, totalCount, errorRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getTemplate")
|
||||||
|
public void getTemplate(BigInteger tableId, HttpServletResponse response) throws Exception {
|
||||||
|
List<DatacenterTableField> 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<List<String>> headList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (DatacenterTableField field : fields) {
|
||||||
|
List<String> head = new ArrayList<>();
|
||||||
|
head.add(field.getFieldName());
|
||||||
|
headList.add(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入Excel
|
||||||
|
EasyExcel.write(response.getOutputStream())
|
||||||
|
.head(headList)
|
||||||
|
.sheet("模板")
|
||||||
|
.doWrite(new ArrayList<>()); // 写入空数据,只生成模板
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DatacenterTableFieldService, DatacenterTableField> {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysJobService, SysJob> {
|
||||||
|
public SysJobController(SysJobService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/start")
|
||||||
|
@SaCheckPermission("/api/v1/sysJob/save")
|
||||||
|
public Result<Void> 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<Void> stop(BigInteger id) {
|
||||||
|
SysJob sysJob = new SysJob();
|
||||||
|
sysJob.setId(id);
|
||||||
|
sysJob.setStatus(EnumJobStatus.STOP.getCode());
|
||||||
|
ArrayList<Serializable> ids = new ArrayList<>();
|
||||||
|
ids.add(id);
|
||||||
|
service.deleteJob(ids);
|
||||||
|
service.updateById(sysJob);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getNextTimes")
|
||||||
|
public Result<List<String>> getNextTimes(String cronExpression) throws Exception{
|
||||||
|
CronExpression ex = new CronExpression(cronExpression);
|
||||||
|
List<String> 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<Serializable> ids) {
|
||||||
|
service.deleteJob(ids);
|
||||||
|
return super.onRemoveBefore(ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysJobLogService, SysJobLog> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysAccountService, SysAccount> {
|
||||||
|
public SysAccountController(SysAccountService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@LogRecord("分页查询")
|
||||||
|
protected Page<SysAccount> queryPage(Page<SysAccount> 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<Serializable> ids) {
|
||||||
|
List<SysAccount> 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<SysAccount> myProfile() {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
SysAccount sysAccount = service.getById(account.getId());
|
||||||
|
return Result.ok(sysAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/updateProfile")
|
||||||
|
public Result<Void> 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<Void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysApiKeyService, SysApiKey> {
|
||||||
|
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<PkVo> 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<SysApiKey>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
|
||||||
|
Result<Page<SysApiKey>> pageResult = (Result<Page<SysApiKey>>) super.page(request, sortKey, sortType, pageNumber, pageSize);
|
||||||
|
Page<SysApiKey> data = pageResult.getData();
|
||||||
|
List<SysApiKey> records = data.getRecords();
|
||||||
|
records.forEach(record -> {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create().select(SysApiKeyResourceMapping::getApiKeyResourceId).eq(SysApiKeyResourceMapping::getApiKeyId, record.getId());
|
||||||
|
List<BigInteger> resourceIds = sysApiKeyResourceMappingService.listAs(queryWrapper, BigInteger.class);
|
||||||
|
record.setPermissionIds(resourceIds);
|
||||||
|
});
|
||||||
|
return pageResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysApiKeyResourceService, SysApiKeyResource> {
|
||||||
|
public SysApiKeyResourceController(SysApiKeyResourceService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysApiKeyResourceMappingService, SysApiKeyResourceMapping> {
|
||||||
|
public SysApiKeyResourceMappingController(SysApiKeyResourceMappingService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysDeptService, SysDept> {
|
||||||
|
|
||||||
|
@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>> list(SysDept entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<SysDept> 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<Serializable> ids) {
|
||||||
|
List<SysDept> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysDictService, SysDict> {
|
||||||
|
|
||||||
|
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<Serializable> ids) {
|
||||||
|
List<SysDict> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysDictItemService, SysDictItem> {
|
||||||
|
public SysDictItemController(SysDictItemService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysLogService, SysLog> {
|
||||||
|
public SysLogController(SysLogService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@LogRecord("分页查询")
|
||||||
|
protected Page<SysLog> queryPage(Page<SysLog> page, QueryWrapper queryWrapper) {
|
||||||
|
RelationManager.setQueryRelations(Collections.singleton("account"));
|
||||||
|
return service.getMapper().paginateWithRelations(page, queryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysMenuService, SysMenu> {
|
||||||
|
|
||||||
|
@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>> list(SysMenu entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
|
List<SysMenu> sysMenus = service.list(queryWrapper);
|
||||||
|
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("tree")
|
||||||
|
public Result<List<SysMenu>> tree(SysMenu entity) {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
BigInteger accountId = account.getId();
|
||||||
|
List<SysMenu> sysMenus = service.getMenusByAccountId(entity,accountId);
|
||||||
|
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("treeV2")
|
||||||
|
public Result<List<MenuVo>> treeV2(SysMenu entity) {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
BigInteger accountId = account.getId();
|
||||||
|
List<SysMenu> sysMenus = service.getMenusByAccountId(entity,accountId);
|
||||||
|
List<MenuVo> menuVos = buildMenuVos(sysMenus);
|
||||||
|
return Result.ok(Tree.tryToTree(menuVos, "id", "parentId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角色id获取菜单树
|
||||||
|
*/
|
||||||
|
@GetMapping("getCheckedByRoleId/{roleId}")
|
||||||
|
public Result<List<BigInteger>> getCheckedByRoleId(@PathVariable BigInteger roleId) {
|
||||||
|
QueryWrapper rmWrapper = QueryWrapper.create();
|
||||||
|
rmWrapper.eq("role_id", roleId);
|
||||||
|
List<SysRoleMenu> list = sysRoleMenuService.list(rmWrapper);
|
||||||
|
List<BigInteger> 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<SysMenu> 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<Serializable> 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<MenuVo> buildMenuVos(List<SysMenu> sysMenus) {
|
||||||
|
List<MenuVo> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Map<String, Object>> list(String[] keys) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
if (keys == null || keys.length == 0) {
|
||||||
|
return Result.ok(data);
|
||||||
|
}
|
||||||
|
List<SysOption> 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<Void> save(@JsonBody Map<String, String> 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<Void> 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<SysOption> getByKey(String key) {
|
||||||
|
if (key == null || key.isEmpty()) {
|
||||||
|
throw new BusinessException("key is empty");
|
||||||
|
}
|
||||||
|
return Result.ok(service.getByOptionKey(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 职位表 控制层。
|
||||||
|
* <p>
|
||||||
|
* 提供岗位的增删改查及状态管理功能。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author ArkLight
|
||||||
|
* @since 2025-03-14
|
||||||
|
*/
|
||||||
|
@RestController("sysPositionController")
|
||||||
|
@RequestMapping("/api/v1/sysPosition")
|
||||||
|
public class SysPositionController extends BaseCurdController<SysPositionService, SysPosition> {
|
||||||
|
public SysPositionController(SysPositionService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询岗位列表
|
||||||
|
* <p>
|
||||||
|
* 支持按岗位名称模糊查询,按状态、编码精确查询。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param request 请求对象
|
||||||
|
* @param sortKey 排序字段
|
||||||
|
* @param sortType 排序类型 (asc/desc)
|
||||||
|
* @param pageNumber 当前页码
|
||||||
|
* @param pageSize 每页条数
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@GetMapping("page")
|
||||||
|
public Result<Page<SysPosition>> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysRoleService, SysRole> {
|
||||||
|
|
||||||
|
@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<Void> saveRoleMenu(@PathVariable("roleId") BigInteger roleId, @JsonBody List<String> keys) {
|
||||||
|
service.saveRoleMenu(roleId, keys);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色菜单id
|
||||||
|
*/
|
||||||
|
@GetMapping("/getRoleMenuIds")
|
||||||
|
@SaCheckPermission("/api/v1/sysRole/query")
|
||||||
|
public Result<List<BigInteger>> getRoleMenuIds(BigInteger roleId) {
|
||||||
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq("role_id", roleId);
|
||||||
|
List<BigInteger> 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<List<BigInteger>> getRoleDeptIds(BigInteger roleId) {
|
||||||
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq("role_id", roleId);
|
||||||
|
List<BigInteger> res = sysRoleDeptService.list(w).stream().map(SysRoleDept::getDeptId).collect(Collectors.toList());
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存角色
|
||||||
|
*/
|
||||||
|
@PostMapping("saveRole")
|
||||||
|
@SaCheckPermission("/api/v1/sysRole/save")
|
||||||
|
public Result<Void> 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<Serializable> ids) {
|
||||||
|
List<SysRole> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysUserFeedbackService, SysUserFeedback> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
easyflow-api/easyflow-api-mcp/pom.xml
Normal file
14
easyflow-api/easyflow-api-mcp/pom.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easyflow-api-mcp</artifactId>
|
||||||
|
|
||||||
|
</project>
|
||||||
37
easyflow-api/easyflow-api-public/pom.xml
Normal file
37
easyflow-api/easyflow-api-public/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easyflow-api-public</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.javaparser</groupId>
|
||||||
|
<artifactId>javaparser-core</artifactId>
|
||||||
|
<version>3.25.8</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -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<String> filePath = getAllFilePaths(dir);
|
||||||
|
for (String path : filePath) {
|
||||||
|
extractCommentsFromFile(path, mapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getAllFilePaths(String directoryPath) throws IOException {
|
||||||
|
Path startPath = Paths.get(directoryPath);
|
||||||
|
|
||||||
|
try (Stream<Path> 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<AnnotationExpr> 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<AnnotationExpr> 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("//", "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Bot> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Workflow> 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<String, Object> variables) {
|
||||||
|
|
||||||
|
Workflow workflow = workflowService.getById(workflowId);
|
||||||
|
if (workflow == null) {
|
||||||
|
return Result.fail(1, "工作流不存在");
|
||||||
|
}
|
||||||
|
Map<String, Object> res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行工作流 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/runAsync")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
|
||||||
|
@JsonBody("variables") Map<String, Object> 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<ChainInfo> getChainStatus(@JsonBody(value = "executeId") String executeId,
|
||||||
|
@JsonBody("nodes") List<NodeInfo> nodes) {
|
||||||
|
ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复工作流运行 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/resume")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
|
||||||
|
@JsonBody("confirmParams") Map<String, Object> 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<Parameter> chainParameters = definition.getStartParameters();
|
||||||
|
Map<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Void> failed = Result.fail(401, "密钥不正确");
|
||||||
|
ResponseUtil.renderJson(response, failed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sysApiKeyService.checkApikeyPermission(apiKey, requestURI);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
easyflow-api/easyflow-api-usercenter/pom.xml
Normal file
29
easyflow-api/easyflow-api-usercenter/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easyflow-api-usercenter</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-auth</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-captcha</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -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<BotCategoryService, BotCategory> {
|
||||||
|
public UcBotCategoryController(BotCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotService, Bot> {
|
||||||
|
|
||||||
|
private final ModelService modelService;
|
||||||
|
private final BotWorkflowService botWorkflowService;
|
||||||
|
private final BotDocumentCollectionService botDocumentCollectionService;
|
||||||
|
@Resource
|
||||||
|
private BotService botService;
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("defaultCache") // 指定 Bean 名称
|
||||||
|
private Cache<String, Object> 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<Long> generateConversationId() {
|
||||||
|
long nextId = new SnowFlakeIDKeyGenerator().nextId();
|
||||||
|
return Result.ok(nextId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("updateOptions")
|
||||||
|
@SaCheckPermission("/api/v1/bot/save")
|
||||||
|
public Result<Void> updateOptions(@JsonBody("id") BigInteger id,
|
||||||
|
@JsonBody("options") Map<String, Object> options) {
|
||||||
|
Bot aiBot = service.getById(id);
|
||||||
|
Map<String, Object> 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<Void> updateLlmOptions(@JsonBody("id")
|
||||||
|
BigInteger id, @JsonBody("llmOptions")
|
||||||
|
Map<String, Object> llmOptions) {
|
||||||
|
Bot aiBot = service.getById(id);
|
||||||
|
Map<String, Object> 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<String> 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<Map<String, String>> messages,
|
||||||
|
@JsonBody(value = "attachments") List<String> 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<Void> updateBotLlmId(@RequestBody
|
||||||
|
Bot aiBot) {
|
||||||
|
service.updateBotLlmId(aiBot);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getDetail")
|
||||||
|
@SaIgnore
|
||||||
|
public Result<Bot> getDetail(String id) {
|
||||||
|
return Result.ok(botService.getDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SaIgnore
|
||||||
|
public Result<Bot> detail(String id) {
|
||||||
|
Bot data = botService.getDetail(id);
|
||||||
|
if (data == null) {
|
||||||
|
return Result.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> 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<String, Object> 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<String, Object> getDefaultLlmOptions() {
|
||||||
|
Map<String, Object> 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<String, Object> errorRespnseMsg(int errorCode, String message) {
|
||||||
|
HashMap<String, Object> result = new HashMap<>();
|
||||||
|
result.put("error", errorCode);
|
||||||
|
result.put("message", message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<?> onRemoveBefore(Collection<Serializable> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<BotConversationService, BotConversation> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BotConversationService conversationMessageService;
|
||||||
|
|
||||||
|
public UcBotConversationController(BotConversationService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定会话
|
||||||
|
*/
|
||||||
|
@GetMapping("/deleteConversation")
|
||||||
|
public Result<Void> deleteConversation(String botId, String conversationId) {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
conversationMessageService.deleteConversation(botId, conversationId, account.getId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会话标题
|
||||||
|
*/
|
||||||
|
@GetMapping("/updateConversation")
|
||||||
|
public Result<Void> 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>> 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<BotConversation>> 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<BotConversation> botConversationPage = service.getMapper().paginateWithRelations(pageNumber, pageSize, queryWrapper);
|
||||||
|
return Result.ok(botConversationPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表主键查询数据详情。
|
||||||
|
*
|
||||||
|
* @param id 主键值
|
||||||
|
* @return 内容详情
|
||||||
|
*/
|
||||||
|
@GetMapping("detail")
|
||||||
|
@SaIgnore
|
||||||
|
public Result<BotConversation> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotMessageService, BotMessage> {
|
||||||
|
private final BotMessageService botMessageService;
|
||||||
|
|
||||||
|
public UcBotMessageController(BotMessageService service, BotMessageService botMessageService) {
|
||||||
|
super(service);
|
||||||
|
this.botMessageService = botMessageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getMessages")
|
||||||
|
@SaIgnore
|
||||||
|
public Result<List<ChatMessageVO>> getMessages(BigInteger botId, BigInteger conversationId) {
|
||||||
|
List<ChatMessageVO> res = new ArrayList<>();
|
||||||
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq(BotMessage::getBotId, botId);
|
||||||
|
w.eq(BotMessage::getConversationId, conversationId);
|
||||||
|
List<BotMessage> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<BotRecentlyUsedService, BotRecentlyUsed> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BotService botService;
|
||||||
|
|
||||||
|
public UcBotRecentlyUsedController(BotRecentlyUsedService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getRecentlyBot")
|
||||||
|
public Result<List<Bot>> getRecentlyBot() {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq(BotRecentlyUsed::getCreatedBy,account.getId());
|
||||||
|
w.orderBy(BotRecentlyUsed::getSortNo,true);
|
||||||
|
List<BotRecentlyUsed> list = service.list(w);
|
||||||
|
if (CollectionUtil.isNotEmpty(list)) {
|
||||||
|
List<BigInteger> botIds = list.stream().map(BotRecentlyUsed::getBotId).collect(Collectors.toList());
|
||||||
|
QueryWrapper botQw = QueryWrapper.create();
|
||||||
|
botQw.in(Bot::getId,botIds);
|
||||||
|
List<Bot> 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<Void> 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>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ResourceService, Resource> {
|
||||||
|
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<Resource> queryPage(Page<Resource> page, QueryWrapper queryWrapper) {
|
||||||
|
queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
|
||||||
|
return super.queryPage(page, queryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowCategoryService, WorkflowCategory> {
|
||||||
|
|
||||||
|
public UcWorkflowCategoryController(WorkflowCategoryService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<?> onSaveOrUpdateBefore(WorkflowCategory entity, boolean isSave) {
|
||||||
|
return Result.fail("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
|
||||||
|
return Result.fail("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowService, Workflow> {
|
||||||
|
|
||||||
|
@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<String, Object> variables) {
|
||||||
|
|
||||||
|
Workflow workflow = service.getById(workflowId);
|
||||||
|
if (workflow == null) {
|
||||||
|
return Result.fail(1, "工作流不存在");
|
||||||
|
}
|
||||||
|
Map<String, Object> res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行工作流 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/runAsync")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
|
||||||
|
@JsonBody("variables") Map<String, Object> 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<ChainInfo> getChainStatus(@JsonBody(value = "executeId") String executeId,
|
||||||
|
@JsonBody("nodes") List<NodeInfo> nodes) {
|
||||||
|
ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复工作流运行 - v2
|
||||||
|
*/
|
||||||
|
@PostMapping("/resume")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
|
||||||
|
@JsonBody("confirmParams") Map<String, Object> 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<Parameter> chainParameters = definition.getStartParameters();
|
||||||
|
Map<String, Object> 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<Serializable> ids) {
|
||||||
|
return Result.fail("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowExecResultService, WorkflowExecResult> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowExecStepService recordStepService;
|
||||||
|
|
||||||
|
public UcWorkflowExecResultController(WorkflowExecResultService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@GetMapping("/del")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@SaCheckPermission("/api/v1/workflow/remove")
|
||||||
|
public Result<Void> 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<Page<WorkflowExecResult>> 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<WorkflowExecResult> queryPage(Page<WorkflowExecResult> page, QueryWrapper queryWrapper) {
|
||||||
|
queryWrapper.eq(WorkflowExecResult::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
|
||||||
|
Page<WorkflowExecResult> res = super.queryPage(page, queryWrapper);
|
||||||
|
for (WorkflowExecResult record : res.getRecords()) {
|
||||||
|
record.setWorkflowJson(WorkFlowUtil.removeSensitiveInfo(record.getWorkflowJson()));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkflowExecStepService, WorkflowExecStep> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WorkflowExecResultService execRecordService;
|
||||||
|
|
||||||
|
public UcWorkflowExecStepController(WorkflowExecStepService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据执行记录id获取执行记录步骤列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/getListByRecordId")
|
||||||
|
public Result<List<WorkflowExecStep>> 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<String, String> 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<WorkflowExecStep> list = service.list(w);
|
||||||
|
for (WorkflowExecStep step : list) {
|
||||||
|
step.setNodeData(null);
|
||||||
|
step.setNodeType(idTypeMap.get(step.getNodeId()));
|
||||||
|
}
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LoginVO> login(@JsonBody LoginDTO loginDTO) {
|
||||||
|
LoginVO res = authService.login(loginDTO);
|
||||||
|
return Result.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登出
|
||||||
|
*/
|
||||||
|
@PostMapping("logout")
|
||||||
|
public Result<Void> logout() {
|
||||||
|
StpUtil.logout();
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取权限
|
||||||
|
*/
|
||||||
|
@GetMapping("getPermissions")
|
||||||
|
public Result<List<String>> getPermissions() {
|
||||||
|
List<String> permissionList = StpUtil.getPermissionList();
|
||||||
|
return Result.ok(permissionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<List<DictItem>> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) {
|
||||||
|
DictLoader loader = dictManager.getLoader(code);
|
||||||
|
if (loader == null) {
|
||||||
|
return Result.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||||
|
Dict dict = loader.load(keyword, parameterMap);
|
||||||
|
if (dict == null) {
|
||||||
|
return Result.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return Result.ok(dict.getItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ImageCaptchaVO> getCaptcha() {
|
||||||
|
return application.generateCaptcha(CaptchaTypeConstant.SLIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码校验
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/check", produces = "application/json")
|
||||||
|
public ApiResponse<String> checkCaptcha(@RequestBody CaptchaData data) {
|
||||||
|
ApiResponse<?> response = application.matching(data.getId(), data.getData());
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
return ApiResponse.ofError("验证码错误");
|
||||||
|
}
|
||||||
|
return ApiResponse.ofSuccess(data.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UploadResVo> 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<UploadResVo> 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<UploadResVo> uploadPrePath(MultipartFile file, String prePath) {
|
||||||
|
String path = storageService.save(file,prePath);
|
||||||
|
UploadResVo resVo = new UploadResVo();
|
||||||
|
resVo.setPath(path);
|
||||||
|
return Result.ok(resVo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysAccount> myProfile() {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
SysAccount sysAccount = service.getById(account.getId());
|
||||||
|
return Result.ok(sysAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户信息
|
||||||
|
*/
|
||||||
|
@PostMapping("/updateProfile")
|
||||||
|
public Result<Void> 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<Void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SysUserFeedbackService, SysUserFeedback> {
|
||||||
|
|
||||||
|
@PostMapping("/save")
|
||||||
|
@SaIgnore
|
||||||
|
public Result<?> save(@JsonBody SysUserFeedback entity) {
|
||||||
|
return super.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UcUserFeedbackController(SysUserFeedbackService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
easyflow-api/pom.xml
Normal file
22
easyflow-api/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easyflow-api</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>easyflow-api-admin</module>
|
||||||
|
<module>easyflow-api-mcp</module>
|
||||||
|
<module>easyflow-api-public</module>
|
||||||
|
<module>easyflow-api-usercenter</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
||||||
418
easyflow-chat-protocol.md
Normal file
418
easyflow-chat-protocol.md
Normal file
@@ -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 判断业务,不依赖协议本身,支持其他协议的扩展
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
128
easyflow-commons/easyflow-common-ai/pom.xml
Normal file
128
easyflow-commons/easyflow-common-ai/pom.xml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-commons</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>easyflow-common-ai</name>
|
||||||
|
<artifactId>easyflow-common-ai</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.commonmark</groupId>
|
||||||
|
<artifactId>commonmark</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
<artifactId>jsqlparser</artifactId>
|
||||||
|
<version>4.9</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.easyagents</groupId>
|
||||||
|
<artifactId>easy-agents-bom</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.squareup.okio</groupId>
|
||||||
|
<artifactId>okio</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>ooxml-schemas</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||||
|
<artifactId>httpclient5</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-base</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-options</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-json</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.idev.excel</groupId>
|
||||||
|
<artifactId>fastexcel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package tech.easyflow.common.ai;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天专用SSE发射器
|
||||||
|
*/
|
||||||
|
public class ChatSseEmitter extends SseEmitter {
|
||||||
|
|
||||||
|
// 默认超时时间:30分钟
|
||||||
|
public static final long DEFAULT_TIMEOUT = 5 * 60 * 1000L;
|
||||||
|
|
||||||
|
public ChatSseEmitter() {
|
||||||
|
super(DEFAULT_TIMEOUT);
|
||||||
|
// 注册基础的超时/异常回调(避免连接泄漏)
|
||||||
|
registerBasicCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatSseEmitter(long timeout) {
|
||||||
|
super(timeout);
|
||||||
|
registerBasicCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础回调:仅做简单的连接清理
|
||||||
|
*/
|
||||||
|
private void registerBasicCallbacks() {
|
||||||
|
// 超时回调
|
||||||
|
onTimeout(this::complete);
|
||||||
|
// 异常回调
|
||||||
|
onError(e -> complete());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatSseEmitter create() {
|
||||||
|
return new ChatSseEmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送普通消息(最常用的核心方法)
|
||||||
|
* @param message 消息内容
|
||||||
|
* @throws IOException 发送失败时抛出IO异常
|
||||||
|
*/
|
||||||
|
public void sendMessage(String message) throws IOException {
|
||||||
|
// 封装标准的SSE消息格式
|
||||||
|
send(SseEmitter.event().name("message").data(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速创建发射器
|
||||||
|
*/
|
||||||
|
public ChatSseEmitter createChatSseEmitter() {
|
||||||
|
return new ChatSseEmitter();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package tech.easyflow.common.ai.inteceptor;
|
||||||
|
|
||||||
|
import com.easyagents.core.model.chat.tool.GlobalToolInterceptors;
|
||||||
|
import com.easyagents.core.model.chat.tool.ToolChain;
|
||||||
|
import com.easyagents.core.model.chat.tool.ToolContext;
|
||||||
|
import com.easyagents.core.model.chat.tool.ToolInterceptor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.Map;
|
||||||
|
@Component
|
||||||
|
public class ToolLoggingInterceptor implements ToolInterceptor {
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
GlobalToolInterceptors.addInterceptor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(ToolContext context, ToolChain chain) throws Exception {
|
||||||
|
String toolName = context.getTool().getName();
|
||||||
|
Map<String, Object> args = context.getArgsMap();
|
||||||
|
|
||||||
|
System.out.println("▶ 调用工具: " + toolName + ", 参数: " + args);
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
Object result = chain.proceed(context);
|
||||||
|
System.out.println("✅ 工具返回: " + result);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
long duration = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("⏱️ 耗时: " + duration + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package tech.easyflow.common.ai.plugin;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class NestedParamConverter {
|
||||||
|
|
||||||
|
public static Map<String, Object> convertToNestedParamMap(List<PluginParam> pluginParams) {
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
if (pluginParams == null || pluginParams.isEmpty()) return result;
|
||||||
|
|
||||||
|
for (PluginParam param : pluginParams) {
|
||||||
|
if (!param.isEnabled()) continue;
|
||||||
|
result.put(param.getName(), buildValue(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object buildValue(PluginParam param) {
|
||||||
|
if ("String".equalsIgnoreCase(param.getType())) {
|
||||||
|
return param.getDefaultValue();
|
||||||
|
} else if ("Object".equalsIgnoreCase(param.getType())) {
|
||||||
|
Map<String, Object> objMap = new LinkedHashMap<>();
|
||||||
|
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
|
||||||
|
for (PluginParam child : param.getChildren()) {
|
||||||
|
objMap.put(child.getName(), buildValue(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objMap;
|
||||||
|
} else if ("Array".equalsIgnoreCase(param.getType())) {
|
||||||
|
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
|
||||||
|
PluginParam arrayItemTemplate = param.getChildren().get(0);
|
||||||
|
if ("Array[Object]".equalsIgnoreCase(arrayItemTemplate.getType())) {
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<String, Object> item = new LinkedHashMap<>();
|
||||||
|
for (PluginParam child : arrayItemTemplate.getChildren()) {
|
||||||
|
item.put(child.getName(), buildValue(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(item); // 示例中只添加一个对象
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package tech.easyflow.common.ai.plugin;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.http.*;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class PluginHttpClient {
|
||||||
|
|
||||||
|
private static final int TIMEOUT = 10_000;
|
||||||
|
|
||||||
|
public static JSONObject sendRequest(String url, String method,
|
||||||
|
Map<String, Object> headers,
|
||||||
|
List<PluginParam> pluginParams) {
|
||||||
|
// 1. 处理路径参数
|
||||||
|
String processedUrl = replacePathVariables(url, pluginParams);
|
||||||
|
|
||||||
|
// 2. 初始化请求
|
||||||
|
Method httpMethod = Method.valueOf(method.toUpperCase());
|
||||||
|
HttpRequest request = HttpRequest.of(processedUrl)
|
||||||
|
.method(httpMethod)
|
||||||
|
.timeout(TIMEOUT);
|
||||||
|
// 3. 处理请求头(合并默认头和参数头)
|
||||||
|
processHeaders(request, headers, pluginParams);
|
||||||
|
|
||||||
|
// 4. 处理查询参数和请求体
|
||||||
|
processQueryAndBodyParams(request, httpMethod, pluginParams);
|
||||||
|
|
||||||
|
// 5. 执行请求
|
||||||
|
HttpResponse response = request.execute();
|
||||||
|
return JSONUtil.parseObj(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理请求头(合并默认头和参数头)
|
||||||
|
*/
|
||||||
|
private static void processHeaders(HttpRequest request,
|
||||||
|
Map<String, Object> defaultHeaders,
|
||||||
|
List<PluginParam> params) {
|
||||||
|
// 添加默认头
|
||||||
|
if (ObjectUtil.isNotEmpty(defaultHeaders)) {
|
||||||
|
defaultHeaders.forEach((k, v) -> request.header(k, v.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加参数中指定的头
|
||||||
|
params.stream()
|
||||||
|
.filter(p -> "header".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
|
||||||
|
.forEach(p -> request.header(p.getName(), p.getDefaultValue().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查询参数和请求体
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 处理查询参数和请求体(新增文件参数支持)
|
||||||
|
*/
|
||||||
|
private static void processQueryAndBodyParams(HttpRequest request,
|
||||||
|
Method httpMethod,
|
||||||
|
List<PluginParam> params) {
|
||||||
|
Map<String, Object> queryParams = new HashMap<>();
|
||||||
|
Map<String, Object> bodyParams = new HashMap<>();
|
||||||
|
// 标记是否包含文件参数
|
||||||
|
AtomicBoolean hasMultipartFile = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// 分类参数(同时检测是否有文件)
|
||||||
|
params.stream()
|
||||||
|
.filter(PluginParam::isEnabled)
|
||||||
|
.forEach(p -> {
|
||||||
|
String methodType = p.getMethod().toLowerCase();
|
||||||
|
Object paramValue = buildNestedParamValue(p);
|
||||||
|
|
||||||
|
// 检测是否为文件参数(MultipartFile 类型)
|
||||||
|
if (paramValue instanceof org.springframework.web.multipart.MultipartFile) {
|
||||||
|
hasMultipartFile.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (methodType) {
|
||||||
|
case "query":
|
||||||
|
queryParams.put(p.getName(), paramValue);
|
||||||
|
break;
|
||||||
|
case "body":
|
||||||
|
bodyParams.put(p.getName(), paramValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. 设置查询参数(原有逻辑不变)
|
||||||
|
if (!queryParams.isEmpty()) {
|
||||||
|
request.form(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 设置请求体(分两种情况:有文件 vs 无文件)
|
||||||
|
if (!bodyParams.isEmpty() && (httpMethod == Method.POST || httpMethod == Method.PUT)) {
|
||||||
|
if (hasMultipartFile.get()) {
|
||||||
|
// 2.1 包含文件参数 → 用 multipart/form-data 格式
|
||||||
|
processMultipartBody(request, bodyParams);
|
||||||
|
} else {
|
||||||
|
// 2.2 无文件参数 → 保持原有 JSON 格式
|
||||||
|
request.body(JSONUtil.toJsonStr(bodyParams))
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归构建嵌套参数值
|
||||||
|
* @param param 当前参数
|
||||||
|
* @return 如果是 Object 类型,返回 Map;否则返回 defaultValue
|
||||||
|
*/
|
||||||
|
private static Object buildNestedParamValue(PluginParam param) {
|
||||||
|
// 如果不是 Object 类型,直接返回默认值
|
||||||
|
if (!"Object".equalsIgnoreCase(param.getType())) {
|
||||||
|
return param.getDefaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是 Object 类型,递归处理子参数
|
||||||
|
Map<String, Object> nestedParams = new HashMap<>();
|
||||||
|
if (param.getChildren() != null) {
|
||||||
|
param.getChildren().stream()
|
||||||
|
.filter(PluginParam::isEnabled)
|
||||||
|
.forEach(child -> {
|
||||||
|
Object childValue = buildNestedParamValue(child); // 递归处理子参数
|
||||||
|
nestedParams.put(child.getName(), childValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return nestedParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换URL中的路径变量 {xxx}
|
||||||
|
*/
|
||||||
|
private static String replacePathVariables(String url, List<PluginParam> params) {
|
||||||
|
String result = url;
|
||||||
|
|
||||||
|
// 收集路径参数
|
||||||
|
Map<String, Object> pathParams = new HashMap<>();
|
||||||
|
params.stream()
|
||||||
|
.filter(p -> "path".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
|
||||||
|
.forEach(p -> pathParams.put(p.getName(), p.getDefaultValue()));
|
||||||
|
|
||||||
|
// 替换变量
|
||||||
|
for (Map.Entry<String, Object> entry : pathParams.entrySet()) {
|
||||||
|
result = result.replaceAll("\\{" + entry.getKey() + "\\}",
|
||||||
|
entry.getValue().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processMultipartBody(HttpRequest request, Map<String, Object> bodyParams) {
|
||||||
|
// 手动设置 Content-Type 为 multipart/form-data
|
||||||
|
request.header(Header.CONTENT_TYPE, "multipart/form-data");
|
||||||
|
for (Map.Entry<String, Object> entry : bodyParams.entrySet()) {
|
||||||
|
String paramName = entry.getKey();
|
||||||
|
Object paramValue = entry.getValue();
|
||||||
|
|
||||||
|
if (paramValue instanceof MultipartFile) {
|
||||||
|
MultipartFile file = (MultipartFile) paramValue;
|
||||||
|
try {
|
||||||
|
request.form(paramName, file.getBytes(), file.getOriginalFilename());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(String.format("文件参数处理失败:参数名=%s,文件名=%s",
|
||||||
|
paramName, file.getOriginalFilename()), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 处理普通参数
|
||||||
|
String valueStr;
|
||||||
|
if (paramValue instanceof String || paramValue instanceof Number || paramValue instanceof Boolean) {
|
||||||
|
valueStr = paramValue.toString();
|
||||||
|
} else {
|
||||||
|
valueStr = JSONUtil.toJsonStr(paramValue);
|
||||||
|
}
|
||||||
|
request.form(paramName, valueStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package tech.easyflow.common.ai.plugin;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PluginParam {
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String type;
|
||||||
|
private String method; // Query / Body / Header / PathVariable 等
|
||||||
|
private Object defaultValue;
|
||||||
|
private boolean required;
|
||||||
|
private boolean enabled;
|
||||||
|
private String key;
|
||||||
|
private List<PluginParam> children;
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultValue(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequired() {
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequired(boolean required) {
|
||||||
|
this.required = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PluginParam> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(List<PluginParam> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package tech.easyflow.common.ai.plugin;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PluginParamConverter {
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将JSON字符串转换为PluginParam对象列表
|
||||||
|
* @param jsonStr 数据库中的JSON字符串
|
||||||
|
* @return PluginParam对象列表
|
||||||
|
*/
|
||||||
|
public static List<PluginParam> convertFromJson(String jsonStr) {
|
||||||
|
try {
|
||||||
|
// 将JSON字符串解析为List<Map>结构
|
||||||
|
List<Map<String, Object>> paramMaps = objectMapper.readValue(
|
||||||
|
jsonStr,
|
||||||
|
new TypeReference<List<Map<String, Object>>>(){}
|
||||||
|
);
|
||||||
|
|
||||||
|
List<PluginParam> result = new ArrayList<>();
|
||||||
|
for (Map<String, Object> paramMap : paramMaps) {
|
||||||
|
result.add(convertMapToPluginParam(paramMap));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to convert JSON to PluginParam", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归将Map转换为PluginParam对象
|
||||||
|
*/
|
||||||
|
private static PluginParam convertMapToPluginParam(Map<String, Object> map) {
|
||||||
|
PluginParam param = new PluginParam();
|
||||||
|
param.setKey(getStringValue(map, "key"));
|
||||||
|
param.setName(getStringValue(map, "name"));
|
||||||
|
param.setDescription(getStringValue(map, "description"));
|
||||||
|
param.setType(getStringValue(map, "type"));
|
||||||
|
param.setMethod(getStringValue(map, "method"));
|
||||||
|
param.setDefaultValue(map.get("defaultValue"));
|
||||||
|
param.setRequired(getBooleanValue(map, "required"));
|
||||||
|
param.setEnabled(getBooleanValue(map, "enabled"));
|
||||||
|
|
||||||
|
// 处理子节点
|
||||||
|
if (map.containsKey("children")) {
|
||||||
|
Object childrenObj = map.get("children");
|
||||||
|
if (childrenObj instanceof List) {
|
||||||
|
List<Map<String, Object>> childrenMaps = (List<Map<String, Object>>) childrenObj;
|
||||||
|
List<PluginParam> children = new ArrayList<>();
|
||||||
|
for (Map<String, Object> childMap : childrenMaps) {
|
||||||
|
children.add(convertMapToPluginParam(childMap));
|
||||||
|
}
|
||||||
|
param.setChildren(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStringValue(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
return value != null ? value.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getBooleanValue(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package tech.easyflow.common.ai.rag;
|
||||||
|
|
||||||
|
import com.easyagents.core.document.Document;
|
||||||
|
import com.easyagents.core.document.DocumentSplitter;
|
||||||
|
import com.easyagents.core.document.id.DocumentIdGenerator;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.TypeReference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ExcelDocumentSplitter implements DocumentSplitter {
|
||||||
|
private int rowsPerChunk;
|
||||||
|
private boolean includeHeader;
|
||||||
|
|
||||||
|
public ExcelDocumentSplitter(int rowsPerChunk) {
|
||||||
|
if (rowsPerChunk <= 0) {
|
||||||
|
throw new IllegalArgumentException("rows must be greater than 0");
|
||||||
|
}
|
||||||
|
this.rowsPerChunk = rowsPerChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExcelDocumentSplitter(int rowsPerChunk, boolean includeHeader) {
|
||||||
|
if (rowsPerChunk <= 0) {
|
||||||
|
throw new IllegalArgumentException("rows must be greater than 0");
|
||||||
|
}
|
||||||
|
this.rowsPerChunk = rowsPerChunk;
|
||||||
|
this.includeHeader = includeHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Document> split(Document document, DocumentIdGenerator idGenerator) {
|
||||||
|
if (document == null || document.getContent() == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析JSON数据为表格结构
|
||||||
|
List<List<String>> tableData = JSON.parseObject(document.getContent(),
|
||||||
|
new TypeReference<List<List<String>>>() {});
|
||||||
|
|
||||||
|
if (tableData == null || tableData.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Document> chunks = new ArrayList<>();
|
||||||
|
List<String> headers = includeHeader ? tableData.get(0) : null;
|
||||||
|
int startRow = includeHeader ? 1 : 0;
|
||||||
|
|
||||||
|
// 按照指定行数分割数据
|
||||||
|
for (int i = startRow; i < tableData.size(); i += rowsPerChunk) {
|
||||||
|
int endRow = Math.min(i + rowsPerChunk, tableData.size());
|
||||||
|
|
||||||
|
// 构建当前分块的Markdown表格
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 添加表头(如果包含)
|
||||||
|
if (headers != null) {
|
||||||
|
sb.append("| ").append(String.join(" | ", headers)).append(" |\n");
|
||||||
|
sb.append("|");
|
||||||
|
for (int j = 0; j < headers.size(); j++) {
|
||||||
|
sb.append(" --- |");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加数据行
|
||||||
|
for (int j = i; j < endRow; j++) {
|
||||||
|
List<String> row = tableData.get(j);
|
||||||
|
sb.append("| ").append(String.join(" | ", row)).append(" |\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新文档
|
||||||
|
Document newDocument = new Document();
|
||||||
|
newDocument.addMetadata(document.getMetadataMap());
|
||||||
|
newDocument.setContent(sb.toString());
|
||||||
|
|
||||||
|
if (idGenerator != null) {
|
||||||
|
newDocument.setId(idGenerator.generateId(newDocument));
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.add(newDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRowsPerChunk() {
|
||||||
|
return rowsPerChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowsPerChunk(int rowsPerChunk) {
|
||||||
|
this.rowsPerChunk = rowsPerChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIncludeHeader() {
|
||||||
|
return includeHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncludeHeader(boolean includeHeader) {
|
||||||
|
this.includeHeader = includeHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
easyflow-commons/easyflow-common-all/pom.xml
Normal file
44
easyflow-commons/easyflow-common-all/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-commons</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>easyflow-common-all</name>
|
||||||
|
<artifactId>easyflow-common-all</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-base</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-file-storage</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-options</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-captcha</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user