【v3.8.0 合并】Merge remote-tracking branch 'origin/master' into springboot3

# Conflicts:
#	README.md
#	jeecg-boot/db/tables_nacos.sql
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java
#	jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/controller/JeecgDemoController.java
#	jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/entity/JeecgDemo.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiLogController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApi.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiAuth.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiHeader.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiLog.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiParam.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/filter/ApiAuthFilter.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/mapper/OpenApiLogMapper.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/service/OpenApiLogService.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCommentController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysGatewayRouteController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysCheckRule.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysComment.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDataSource.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartPermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRole.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRolePermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRoleUser.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFillRule.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFormFile.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysGatewayRoute.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysPackPermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysPosition.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysRoleIndex.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTableWhiteList.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTenantPack.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTenantPackUser.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysThirdAccount.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysThirdAppConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUserPosition.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUserTenant.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DuplicateCheckVo.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/one/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/tree/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/tree/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one2/java/${bussiPackage}/controller/${entityPackage}/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-start/pom.xml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/config/flyway/FlywayConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/system/test/SampleTest.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/DynamicRouteLoader.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/resources/application.yml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-more/src/main/java/org/jeecg/modules/test/feign/controller/JeecgTestFeignController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-rocketmq/src/main/java/org/jeecg/modules/test/rocketmq/controller/JeecgMqTestController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/jeecg-cloud-test-seata-order/src/main/java/org/jeecg/modules/test/seata/order/controller/SeataOrderController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
#	jeecg-boot/pom.xml
This commit is contained in:
JEECG
2025-05-15 20:01:54 +08:00
639 changed files with 25705 additions and 10187 deletions

View File

@ -0,0 +1,178 @@
<?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>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-module</artifactId>
<version>3.8.0</version>
</parent>
<artifactId>jeecg-boot-module-airag</artifactId>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<kotlin.version>1.6.21</kotlin.version>
<liteflow.version>2.12.4.1</liteflow.version>
<langchain4j.version>0.35.0</langchain4j.version>
<apache-tika.version>2.9.1</apache-tika.version>
</properties>
<dependencies>
<!-- system单体 api-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-local-api</artifactId>
</dependency>
<!-- 微服务starter和system微服务 api
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-cloud-api</artifactId>
</dependency>-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-aiflow</artifactId>
<version>1.0.4</version>
</dependency>
<!-- aiflow 脚本依赖 -->
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-graaljs</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-groovy</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-kotlin</artifactId>
<version>${liteflow.version}</version>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jsr223</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-aviator</artifactId>
<version>${liteflow.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<artifactId>aviator</artifactId>
<groupId>com.googlecode.aviator</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- aiflow 脚本依赖 -->
<!-- langChain4j model support -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-zhipu-ai</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qianfan</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<artifactId>okio</artifactId>
<groupId>com.squareup.okio</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- langChain4j vextor support -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- langChain4j Document Parser -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>${apache-tika.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-html-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-pdf-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-text-module</artifactId>
<version>${apache-tika.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,12 @@
//package org.jeecg;
//
//import org.springframework.boot.SpringApplication;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
//
//@SpringBootApplication
//public class JeecgAiRagApplication {
//
// public static void main(String[] args) {
// SpringApplication.run(JeecgAiRagApplication.class, args);
// }
//}

View File

@ -0,0 +1,37 @@
package org.jeecg.modules.airag.app.consts;
/**
* AI应用常量类
*
* @author chenrui
* @date 2025/2/25 14:52
*/
public class AiAppConsts {
/**
* 状态:启用
*/
public static final String STATUS_ENABLE = "enable";
/**
* 状态:禁用
*/
public static final String STATUS_DISABLE = "disable";
/**
* 默认应用id
*/
public static final String DEFAULT_APP_ID = "default";
/**
* 应用类型:简单聊天
*/
public static final String APP_TYPE_CHAT_SIMPLE = "chatSimple";
/**
* 应用类型:聊天流(高级编排)
*/
public static final String APP_TYPE_CHAT_FLOW = "chatFLow";
}

View File

@ -0,0 +1,107 @@
package org.jeecg.modules.airag.app.consts;
/**
* @Description: 提示词常量
* @Author: chenrui
* @Date: 2025/3/12 15:03
*/
public class Prompts {
/**
* 根据提示生成智能体提示词
*/
public static final String GENERATE_LLM_PROMPT = "# 角色\n" +
"你是一位专业且高效的AI提示词工程师擅长根据用户多样化需求自动生成高质量的结构化提示词模板具备全面而敏锐的分析能力和出色的创造力。\n" +
"## 要求:\n" +
"1. \"\"\"只输出提示词,不要输出多余解释\"\"\"\n" +
"2. \"\"\"不要在前后增加代码块的md语法.\"\"\"\n" +
"2. 贴合用户需求,描述智能助手的定位、能力、知识储备\n" +
"3. 提示词应清晰、精确、易于理解,在保持质量的同时,尽可能简洁\n" +
"4. 严格按照给定的流程和格式执行任务,确保输出规范准确。\n" +
"\n" +
"## 流程\n" +
"### 1: 需求分析\n" +
"1. 当用户描述需求时严格运用SCQA框架确认核心要素精准分析和联想\"当前场景(Situation)是什么?主要矛盾(Complication)有哪些?需要解决的关键问题(Question)是?预期达成什么效果(Answer)\"\n" +
"2. 通过5W1H细致分析和联想细节\"目标受众(Who)?使用场景(Where/When)?具体要实现什么(What)?为什么需要这些特征(Why)?如何量化效果(How)\"\n" +
"\n" +
"### 2: 框架选择\n" +
"根据需求从给定模板库中匹配最佳提示词类型:\n" +
"* 角色扮演型:\n" +
"```\n" +
"你将扮演一个人物角色<角色名称>,以下是关于这个角色的详细设定,请根据这些信息来构建你的回答。 \n" +
"\n" +
"**人物基本信息:**\n" +
"- 你是:<角色的名称、身份等基本介绍>\n" +
"- 人称:第一人称\n" +
"- 出身背景与上下文:<交代角色背景信息和上下文>\n" +
"**性格特点:**\n" +
"- <性格特点描述>\n" +
"**语言风格:**\n" +
"- <语言风格描述> \n" +
"**人际关系:**\n" +
"- <人际关系描述>\n" +
"**过往经历:**\n" +
"- <过往经历描述>\n" +
"**经典台词或口头禅:**\n" +
"补充信息: 即你可以将动作、神情语气、心理活动、故事背景放在()中来表示,为对话提供补充信息。\n" +
"- 台词1<角色台词示例1> \n" +
"- 台词2<角色台词示例2>\n" +
"- ...\n" +
"\n" +
"要求: \n" +
"- 要求1\n" +
"- 要求2\n" +
"- ... \n" +
"```\n" +
"* 多步骤型:\n" +
"```\n" +
"# 角色 \n" +
"你是<角色设定(比如:xx领域的专家)>\n" +
"你的目标是<希望模型执行什么任务,达成什么目标>\n" +
"\n" +
"{#以下可以采用先总括,再展开详细说明的方式,描述你希望智能体在每一个步骤如何进行工作,具体的工作步骤数量可以根据实际需求增删#}\n" +
"## 工作步骤 \n" +
"1. <工作流程1的一句话概括> \n" +
"2. <工作流程2的一句话概括> \n" +
"3. <工作流程3的一句话概括>\n" +
"\n" +
"### 第一步 <工作流程1标题> \n" +
"<工作流程步骤1的具体工作要求和举例说明可以分点列出希望在本步骤做哪些事情需要完成什么阶段性的工作目标>\n" +
"### 第二步 <工作流程2标题> \n" +
"<工作流程步骤2的具体工作要求和举例说明可以分点列出希望在本步骤做哪些事情需要完成什么阶段性的工作目标>\n" +
"### 第三步 <工作流程3标题>\n" +
"<工作流程步骤3的具体工作要求和举例说明可以分点列出希望在本步骤做哪些事情需要完成什么阶段性的工作目标>\n" +
"```\n" +
"* 限制性模板:\n" +
"```\n" +
"# 角色:<角色名称>\n" +
"<角色概述和主要职责的一句话描述>\n" +
"\n" +
"## 目标:\n" +
"<角色的工作目标如果有多目标可以分点列出但建议更聚焦1-2个目标>\n" +
"\n" +
"## 技能:\n" +
"1. <为了实现目标角色需要具备的技能1>\n" +
"2. <为了实现目标角色需要具备的技能2>\n" +
"3. <为了实现目标角色需要具备的技能3>\n" +
"\n" +
"## 工作流:\n" +
"1. <描述角色工作流程的第一步>\n" +
"2. <描述角色工作流程的第二步>\n" +
"3. <描述角色工作流程的第三步>\n" +
"\n" +
"## 输出格式:\n" +
"<如果对角色的输出格式有特定要求,可以在这里强调并举例说明想要的输出格式>\n" +
"\n" +
"## 限制:\n" +
"- <描述角色在互动过程中需要遵循的限制条件1>\n" +
"- <描述角色在互动过程中需要遵循的限制条件2>\n" +
"- <描述角色在互动过程中需要遵循的限制条件3>\n" +
"```\n" +
"\n" +
"### 3: 生成优化\n" +
"1. 输出时自动添加三重保障机制:\n" +
" - 反幻觉校验:\"所有数据需标注来源,不确定信息用[需核实]标记\"\n" +
" - 风格校准器:\"对比[目标风格]与生成内容的余弦相似度低于0.7时启动重写\"\n" +
" - 伦理审查模块:\"自动过滤涉及隐私/偏见/违法内容,替换为[合规表达]\"";
}

View File

@ -0,0 +1,156 @@
package org.jeecg.modules.airag.app.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@RestController
@RequestMapping("/airag/app")
@Slf4j
public class AiragAppController extends JeecgController<AiragApp, IAiragAppService> {
@Autowired
private IAiragAppService airagAppService;
@Autowired
private IAiragChatService airagChatService;
/**
* 分页列表查询
*
* @param airagApp
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragApp>> queryPageList(AiragApp airagApp,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<AiragApp> queryWrapper = QueryGenerator.initQueryWrapper(airagApp, req.getParameterMap());
Page<AiragApp> page = new Page<AiragApp>(pageNo, pageSize);
IPage<AiragApp> pageList = airagAppService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 新增或编辑
*
* @param airagApp
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody AiragApp airagApp) {
AssertUtils.assertNotEmpty("参数异常", airagApp);
AssertUtils.assertNotEmpty("请输入应用名称", airagApp.getName());
AssertUtils.assertNotEmpty("请选择应用类型", airagApp.getType());
airagApp.setStatus(AiAppConsts.STATUS_ENABLE);
airagAppService.saveOrUpdate(airagApp);
return Result.OK("保存完成!", airagApp.getId());
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
airagAppService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.airagAppService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@IgnoreAuth
@GetMapping(value = "/queryById")
public Result<AiragApp> queryById(@RequestParam(name = "id", required = true) String id) {
AiragApp airagApp = airagAppService.getById(id);
if (airagApp == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagApp);
}
/**
* 调试应用
*
* @param appDebugParams
* @return
* @author chenrui
* @date 2025/2/28 10:49
*/
@PostMapping(value = "/debug")
public SseEmitter debugApp(@RequestBody AppDebugParams appDebugParams) {
return airagChatService.debugApp(appDebugParams);
}
/**
* 根据需求生成提示词
*
* @param prompt
* @return
* @author chenrui
* @date 2025/3/12 15:30
*/
@GetMapping(value = "/prompt/generate")
public Result<?> generatePrompt(@RequestParam(name = "prompt", required = true) String prompt) {
return (Result<?>) airagAppService.generatePrompt(prompt,true);
}
/**
* 根据需求生成提示词
*
* @param prompt
* @return
* @author chenrui
* @date 2025/3/12 15:30
*/
@PostMapping(value = "/prompt/generate")
public SseEmitter generatePromptSse(@RequestParam(name = "prompt", required = true) String prompt) {
return (SseEmitter) airagAppService.generatePrompt(prompt,false);
}
}

View File

@ -0,0 +1,144 @@
package org.jeecg.modules.airag.app.controller;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* airag应用-chat
*
* @Author: chenrui
* @Date: 2025-02-25 11:40
*/
@Slf4j
@RestController
@RequestMapping("/airag/chat")
public class AiragChatController {
@Autowired
IAiragChatService chatService;
/**
* 发送消息
*
* @return 返回一个Result对象表示发送消息的结果
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@PostMapping(value = "/send")
public SseEmitter send(@RequestBody ChatSendParams chatSendParams) {
return chatService.send(chatSendParams);
}
/**
* 发送消息 <br/>
* 兼容旧版浏览器
* @param content
* @param conversationId
* @param topicId
* @param appId
* @return
* @author chenrui
* @date 2025/2/25 18:13
*/
@GetMapping(value = "/send")
public SseEmitter sendByGet(@RequestParam("content") String content,
@RequestParam(value = "conversationId", required = false) String conversationId,
@RequestParam(value = "topicId", required = false) String topicId,
@RequestParam(value = "appId", required = false) String appId) {
ChatSendParams chatSendParams = new ChatSendParams(content, conversationId, topicId, appId);
return chatService.send(chatSendParams);
}
/**
* 获取所有对话
*
* @return 返回一个Result对象包含所有对话的信息
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/conversations")
public Result<?> getConversations(@RequestParam(value = "appId", required = false) String appId) {
return chatService.getConversations(appId);
}
/**
* 删除会话
*
* @param id
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
@IgnoreAuth
@DeleteMapping(value = "/conversation/{id}")
public Result<?> deleteConversation(@PathVariable("id") String id) {
return chatService.deleteConversation(id);
}
/**
* 更新会话标题
*
* @param updateTitleParams
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
@IgnoreAuth
@PutMapping(value = "/conversation/update/title")
public Result<?> updateConversationTitle(@RequestBody ChatConversation updateTitleParams) {
return chatService.updateConversationTitle(updateTitleParams);
}
/**
* 获取消息
*
* @return 返回一个Result对象包含消息的信息
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/messages")
public Result<?> getMessages(@RequestParam(value = "conversationId", required = true) String conversationId) {
return chatService.getMessages(conversationId);
}
/**
* 清空消息
*
* @return
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/messages/clear/{conversationId}")
public Result<?> clearMessage(@PathVariable(value = "conversationId") String conversationId) {
return chatService.clearMessage(conversationId);
}
/**
* 根据请求ID停止某个请求的处理
*
* @param requestId 请求的唯一标识符,用于识别和停止特定的请求
* @return 返回一个Result对象表示停止请求的结果
* @author chenrui
* @date 2025/2/25 11:42
*/
@IgnoreAuth
@GetMapping(value = "/stop/{requestId}")
public Result<?> stop(@PathVariable(name = "requestId", required = true) String requestId) {
return chatService.stop(requestId);
}
}

View File

@ -0,0 +1,190 @@
package org.jeecg.modules.airag.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecg.common.util.oConvertUtils;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@Data
@TableName("airag_app")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="AI应用")
public class AiragApp implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private java.util.Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private java.util.Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 应用名称
*/
@Excel(name = "应用名称", width = 15)
@Schema(description = "应用名称")
private String name;
/**
* 应用描述
*/
@Excel(name = "应用描述", width = 15)
@Schema(description = "应用描述")
private String descr;
/**
* 应用图标
*/
@Excel(name = "应用图标", width = 15)
@Schema(description = "应用图标")
private String icon;
/**
* 应用类型
*/
@Excel(name = "应用类型", width = 15, dicCode = "ai_app_type")
@Dict(dicCode = "ai_app_type")
@Schema(description = "应用类型")
private String type;
/**
* 开场白
*/
@Excel(name = "开场白", width = 15)
@Schema(description = "开场白")
private String prologue;
/**
* 预设问题
*/
@Excel(name = "预设问题", width = 15)
@Schema(description = "预设问题")
private String presetQuestion;
/**
* 提示词
*/
@Excel(name = "提示词", width = 15)
@Schema(description = "提示词")
private String prompt;
/**
* 模型配置
*/
@Excel(name = "模型配置", width = 15, dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Schema(description = "模型配置")
private String modelId;
/**
* 历史消息数
*/
@Excel(name = "历史消息数", width = 15)
@Schema(description = "历史消息数")
private Integer msgNum;
/**
* 知识库
*/
@Excel(name = "知识库", width = 15, dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Schema(description = "知识库")
private String knowledgeIds;
/**
* 流程
*/
@Excel(name = "流程", width = 15, dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Schema(description = "流程")
private String flowId;
/**
* 快捷指令
*/
@Excel(name = "快捷指令", width = 15)
@Schema(description = "快捷指令")
private String quickCommand;
/**
* 状态
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private String status;
/**
* 元数据
*/
@Excel(name = "元数据", width = 15)
@Schema(description = "元数据")
private String metadata;
/**
* 知识库ids
*/
@TableField(exist = false)
private List<String> knowIds;
/**
* 获取知识库id
*
* @return
* @author chenrui
* @date 2025/2/28 11:45
*/
public List<String> getKnowIds() {
if (oConvertUtils.isNotEmpty(knowledgeIds)) {
String[] knowIds = knowledgeIds.split(",");
return Arrays.asList(knowIds);
} else {
return new ArrayList<>(0);
}
}
}

View File

@ -0,0 +1,14 @@
package org.jeecg.modules.airag.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
public interface AiragAppMapper extends BaseMapper<AiragApp> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.app.mapper.AiragAppMapper">
</mapper>

View File

@ -0,0 +1,24 @@
package org.jeecg.modules.airag.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
public interface IAiragAppService extends IService<AiragApp> {
/**
* 生成提示词
* @param prompt
* @return blocking 是否阻塞
* @return
* @author chenrui
* @date 2025/3/12 14:45
*/
Object generatePrompt(String prompt,boolean blocking);
}

View File

@ -0,0 +1,95 @@
package org.jeecg.modules.airag.app.service;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* ai聊天
*
* @author chenrui
* @date 2025/2/25 13:36
*/
public interface IAiragChatService {
/**
* 发送消息
*
* @param chatSendParams
* @return
* @author chenrui
* @date 2025/2/25 13:39
*/
SseEmitter send(ChatSendParams chatSendParams);
/**
* 调试应用
*
* @param appDebugParams
* @return
* @author chenrui
* @date 2025/2/28 10:49
*/
SseEmitter debugApp(AppDebugParams appDebugParams);
/**
* 停止响应
*
* @param requestId
* @return
* @author chenrui
* @date 2025/2/25 17:17
*/
Result<?> stop(String requestId);
/**
* 获取所有对话
*
* @param appId
* @return
* @author chenrui
* @date 2025/2/26 14:48
*/
Result<?> getConversations(String appId);
/**
* 获取对话聊天记录
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/26 15:16
*/
Result<?> getMessages(String conversationId);
/**
* 删除会话
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/3/3 16:55
*/
Result<?> deleteConversation(String conversationId);
/**
* 更新会话标题
* @param updateTitleParams
* @return
* @author chenrui
* @date 2025/3/3 17:02
*/
Result<?> updateConversationTitle(ChatConversation updateTitleParams);
/**
* 清空消息
* @param conversationId
* @return
* @author chenrui
* @date 2025/3/3 19:49
*/
Result<?> clearMessage(String conversationId);
}

View File

@ -0,0 +1,157 @@
package org.jeecg.modules.airag.app.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.modules.airag.app.consts.Prompts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.common.utils.AiragLocalCache;
import org.jeecg.modules.airag.common.vo.event.EventData;
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @Description: AI应用
* @Author: jeecg-boot
* @Date: 2025-02-26
* @Version: V1.0
*/
@Slf4j
@Service
public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> implements IAiragAppService {
@Autowired
IAIChatHandler aiChatHandler;
@Override
public Object generatePrompt(String prompt, boolean blocking) {
AssertUtils.assertNotEmpty("请输入提示词", prompt);
List<ChatMessage> messages = Arrays.asList(new SystemMessage(Prompts.GENERATE_LLM_PROMPT), new UserMessage(prompt));
AIChatParams params = new AIChatParams();
params.setTemperature(0.8);
params.setTopP(0.9);
params.setPresencePenalty(0.1);
params.setFrequencyPenalty(0.1);
if(blocking){
String promptValue = aiChatHandler.completionsByDefaultModel(messages, params);
if (promptValue == null || promptValue.isEmpty()) {
return Result.error("生成失败");
}
return Result.OK("success", promptValue);
}else{
SseEmitter emitter = new SseEmitter(-0L);
// 异步运行(流式)
TokenStream tokenStream = aiChatHandler.chatByDefaultModel(messages, params);
/**
* 是否正在思考
*/
AtomicBoolean isThinking = new AtomicBoolean(false);
String requestId = UUIDGenerator.generate();
// ai聊天响应逻辑
tokenStream.onNext((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
resMessage = "> ";
}
if ("</think>".equals(resMessage)) {
isThinking.set(false);
resMessage = "\n\n";
}
if (isThinking.get()) {
if (null != resMessage && resMessage.contains("\n")) {
resMessage = "\n> ";
}
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE);
EventMessageData messageEventData = EventMessageData.builder()
.message(resMessage)
.build();
eventData.setData(messageEventData);
try {
String eventStr = JSONObject.toJSONString(eventData);
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
emitter.send(SseEmitter.event().data(eventStr));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.onComplete((responseMessage) -> {
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
// 正常结束
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END);
try {
log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
throw new RuntimeException(e);
}
closeSSE(emitter, eventData);
} else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
// 需要执行工具
// TODO author: chenrui for: date:2025/3/7
} else {
// 异常结束
log.error("调用模型异常:" + respText);
if (respText.contains("insufficient Balance")) {
respText = "大预言模型账号余额不足!";
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
closeSSE(emitter, eventData);
}
})
.onError((Throwable error) -> {
// sse
String errMsg = "调用大模型接口失败:" + error.getMessage();
log.error(errMsg, error);
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
closeSSE(emitter, eventData);
})
.start();
return emitter;
}
}
private static void closeSSE(SseEmitter emitter, EventData eventData) {
try {
// 发送完成事件
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
log.error("终止会话时发生错误", e);
} finally {
// 从缓存中移除emitter
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, eventData.getRequestId());
// 关闭emitter
emitter.complete();
}
}
}

View File

@ -0,0 +1,902 @@
package org.jeecg.modules.airag.app.service.impl;
import com.alibaba.fastjson.JSONObject;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.data.message.*;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootBizTipException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.*;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.common.utils.AiragLocalCache;
import org.jeecg.modules.airag.common.vo.MessageHistory;
import org.jeecg.modules.airag.common.vo.event.EventData;
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
import org.jeecg.modules.airag.flow.consts.FlowConsts;
import org.jeecg.modules.airag.flow.service.IAiragFlowService;
import org.jeecg.modules.airag.flow.vo.api.FlowRunParams;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* AI助手聊天Service
*
* @author chenrui
* @date 2024/1/26 20:07
*/
@Service
@Slf4j
public class AiragChatServiceImpl implements IAiragChatService {
@Autowired
IAIChatHandler aiChatHandler;
@Autowired
RedisTemplate redisTemplate;
@Autowired
IAiragAppService airagAppService;
@Autowired
IAiragFlowService airagFlowService;
@Autowired
private ISysBaseAPI sysBaseApi;
@Autowired
private RedisUtil redisUtil;
@Override
public SseEmitter send(ChatSendParams chatSendParams) {
AssertUtils.assertNotEmpty("参数异常", chatSendParams);
String userMessage = chatSendParams.getContent();
AssertUtils.assertNotEmpty("至少发送一条消息", userMessage);
// 获取会话信息
String conversationId = chatSendParams.getConversationId();
String topicId = oConvertUtils.getString(chatSendParams.getTopicId(), UUIDGenerator.generate());
// 获取app信息
AiragApp app = null;
if (oConvertUtils.isNotEmpty(chatSendParams.getAppId())) {
app = airagAppService.getById(chatSendParams.getAppId());
}
ChatConversation chatConversation = getOrCreateChatConversation(app, conversationId);
// 更新标题
if (oConvertUtils.isEmpty(chatConversation.getTitle())) {
chatConversation.setTitle(userMessage.length() > 5 ? userMessage.substring(0, 5) : userMessage);
}
// 发送消息
return doChat(chatConversation, topicId, chatSendParams);
}
@Override
public SseEmitter debugApp(AppDebugParams appDebugParams) {
AssertUtils.assertNotEmpty("参数异常", appDebugParams);
String userMessage = appDebugParams.getContent();
AssertUtils.assertNotEmpty("至少发送一条消息", userMessage);
AssertUtils.assertNotEmpty("应用信息不能为空", appDebugParams.getApp());
// 获取会话信息
String topicId = oConvertUtils.getString(appDebugParams.getTopicId(), UUIDGenerator.generate());
AiragApp app = appDebugParams.getApp();
app.setId("__DEBUG_APP");
ChatConversation chatConversation = getOrCreateChatConversation(app, topicId);
// 发送消息
SseEmitter emitter = doChat(chatConversation, topicId, appDebugParams);
//保存会话
saveChatConversation(chatConversation, true, null);
return emitter;
}
@Override
public Result<?> stop(String requestId) {
AssertUtils.assertNotEmpty("requestId不能为空", requestId);
// 从缓存中获取对应的SseEmitter
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (emitter != null) {
closeSSE(emitter, new EventData(requestId, null, EventData.EVENT_MESSAGE_END));
return Result.ok("会话已成功终止");
} else {
return Result.error("未找到对应的会话");
}
}
/**
* 关闭sse
*
* @param emitter
* @param eventData
* @throws IOException
* @author chenrui
* @date 2025/2/27 15:56
*/
private static void closeSSE(SseEmitter emitter, EventData eventData) {
AssertUtils.assertNotEmpty("请求id不能为空", eventData);
if (null == emitter) {
log.warn("会话已关闭");
return;
}
try {
// 发送完成事件
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
log.error("终止会话时发生错误", e);
} finally {
// 从缓存中移除emitter
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, eventData.getRequestId());
// 关闭emitter
emitter.complete();
}
}
@Override
public Result<?> getConversations(String appId) {
if (oConvertUtils.isEmpty(appId)) {
appId = AiAppConsts.DEFAULT_APP_ID;
}
String key = getConversationDirCacheKey(null);
key = key + ":*";
List<String> keys = redisUtil.scan(key);
// 如果键集合为空,返回空列表
if (keys.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 遍历键集合,获取对应的 ChatConversation 对象
List<ChatConversation> conversations = new ArrayList<>();
for (Object k : keys) {
ChatConversation conversation = (ChatConversation) redisTemplate.boundValueOps(k).get();
if (conversation != null) {
AiragApp app = conversation.getApp();
if (null == app) {
continue;
}
String conversationAppId = app.getId();
if (appId.equals(conversationAppId)) {
conversation.setApp(null);
conversation.setMessages(null);
conversations.add(conversation);
}
}
}
// 对会话列表按创建时间降序排序
conversations.sort((o1, o2) -> {
Date date1 = o1.getCreateTime();
Date date2 = o2.getCreateTime();
if (date1 == null && date2 == null) {
return 0;
}
if (date1 == null) {
return 1;
}
if (date2 == null) {
return -1;
}
return date2.compareTo(date1);
});
// 返回结果
return Result.ok(conversations);
}
@Override
public Result<?> getMessages(String conversationId) {
AssertUtils.assertNotEmpty("请先选择会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isEmpty(key)) {
return Result.ok(Collections.emptyList());
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (oConvertUtils.isObjectEmpty(chatConversation)) {
return Result.ok(Collections.emptyList());
}
return Result.ok(chatConversation.getMessages());
}
@Override
public Result<?> clearMessage(String conversationId) {
AssertUtils.assertNotEmpty("请先选择会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isEmpty(key)) {
return Result.ok(Collections.emptyList());
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (null != chatConversation && oConvertUtils.isObjectNotEmpty(chatConversation.getMessages())) {
chatConversation.getMessages().clear();
saveChatConversation(chatConversation);
}
return Result.ok();
}
@Override
public Result<?> deleteConversation(String conversationId) {
AssertUtils.assertNotEmpty("请选择要删除的会话", conversationId);
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isNotEmpty(key)) {
Boolean delete = redisTemplate.delete(key);
if (delete) {
return Result.ok();
} else {
return Result.error("删除会话失败");
}
}
log.warn("[ai-chat]删除会话:未找到会话:{}", conversationId);
return Result.ok();
}
@Override
public Result<?> updateConversationTitle(ChatConversation updateTitleParams) {
AssertUtils.assertNotEmpty("请先选择会话", updateTitleParams);
AssertUtils.assertNotEmpty("请先选择会话", updateTitleParams.getId());
AssertUtils.assertNotEmpty("请输入会话标题", updateTitleParams.getTitle());
String key = getConversationCacheKey(updateTitleParams.getId(), null);
if (oConvertUtils.isEmpty(key)) {
log.warn("[ai-chat]删除会话:未找到会话:{}", updateTitleParams.getId());
return Result.ok();
}
ChatConversation chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
chatConversation.setTitle(updateTitleParams.getTitle());
saveChatConversation(chatConversation);
return Result.ok();
}
/**
* 获取会话缓存key
*
* @param conversationId
* @param httpRequest
* @return
* @author chenrui
* @date 2025/2/25 19:27
*/
private String getConversationCacheKey(String conversationId,HttpServletRequest httpRequest) {
if (oConvertUtils.isEmpty(conversationId)) {
return null;
}
String key = getConversationDirCacheKey(httpRequest);
key = key + ":" + conversationId;
return key;
}
/**
* 获取当前用户会话的缓存目录
*
* @param httpRequest
* @return
* @author chenrui
* @date 2025/2/26 15:09
*/
private String getConversationDirCacheKey(HttpServletRequest httpRequest) {
String username = getUsername(httpRequest);
// 如果用户不存在,获取当前请求的sessionid
if (oConvertUtils.isEmpty(username)) {
try {
if (null == httpRequest) {
httpRequest = SpringContextUtils.getHttpServletRequest();
}
username = httpRequest.getSession().getId();
} catch (Exception e) {
log.error("获取当前请求的sessionid失败", e);
}
}
AssertUtils.assertNotEmpty("请先登录", username);
return "airag:chat:" + username;
}
/**
* 获取会话
*
* @param app
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/25 19:19
*/
@NotNull
private ChatConversation getOrCreateChatConversation(AiragApp app, String conversationId) {
if (oConvertUtils.isObjectEmpty(app)) {
app = new AiragApp();
app.setId(AiAppConsts.DEFAULT_APP_ID);
}
ChatConversation chatConversation = null;
String key = getConversationCacheKey(conversationId, null);
if (oConvertUtils.isNotEmpty(key)) {
chatConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
}
if (null == chatConversation) {
chatConversation = createConversation(conversationId);
}
chatConversation.setApp(app);
return chatConversation;
}
/**
* 创建新的会话
*
* @param conversationId
* @return
* @author chenrui
* @date 2025/2/26 15:53
*/
@NotNull
private ChatConversation createConversation(String conversationId) {
// 新会话
conversationId = oConvertUtils.getString(conversationId, UUIDGenerator.generate());
ChatConversation chatConversation = new ChatConversation();
chatConversation.setId(conversationId);
chatConversation.setCreateTime(new Date());
return chatConversation;
}
/**
* 保存会话
*
* @param chatConversation
* @author chenrui
* @date 2025/2/25 19:27
*/
private void saveChatConversation(ChatConversation chatConversation) {
saveChatConversation(chatConversation, false, null);
}
/**
* 保存会话
*
* @param chatConversation
* @param temp 是否临时会话
* @author chenrui
* @date 2025/2/25 19:27
*/
private void saveChatConversation(ChatConversation chatConversation, boolean temp,HttpServletRequest httpRequest) {
if (null == chatConversation) {
return;
}
String key = getConversationCacheKey(chatConversation.getId(), httpRequest);
if (oConvertUtils.isEmpty(key)) {
return;
}
BoundValueOperations chatRedisCacheOp = redisTemplate.boundValueOps(key);
chatRedisCacheOp.set(chatConversation);
if (temp) {
chatRedisCacheOp.expire(3, TimeUnit.HOURS);
}
}
/**
* 构造消息
*
* @param conversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/25 15:26
*/
private List<ChatMessage> collateMessage(ChatConversation conversation, String topicId) {
List<MessageHistory> messagesHistory = conversation.getMessages();
if (oConvertUtils.isObjectEmpty(messagesHistory)) {
return new LinkedList<>();
}
LinkedList<ChatMessage> chatMessages = new LinkedList<>();
for (int i = messagesHistory.size() - 1; i >= 0; i--) {
MessageHistory history = messagesHistory.get(i);
if (topicId.equals(history.getTopicId())) {
ChatMessage chatMessage = null;
switch (history.getRole()) {
case AiragConsts.MESSAGE_ROLE_USER:
List<Content> contents = new ArrayList<>();
List<MessageHistory.ImageHistory> images = history.getImages();
if (oConvertUtils.isObjectNotEmpty(images)
&& !images.isEmpty()) {
contents.addAll(images.stream().map(imageHistory -> {
if (oConvertUtils.isNotEmpty(imageHistory.getUrl())) {
return ImageContent.from(imageHistory.getUrl());
} else {
return ImageContent.from(imageHistory.getBase64Data(), imageHistory.getMimeType());
}
}).collect(Collectors.toList()));
}
contents.add(TextContent.from(history.getContent()));
chatMessage = UserMessage.from(contents);
break;
case AiragConsts.MESSAGE_ROLE_AI:
chatMessage = new AiMessage(history.getContent());
break;
}
if (null == chatMessage) {
continue;
}
chatMessages.addFirst(chatMessage);
}
}
return chatMessages;
}
/**
* 追加消息
*
* @param messages
* @param message
* @param chatConversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/25 19:05
*/
private void appendMessage(List<ChatMessage> messages, ChatMessage message, ChatConversation
chatConversation, String topicId) {
if (message.type().equals(ChatMessageType.SYSTEM)) {
// 系统消息,放到消息列表最前面,并且不记录历史
messages.add(0, message);
return;
} else {
messages.add(message);
}
List<MessageHistory> histories = chatConversation.getMessages();
if (oConvertUtils.isObjectEmpty(histories)) {
histories = new ArrayList<>();
}
// 消息记录
MessageHistory historyMessage = MessageHistory.builder()
.conversationId(chatConversation.getId())
.topicId(topicId)
.datetime(DateUtils.now())
.build();
if (message.type().equals(ChatMessageType.USER)) {
historyMessage.setRole(AiragConsts.MESSAGE_ROLE_USER);
StringBuilder textContent = new StringBuilder();
List<MessageHistory.ImageHistory> images = new ArrayList<>();
List<Content> contents = ((UserMessage) message).contents();
contents.forEach(content -> {
if (content.type().equals(ContentType.IMAGE)) {
ImageContent imageContent = (ImageContent) content;
Image image = imageContent.image();
MessageHistory.ImageHistory imageMessage = MessageHistory.ImageHistory.from(image.url(), image.base64Data(), image.mimeType());
images.add(imageMessage);
} else if (content.type().equals(ContentType.TEXT)) {
textContent.append(((TextContent) content).text()).append("\n");
}
});
historyMessage.setContent(textContent.toString());
historyMessage.setImages(images);
} else if (message.type().equals(ChatMessageType.AI)) {
historyMessage.setRole(AiragConsts.MESSAGE_ROLE_AI);
historyMessage.setContent(((AiMessage) message).text());
}
histories.add(historyMessage);
chatConversation.setMessages(histories);
}
/**
* 发送聊天消息
*
* @param chatConversation
* @param topicId
* @param sendParams
* @return
* @author chenrui
* @date 2025/2/28 11:04
*/
@NotNull
private SseEmitter doChat(ChatConversation chatConversation, String topicId, ChatSendParams sendParams) {
// 从历史消息中组装本次的消息列表
List<ChatMessage> messages = collateMessage(chatConversation, topicId);
AiragApp aiApp = chatConversation.getApp();
// 每次会话都生成一个新的,用来缓存emitter
String requestId = UUIDGenerator.generate();
SseEmitter emitter = new SseEmitter(-0L);
// 缓存emitter
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE, requestId, emitter);
try {
// 组装用户消息
UserMessage userMessage = aiChatHandler.buildUserMessage(sendParams.getContent(), sendParams.getImages());
// 追加消息
appendMessage(messages, userMessage, chatConversation, topicId);
/* 这里应该是有几种情况:
* 1. 非ai应用:获取默认模型->开始聊天
* 2. AI应用-聊天助手(ChatAssistant):从应用信息组装模型和提示词->开始聊天
* 3. AI应用-聊天流程(ChatFlow):从应用信息获取模型,流程,组装入参->调用工作流
*/
if (null != aiApp && !AiAppConsts.DEFAULT_APP_ID.equals(aiApp.getId())) {
// ai应用:查询应用信息(ChatAssistant,chatflow),模型信息,组装模型-提示词,知识库等
if (AiAppConsts.APP_TYPE_CHAT_FLOW.equals(aiApp.getType())) {
// ai应用:聊天流程(ChatFlow)
sendWithFlow(requestId, aiApp.getFlowId(), chatConversation, topicId, messages, sendParams);
} else {
// AI应用-聊天助手(ChatAssistant):从应用信息组装模型和提示词
sendWithAppChat(requestId, messages, chatConversation, topicId);
}
} else {
// 发消息
sendWithDefault(requestId, chatConversation, topicId, null, messages, null);
}
} catch (Throwable e) {
log.error(e.getMessage(), e);
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(e.getMessage()).build());
closeSSE(emitter, eventData);
}
return emitter;
}
/**
* 运行流程
*
* @param requestId
* @param flowId
* @param chatConversation
* @param topicId
* @param messages
* @param sendParams
* @author chenrui
* @date 2025/2/27 14:55
*/
private void sendWithFlow(String requestId, String flowId, ChatConversation chatConversation, String topicId, List<ChatMessage> messages, ChatSendParams sendParams) {
FlowRunParams flowRunParams = new FlowRunParams();
flowRunParams.setRequestId(requestId);
flowRunParams.setFlowId(flowId);
flowRunParams.setConversationId(chatConversation.getId());
flowRunParams.setTopicId(topicId);
// 支持流式
flowRunParams.setResponseMode(FlowConsts.FLOW_RESPONSE_MODE_STREAMING);
Map<String, Object> flowInputParams = new HashMap<>();
List<MessageHistory> histories = new ArrayList<>();
if (oConvertUtils.isObjectNotEmpty(chatConversation.getMessages())) {
// 创建历史消息的副本(不直接操作原来的list)
histories.addAll(chatConversation.getMessages());
// 移除最后一条历史消息(最后一条是当前发出去的这一条消息)
histories.remove(histories.size() - 1);
}
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_HISTORY, histories);
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_QUESTION, sendParams.getContent());
flowInputParams.put(FlowConsts.FLOW_INPUT_PARAM_IMAGES, sendParams.getImages());
flowRunParams.setInputParams(flowInputParams);
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
// TODO 未改好111
// flowRunParams.setHttpRequest(httpRequest);
// 流程结束后,记录ai返回并保存会话
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
flowRunParams.setEventCallback(eventData -> {
if (EventData.EVENT_FLOW_FINISHED.equals(eventData.getEvent())) {
EventFlowData data = (EventFlowData) eventData.getData();
Object outputs = data.getOutputs();
if (oConvertUtils.isObjectNotEmpty(outputs)) {
AiMessage aiMessage;
if (outputs instanceof String) {
// 兼容推理模型
String messageText = String.valueOf(outputs);
messageText = messageText.replaceAll("<think>([\\s\\S]*?)</think>", "> $1");
aiMessage = new AiMessage(messageText);
} else {
aiMessage = new AiMessage(JSONObject.toJSONString(outputs));
}
EventData msgEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
EventMessageData messageEventData = EventMessageData.builder()
.message(aiMessage.text())
.build();
msgEventData.setData(messageEventData);
try {
String eventStr = JSONObject.toJSONString(msgEventData);
log.debug("[AI应用]接收FLOW返回消息:{}", eventStr);
emitter.send(SseEmitter.event().data(eventStr));
} catch (IOException e) {
throw new RuntimeException(e);
}
appendMessage(messages, aiMessage, chatConversation, topicId);
// 保存会话
saveChatConversation(chatConversation, false, httpRequest);
}
}
});
airagFlowService.runFlow(flowRunParams);
}
/**
* 发送app聊天
*
* @param requestId
* @param messages
* @param chatConversation
* @param topicId
* @return
* @author chenrui
* @date 2025/2/28 10:41
*/
private void sendWithAppChat(String requestId, List<ChatMessage> messages, ChatConversation chatConversation, String topicId) {
AiragApp aiApp = chatConversation.getApp();
String modelId = aiApp.getModelId();
AssertUtils.assertNotEmpty("请先选择模型", modelId);
// AI应用提示词
String prompt = aiApp.getPrompt();
if (oConvertUtils.isNotEmpty(prompt)) {
appendMessage(messages, new SystemMessage(prompt), chatConversation, topicId);
}
AIChatParams aiChatParams = new AIChatParams();
// AI应用自定义的模型参数
String metadataStr = aiApp.getMetadata();
if (oConvertUtils.isNotEmpty(metadataStr)) {
JSONObject metadata = JSONObject.parseObject(metadataStr);
if(oConvertUtils.isNotEmpty(metadata)){
if (metadata.containsKey("temperature")) {
aiChatParams.setTemperature(metadata.getDouble("temperature"));
}
if (metadata.containsKey("topP")) {
aiChatParams.setTopP(metadata.getDouble("temperature"));
}
if (metadata.containsKey("presencePenalty")) {
aiChatParams.setPresencePenalty(metadata.getDouble("temperature"));
}
if (metadata.containsKey("frequencyPenalty")) {
aiChatParams.setFrequencyPenalty(metadata.getDouble("temperature"));
}
if (metadata.containsKey("maxTokens")) {
aiChatParams.setMaxTokens(metadata.getInteger("temperature"));
}
}
}
// 发消息
sendWithDefault(requestId, chatConversation, topicId, modelId, messages, aiChatParams);
}
/**
* 处理聊天
* 向大模型发送消息并接受响应
*
* @param chatConversation
* @param topicId
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/25 19:24
*/
private void sendWithDefault(String requestId, ChatConversation chatConversation, String topicId, String modelId,
List<ChatMessage> messages,AIChatParams aiChatParams) {
// 调用ai聊天
if(null == aiChatParams){
aiChatParams = new AIChatParams();
}
aiChatParams.setKnowIds(chatConversation.getApp().getKnowIds());
aiChatParams.setMaxMsgNumber(oConvertUtils.getInt(chatConversation.getApp().getMsgNum(), 5));
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
TokenStream chatStream;
try {
if (oConvertUtils.isNotEmpty(modelId)) {
chatStream = aiChatHandler.chat(modelId, messages, aiChatParams);
} else {
chatStream = aiChatHandler.chatByDefaultModel(messages, aiChatParams);
}
} catch (Exception e) {
log.error(e.getMessage(),e);
throw new JeecgBootBizTipException("调用大模型接口失败:" + e.getMessage());
}
/**
* 是否正在思考
*/
AtomicBoolean isThinking = new AtomicBoolean(false);
// ai聊天响应逻辑
chatStream.onNext((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
resMessage = "> ";
}
if ("</think>".equals(resMessage)) {
isThinking.set(false);
resMessage = "\n\n";
}
if (isThinking.get()) {
if (null != resMessage && resMessage.contains("\n")) {
resMessage = "\n> ";
}
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
EventMessageData messageEventData = EventMessageData.builder()
.message(resMessage)
.build();
eventData.setData(messageEventData);
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭");
return;
}
try {
String eventStr = JSONObject.toJSONString(eventData);
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
emitter.send(SseEmitter.event().data(eventStr));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.onComplete((responseMessage) -> {
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭");
return;
}
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
// 正常结束
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
try {
log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
emitter.send(SseEmitter.event().data(eventData));
} catch (IOException e) {
throw new RuntimeException(e);
}
appendMessage(messages, aiMessage, chatConversation, topicId);
// 保存会话
saveChatConversation(chatConversation,false,httpRequest);
closeSSE(emitter, eventData);
} else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
// 需要执行工具
// TODO author: chenrui for: date:2025/3/7
} else {
// 异常结束
log.error("调用模型异常:" + respText);
if (respText.contains("insufficient Balance")) {
respText = "大预言模型账号余额不足!";
}
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
closeSSE(emitter, eventData);
}
})
.onError((Throwable error) -> {
// sse
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
if (null == emitter) {
log.warn("[AI应用]接收LLM返回会话已关闭");
return;
}
String errMsg = "调用大模型接口失败:" + error.getMessage();
log.error(errMsg, error);
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
closeSSE(emitter, eventData);
})
.start();
}
/**
* 发送聊天返回结果
*
* @author chenrui
* @date 2025/2/28 11:05
*/
private static class ChatResult {
public final SseEmitter emitter;
public final AiragModel chatModel;
public ChatResult(SseEmitter emitter, AiragModel chatModel) {
this.emitter = emitter;
this.chatModel = chatModel;
}
}
/**
* 总结会话标题
* 几个问题: <br/>
* 1. 如果在发消息时同步总结会话标题,会导致接口很慢甚至超时.
* 2. 但如果异步更新会话标题会导致消息记录丢失(不全)或者标题丢失,需要写很多逻辑去保证最终一致
* so 暂时先不用AI更新会话标题. 后期如果需要单独再增加一个接口,由前端调用或者在第一次消息接收完成后再异步更新
*
* @param chatConversation
* @param question
* @param modelId
* @return
* @author chenrui
* @date 2025/2/25 17:12
*/
protected void summaryConversationTitle(ChatConversation chatConversation, String question, String modelId) {
if (oConvertUtils.isEmpty(chatConversation.getId())) {
return;
}
String key = getConversationCacheKey(chatConversation.getId(), null);
if (oConvertUtils.isEmpty(key)) {
return;
}
CompletableFuture.runAsync(() -> {
List<ChatMessage> messages = new LinkedList<>();
String systemMsgStr = "根据用户的问题,总结会话标题.\n" +
"要求如下:\n" +
"1. 使用中文回答.\n" +
"2. 标题长度控制在5个汉字10个英文字符以内\n" +
"3. 直接回复会话标题,不要有其他任何无关描述\n" +
"4. 如果无法总结,回复不知道\n";
messages.add(new SystemMessage(systemMsgStr));
messages.add(new UserMessage(question));
String summaryTitle;
try {
summaryTitle = aiChatHandler.completions(modelId, messages, null);
log.info("总结会话完成{}", summaryTitle);
if (summaryTitle.equalsIgnoreCase("不知道")) {
summaryTitle = "";
}
} catch (Exception e) {
log.warn("AI总结会话失败" + e.getMessage(), e);
summaryTitle = "";
}
// 更新会话标题
ChatConversation cachedConversation = (ChatConversation) redisTemplate.boundValueOps(key).get();
if (null == cachedConversation) {
cachedConversation = chatConversation;
}
if (oConvertUtils.isEmpty(chatConversation.getTitle())) {
// 再次判断标题是否为空,只有标题为空才更新
if (oConvertUtils.isNotEmpty(summaryTitle)) {
cachedConversation.setTitle(summaryTitle);
} else {
cachedConversation.setTitle(question.length() > 5 ? question.substring(0, 5) : question);
}
//保存会话
saveChatConversation(cachedConversation);
}
});
}
/**
* 获取用户名
* @param httpRequest
* @return
* @author chenrui
* @date 2025/3/27 15:05
*/
private String getUsername(HttpServletRequest httpRequest) {
try {
TokenUtils.getTokenByRequest();
String token;
if(null != httpRequest){
token = TokenUtils.getTokenByRequest(httpRequest);
}else{
token = TokenUtils.getTokenByRequest();
}
if (TokenUtils.verifyToken(token, sysBaseApi, redisUtil)) {
return JwtUtil.getUsername(token);
}
} catch (Exception e) {
return null;
}
return null;
}
}

View File

@ -0,0 +1,20 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.jeecg.modules.airag.app.entity.AiragApp;
/**
* @Description: 应用调试入参
* @Author: chenrui
* @Date: 2025/2/25 11:47
*/
@Data
public class AppDebugParams extends ChatSendParams {
/**
* 应用信息
*/
AiragApp app;
}

View File

@ -0,0 +1,42 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.common.vo.MessageHistory;
import java.util.Date;
import java.util.List;
/**
* @Description: 聊天会话
* @Author: chenrui
* @Date: 2025/2/25 14:56
*/
@Data
public class ChatConversation {
/**
* 会话id
*/
private String id;
/**
* 会话标题
*/
private String title;
/**
* 消息记录
*/
private List<MessageHistory> messages;
/**
* app
*/
private AiragApp app;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,49 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @Description: 发送消息的入参
* @Author: chenrui
* @Date: 2025/2/25 11:47
*/
@NoArgsConstructor
@Data
public class ChatSendParams {
public ChatSendParams(String content, String conversationId, String topicId, String appId) {
this.content = content;
this.conversationId = conversationId;
this.topicId = topicId;
this.appId = appId;
}
/**
* 用户输入的聊天内容
*/
private String content;
/**
* 对话会话ID
*/
private String conversationId;
/**
* 对话主题ID用于关联历史记录
*/
private String topicId;
/**
* 应用id
*/
private String appId;
/**
* 图片列表
*/
private List<String> images;
}

View File

@ -0,0 +1,47 @@
package org.jeecg.modules.airag.llm.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 向量存储库配置
*
* @Author: chenrui
* @Date: 2025/2/18 14:24
*/
@NoArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = EmbedStoreConfigBean.PREFIX)
public class EmbedStoreConfigBean {
public static final String PREFIX = "jeecg.airag.embed-store";
/**
* host
*/
private String host = "127.0.0.1";
/**
* 端口
*/
private int port = 5432;
/**
* 数据库
*/
private String database = "postgres";
/**
* 用户名
*/
private String user = "postgres";
/**
* 密码
*/
private String password = "postgres";
/**
* 存储向量的表
*/
private String table = "embeddings";
}

View File

@ -0,0 +1,31 @@
package org.jeecg.modules.airag.llm.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 知识库配置
*
* @Author: chenrui
* @Date: 2025-04-01 14:19
*/
@NoArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = KnowConfigBean.PREFIX)
public class KnowConfigBean {
public static final String PREFIX = "jeecg.airag.know";
/**
* 开启MinerU解析
*/
private boolean enableMinerU = false;
/**
* conda的环境(默认不使用conda)
*/
private String condaEnv = null;
}

View File

@ -0,0 +1,75 @@
package org.jeecg.modules.airag.llm.consts;
import java.util.regex.Pattern;
/**
* @Description: airag模型常量类
* @Author: chenrui
* @Date: 2025/2/12 17:35
*/
public class LLMConsts {
/**
* 正则表达式:是否是网页
*/
public static final Pattern WEB_PATTERN = Pattern.compile("^(http|https)://.*");
/**
* 状态:启用
*/
public static final String STATUS_ENABLE = "enable";
/**
* 状态:禁用
*/
public static final String STATUS_DISABLE = "disable";
/**
* 模型类型:向量
*/
public static final String MODEL_TYPE_EMBED = "EMBED";
/**
* 模型类型:聊天
*/
public static final String MODEL_TYPE_LLM = "LLM";
/**
* 知识库:文档状态:草稿
*/
public static final String KNOWLEDGE_DOC_STATUS_DRAFT = "draft";
/**
* 知识库:文档状态:构建中
*/
public static final String KNOWLEDGE_DOC_STATUS_BUILDING = "building";
/**
* 知识库:文档状态:构建完成
*/
public static final String KNOWLEDGE_DOC_STATUS_COMPLETE = "complete";
/**
* 知识库:文档类型:文本
*/
public static final String KNOWLEDGE_DOC_TYPE_TEXT = "text";
/**
* 知识库:文档类型:文件
*/
public static final String KNOWLEDGE_DOC_TYPE_FILE = "file";
/**
* 知识库:文档类型:网页
*/
public static final String KNOWLEDGE_DOC_TYPE_WEB = "web";
/**
* 知识库:文档元数据:文件路径
*/
public static final String KNOWLEDGE_DOC_METADATA_FILEPATH = "filePath";
/**
* 知识库:文档元数据:资源路径
*/
public static final String KNOWLEDGE_DOC_METADATA_SOURCES_PATH = "sourcesPath";
}

View File

@ -0,0 +1,320 @@
package org.jeecg.modules.airag.llm.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.modules.airag.common.vo.knowledge.KnowledgeSearchResult;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeDocService;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@RestController
@RequestMapping("/airag/knowledge")
@Slf4j
public class AiragKnowledgeController {
@Autowired
private IAiragKnowledgeService airagKnowledgeService;
@Autowired
private IAiragKnowledgeDocService airagKnowledgeDocService;
@Autowired
EmbeddingHandler embeddingHandler;
/**
* 分页列表查询知识库
*
* @param airagKnowledge
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragKnowledge>> queryPageList(AiragKnowledge airagKnowledge,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<AiragKnowledge> queryWrapper = QueryGenerator.initQueryWrapper(airagKnowledge, req.getParameterMap());
Page<AiragKnowledge> page = new Page<AiragKnowledge>(pageNo, pageSize);
IPage<AiragKnowledge> pageList = airagKnowledgeService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加知识库
*
* @param airagKnowledge 知识库
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@PostMapping(value = "/add")
public Result<String> add(@RequestBody AiragKnowledge airagKnowledge) {
airagKnowledge.setStatus(LLMConsts.STATUS_ENABLE);
airagKnowledgeService.save(airagKnowledge);
return Result.OK("添加成功!");
}
/**
* 编辑知识库
*
* @param airagKnowledge 知识库
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody AiragKnowledge airagKnowledge) {
AiragKnowledge airagKnowledgeEntity = airagKnowledgeService.getById(airagKnowledge.getId());
if (airagKnowledgeEntity == null) {
return Result.error("未找到对应数据");
}
String oldEmbedId = airagKnowledgeEntity.getEmbedId();
airagKnowledgeService.updateById(airagKnowledge);
if (!oldEmbedId.equalsIgnoreCase(airagKnowledge.getEmbedId())) {
// 更新了模型,重建文档
airagKnowledgeDocService.rebuildDocumentByKnowId(airagKnowledge.getId());
}
return Result.OK("编辑成功!");
}
/**
* 重建知识库
*
* @param knowIds
* @return
* @author chenrui
* @date 2025/3/12 17:05
*/
@PutMapping(value = "/rebuild")
public Result<?> rebuild(@RequestParam("knowIds") String knowIds) {
String[] knowIdArr = knowIds.split(",");
for (String knowId : knowIdArr) {
airagKnowledgeDocService.rebuildDocumentByKnowId(knowId);
}
return Result.OK("");
}
/**
* 通过id删除知识库
*
* @param id
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
airagKnowledgeDocService.removeByKnowIds(Collections.singletonList(id));
airagKnowledgeService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除知识库
*
* @param ids
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
List<String> idsList = Arrays.asList(ids.split(","));
airagKnowledgeDocService.removeByKnowIds(idsList);
airagKnowledgeService.removeByIds(idsList);
return Result.OK("批量删除成功!");
}
/**
* 通过id查询知识库
*
* @param id
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/queryById")
public Result<AiragKnowledge> queryById(@RequestParam(name = "id", required = true) String id) {
AiragKnowledge airagKnowledge = airagKnowledgeService.getById(id);
if (airagKnowledge == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagKnowledge);
}
/**
* 文档分页查询
*
* @param airagKnowledgeDoc
* @param pageNo
* @param pageSize
* @param req
* @return
* @author chenrui
* @date 2025/2/18 18:37
*/
@GetMapping(value = "/doc/list")
public Result<IPage<AiragKnowledgeDoc>> queryDocumentPageList(AiragKnowledgeDoc airagKnowledgeDoc,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
AssertUtils.assertNotEmpty("请先选择知识库", airagKnowledgeDoc.getKnowledgeId());
QueryWrapper<AiragKnowledgeDoc> queryWrapper = QueryGenerator.initQueryWrapper(airagKnowledgeDoc, req.getParameterMap());
Page<AiragKnowledgeDoc> page = new Page<>(pageNo, pageSize);
IPage<AiragKnowledgeDoc> pageList = airagKnowledgeDocService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 新增或编辑文档
*
* @param airagKnowledgeDoc 知识库文档
* @return
* @author chenrui
* @date 2025/2/18 15:47
*/
@PostMapping(value = "/doc/edit")
public Result<?> addDocument(@RequestBody AiragKnowledgeDoc airagKnowledgeDoc) {
return airagKnowledgeDocService.editDocument(airagKnowledgeDoc);
}
/**
* 从压缩包导入文档
* @return
* @author chenrui
* @date 2025/3/20 11:29
*/
@PostMapping(value = "/doc/import/zip")
public Result<?> importDocumentFromZip(@RequestParam(name = "knowId", required = true) String knowId,
@RequestParam(name = "file", required = true) MultipartFile file) {
return airagKnowledgeDocService.importDocumentFromZip(knowId,file);
}
/**
* 通过文档库查询导入任务列表
* @param knowId
* @return
* @author chenrui
* @date 2025/3/20 11:37
*/
@GetMapping(value = "/doc/import/task/list")
public Result<?> importDocumentTaskList(@RequestParam(name = "knowId", required = true) String knowId) {
return Result.OK(Collections.emptyList());
}
/**
* 重新向量化文档
*
* @param docIds 文档id集合
* @return
* @author chenrui
* @date 2025/2/18 15:47
*/
@PutMapping(value = "/doc/rebuild")
public Result<?> rebuildDocument(@RequestParam("docIds") String docIds) {
return airagKnowledgeDocService.rebuildDocument(docIds);
}
/**
* 批量删除文档
*
* @param ids
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/doc/deleteBatch")
public Result<String> deleteDocumentBatch(@RequestParam(name = "ids", required = true) String ids) {
List<String> idsList = Arrays.asList(ids.split(","));
airagKnowledgeDocService.removeDocByIds(idsList);
return Result.OK("批量删除成功!");
}
/**
* 命中测试
*
* @param knowId 知识库id
* @param queryText 查询内容
* @param topNumber 最多返回条数
* @param similarity 最小分数
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/embedding/hitTest/{knowId}")
public Result<?> hitTest(@PathVariable("knowId") String knowId,
@RequestParam(name = "queryText") String queryText,
@RequestParam(name = "topNumber") Integer topNumber,
@RequestParam(name = "similarity") Double similarity) {
List<Map<String, Object>> searchResp = embeddingHandler.searchEmbedding(knowId, queryText, topNumber, similarity);
return Result.ok(searchResp);
}
/**
* 向量查询
*
* @param knowIds 知识库ids
* @param queryText 查询内容
* @param topNumber 最多返回条数
* @param similarity 最小分数
* @return
* @author chenrui
* @date 2025/2/18 17:09
*/
@GetMapping(value = "/embedding/search")
public Result<?> embeddingSearch(@RequestParam("knowIds") List<String> knowIds,
@RequestParam(name = "queryText") String queryText,
@RequestParam(name = "topNumber", required = false) Integer topNumber,
@RequestParam(name = "similarity", required = false) Double similarity) {
KnowledgeSearchResult searchResp = embeddingHandler.embeddingSearch(knowIds, queryText, topNumber, similarity);
return Result.ok(searchResp);
}
/**
* 通过ids批量查询知识库
*
* @param ids
* @return
* @author chenrui
* @date 2025/2/27 16:44
*/
@GetMapping(value = "/query/batch/byId")
public Result<?> queryBatchByIds(@RequestParam(name = "ids", required = true) String ids) {
List<String> idList = Arrays.asList(ids.split(","));
List<AiragKnowledge> airagKnowledges = airagKnowledgeService.listByIds(idList);
return Result.OK(airagKnowledges);
}
}

View File

@ -0,0 +1,139 @@
package org.jeecg.modules.airag.llm.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
@Tag(name = "AiRag模型配置")
@RestController
@RequestMapping("/airag/airagModel")
@Slf4j
public class AiragModelController extends JeecgController<AiragModel, IAiragModelService> {
@Autowired
private IAiragModelService airagModelService;
/**
* 分页列表查询
*
* @param airagModel
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<AiragModel>> queryPageList(AiragModel airagModel, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
QueryWrapper<AiragModel> queryWrapper = QueryGenerator.initQueryWrapper(airagModel, req.getParameterMap());
Page<AiragModel> page = new Page<AiragModel>(pageNo, pageSize);
IPage<AiragModel> pageList = airagModelService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加
*
* @param airagModel
* @return
*/
@PostMapping(value = "/add")
public Result<String> add(@RequestBody AiragModel airagModel) {
airagModelService.save(airagModel);
return Result.OK("添加成功!");
}
/**
* 编辑
*
* @param airagModel
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody AiragModel airagModel) {
airagModelService.updateById(airagModel);
return Result.OK("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
airagModelService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.airagModelService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<AiragModel> queryById(@RequestParam(name = "id", required = true) String id) {
AiragModel airagModel = airagModelService.getById(id);
if (airagModel == null) {
return Result.error("未找到对应数据");
}
return Result.OK(airagModel);
}
/**
* 导出excel
*
* @param request
* @param airagModel
*/
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, AiragModel airagModel) {
return super.exportXls(request, airagModel, AiragModel.class, "AiRag模型配置");
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, AiragModel.class);
}
}

View File

@ -0,0 +1,274 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.jeecg.modules.airag.llm.document;
import dev.langchain4j.data.document.BlankDocumentException;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.internal.Utils;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xslf.usermodel.XSLFTextShape;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.tika.Tika;
import org.apache.tika.exception.ZeroByteFileException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.jeecg.common.util.AssertUtils;
import org.xml.sax.ContentHandler;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* tika文档解析器,重写langchain4j的TikaDocumentParser <br/>
* jeecgboot目前不支持poi5.x,所以langchain4j同的方法不能用,自己实现
* @author chenrui
* @date 2025/3/5 16:19
*/
public class TikaDocumentParser {
private static final Tika tika = new Tika();
private static final int NO_WRITE_LIMIT = -1;
public static final Supplier<Parser> DEFAULT_PARSER_SUPPLIER = AutoDetectParser::new;
public static final Supplier<Metadata> DEFAULT_METADATA_SUPPLIER = Metadata::new;
public static final Supplier<ParseContext> DEFAULT_PARSE_CONTEXT_SUPPLIER = ParseContext::new;
public static final Supplier<ContentHandler> DEFAULT_CONTENT_HANDLER_SUPPLIER = () -> new BodyContentHandler(-1);
private final Supplier<Parser> parserSupplier;
private final Supplier<ContentHandler> contentHandlerSupplier;
private final Supplier<Metadata> metadataSupplier;
private final Supplier<ParseContext> parseContextSupplier;
public TikaDocumentParser() {
this((Supplier) ((Supplier) null), (Supplier) null, (Supplier) null, (Supplier) null);
}
public TikaDocumentParser(Supplier<Parser> parserSupplier, Supplier<ContentHandler> contentHandlerSupplier, Supplier<Metadata> metadataSupplier, Supplier<ParseContext> parseContextSupplier) {
this.parserSupplier = (Supplier) Utils.getOrDefault(parserSupplier, () -> DEFAULT_PARSER_SUPPLIER);
this.contentHandlerSupplier = (Supplier) Utils.getOrDefault(contentHandlerSupplier, () -> DEFAULT_CONTENT_HANDLER_SUPPLIER);
this.metadataSupplier = (Supplier) Utils.getOrDefault(metadataSupplier, () -> DEFAULT_METADATA_SUPPLIER);
this.parseContextSupplier = (Supplier) Utils.getOrDefault(parseContextSupplier, () -> DEFAULT_PARSE_CONTEXT_SUPPLIER);
}
public Document parse(File file) {
AssertUtils.assertNotEmpty("请选择文件", file);
try {
// 用于解析
InputStream isForParsing = Files.newInputStream(file.toPath());
// 使用 Tika 自动检测 MIME 类型
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".txt")
|| fileName.endsWith(".md")
|| fileName.endsWith(".pdf")) {
return extractByTika(isForParsing);
} else if (fileName.endsWith(".docx")) {
return extractTextFromDocx(isForParsing);
} else if (fileName.endsWith(".doc")) {
return extractTextFromDoc(isForParsing);
} else if (fileName.endsWith(".xlsx")) {
return extractTextFromExcel(isForParsing);
} else if (fileName.endsWith(".xls")) {
return extractTextFromExcel(isForParsing);
} else if (fileName.endsWith(".pptx")) {
return extractTextFromPptx(isForParsing);
} else if (fileName.endsWith(".ppt")) {
return extractTextFromPpt(isForParsing);
} else {
throw new IllegalArgumentException("不支持的文件格式: " + FilenameUtils.getExtension(fileName));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Document tryExtractDocOrDocx(InputStream inputStream) throws IOException {
try {
// 先尝试 DOCX基于 OPC XML 格式)
return extractTextFromDocx(inputStream);
} catch (Exception e1) {
try {
// 如果 DOCX 解析失败,则尝试 DOC基于二进制格式
return extractTextFromDoc(inputStream);
} catch (Exception e2) {
throw new IOException("无法解析 DOC 或 DOCX 文件", e2);
}
}
}
/**
* 使用tika提取文件内容 <br/>
* pdf/text/md等文件使用tika提取
*
* @param inputStream
* @return
* @author chenrui
* @date 2025/3/5 14:41
*/
private Document extractByTika(InputStream inputStream) {
try {
Parser parser = (Parser) this.parserSupplier.get();
ContentHandler contentHandler = (ContentHandler) this.contentHandlerSupplier.get();
Metadata metadata = (Metadata) this.metadataSupplier.get();
ParseContext parseContext = (ParseContext) this.parseContextSupplier.get();
parser.parse(inputStream, contentHandler, metadata, parseContext);
String text = contentHandler.toString();
if (Utils.isNullOrBlank(text)) {
throw new BlankDocumentException();
} else {
return Document.from(text);
}
} catch (BlankDocumentException e) {
throw e;
} catch (ZeroByteFileException var8) {
throw new BlankDocumentException();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提取docx文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:42
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromDocx(InputStream inputStream) throws IOException {
try (XWPFDocument document = new XWPFDocument(inputStream)) {
StringBuilder text = new StringBuilder();
for (XWPFParagraph para : document.getParagraphs()) {
text.append(para.getText()).append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取doc文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:42
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromDoc(InputStream inputStream) throws IOException {
try (HWPFDocument document = new HWPFDocument(inputStream);
WordExtractor extractor = new WordExtractor(document)) {
return Document.from(extractor.getText());
}
}
/**
* 提取excel文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromExcel(InputStream inputStream) throws IOException {
try (Workbook workbook = WorkbookFactory.create(inputStream)) {
StringBuilder text = new StringBuilder();
for (Sheet sheet : workbook) {
text.append("Sheet: ").append(sheet.getSheetName()).append("\n");
for (Row row : sheet) {
for (Cell cell : row) {
text.append(cell.toString()).append("\t");
}
text.append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取pptx文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromPptx(InputStream inputStream) throws IOException {
try (XMLSlideShow ppt = new XMLSlideShow(inputStream)) {
StringBuilder text = new StringBuilder();
for (XSLFSlide slide : ppt.getSlides()) {
text.append("Slide ").append(slide.getSlideNumber()).append(":\n");
List<XSLFTextShape> shapes = slide.getShapes().stream()
.filter(s -> s instanceof XSLFTextShape)
.map(s -> (XSLFTextShape) s)
.collect(Collectors.toList());
for (XSLFTextShape shape : shapes) {
text.append(shape.getText()).append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
/**
* 提取ppt文件内容
*
* @param inputStream
* @return
* @throws IOException
* @author chenrui
* @date 2025/3/5 14:43
* @deprecated 因为jeecg主项目目前不支持poi5.x, 自己实现提取功能.
*/
@Deprecated
private static Document extractTextFromPpt(InputStream inputStream) throws IOException {
try (org.apache.poi.hslf.usermodel.HSLFSlideShow ppt = new org.apache.poi.hslf.usermodel.HSLFSlideShow(inputStream)) {
StringBuilder text = new StringBuilder();
for (org.apache.poi.hslf.usermodel.HSLFSlide slide : ppt.getSlides()) {
text.append("Slide ").append(slide.getSlideNumber()).append(":\n");
for (List<HSLFTextParagraph> shapes : slide.getTextParagraphs()) {
text.append(HSLFTextParagraph.getText(shapes)).append("\n");
}
text.append("\n");
}
return Document.from(text.toString());
}
}
private static byte[] toByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int nRead;
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
}

View File

@ -0,0 +1,104 @@
package org.jeecg.modules.airag.llm.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Schema(description="AIRag知识库")
@Data
@TableName("airag_knowledge")
public class AiragKnowledge implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private java.util.Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private java.util.Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 知识库名称
*/
@Excel(name = "知识库名称", width = 15)
@Schema(description = "知识库名称")
private String name;
/**
* 向量模型id
*/
@Excel(name = "向量模型id", width = 15, dictTable = "airag_model where model_type = 'EMBED'", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_model where model_type = 'EMBED'", dicText = "name", dicCode = "id")
@Schema(description = "向量模型id")
private String embedId;
/**
* 描述
*/
@Excel(name = "描述", width = 15)
@Schema(description = "描述")
private String descr;
/**
* 状态
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private String status;
}

View File

@ -0,0 +1,127 @@
package org.jeecg.modules.airag.llm.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.*;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.UnsupportedEncodingException;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Schema(description="airag知识库文档")
@Data
@TableName("airag_knowledge_doc")
public class AiragKnowledgeDoc implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 知识库id
*/
@Schema(description = "知识库id")
private String knowledgeId;
/**
* 标题
*/
@Excel(name = "标题", width = 15)
@Schema(description = "标题")
private String title;
/**
* 类型
*/
@Excel(name = "类型", width = 15, dicCode = "know_doc_type")
@Schema(description = "类型")
private String type;
/**
* 内容
*/
@Excel(name = "内容", width = 15)
@Schema(description = "内容")
private String content;
/**
* 元数据,存储上传文件的存储目录以及网站站点 <br/>
* eg. {"filePath":"https://xxxxxx","website":"http://hellp.jeecg.com"}
*/
@Excel(name = "元数据", width = 15)
@Schema(description = "元数据")
private String metadata;
/**
* 状态
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
private String status;
/**
* 服务器基础路径
*/
@TableField(exist = false)
private String baseUrl;
}

View File

@ -0,0 +1,123 @@
package org.jeecg.modules.airag.llm.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-17
* @Version: V1.0
*/
@Data
@TableName("airag_model")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="AiRag模型配置")
public class AiragModel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**
* 创建人
*/
@Schema(description = "创建人")
private String createBy;
/**
* 创建日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**
* 更新人
*/
@Schema(description = "更新人")
private String updateBy;
/**
* 更新日期
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**
* 所属部门
*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
private String tenantId;
/**
* 名称
*/
@Excel(name = "名称", width = 15)
@Schema(description = "名称")
private String name;
/**
* 供应者
*/
@Excel(name = "供应者", width = 15, dicCode = "model_provider")
@Dict(dicCode = "model_provider")
@Schema(description = "供应者")
private String provider;
/**
* 模型类型
*/
@Excel(name = "模型类型", width = 15, dicCode = "model_type")
@Dict(dicCode = "model_type")
@Schema(description = "模型类型")
private String modelType;
/**
* 模型名称
*/
@Excel(name = "模型名称", width = 15)
@Schema(description = "模型名称")
private String modelName;
/**
* API域名
*/
@Excel(name = "API域名", width = 15)
@Schema(description = "API域名")
private String baseUrl;
/**
* 凭证信息
*/
@Excel(name = "凭证信息", width = 15)
@Schema(description = "凭证信息")
private String credential;
/**
* 模型参数
*/
@Excel(name = "模型参数", width = 15)
@Schema(description = "模型参数")
private String modelParams;
}

View File

@ -0,0 +1,286 @@
package org.jeecg.modules.airag.llm.handler;
import com.alibaba.fastjson.JSONObject;
import dev.langchain4j.data.message.*;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.ai.handler.LLMHandler;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.common.handler.AIChatParams;
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
/**
* 大模型聊天工具类
*
* @Author: chenrui
* @Date: 2025/2/18 14:31
*/
@Slf4j
@Component
public class AIChatHandler implements IAIChatHandler {
@Autowired
IAiragModelService airagModelService;
@Autowired
EmbeddingHandler embeddingHandler;
@Autowired
LLMHandler llmHandler;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
/**
* 问答
*
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public String completions(String modelId, List<ChatMessage> messages) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
// 整理消息
return completions(modelId, messages, null);
}
/**
* 问答
*
* @param modelId
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public String completions(String modelId, List<ChatMessage> messages, AIChatParams params) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
AiragModel airagModel = airagModelService.getById(modelId);
return completions(airagModel, messages, params);
}
/**
* 问答
*
* @param airagModel
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/24 17:30
*/
private String completions(AiragModel airagModel, List<ChatMessage> messages, AIChatParams params) {
params = mergeParams(airagModel, params);
String resp = llmHandler.completions(messages, params);
if (resp.contains("</think>")
&& (null == params.getNoThinking() || params.getNoThinking())) {
String[] thinkSplit = resp.split("</think>");
resp = thinkSplit[thinkSplit.length - 1];
}
return resp;
}
/**
* 使用默认模型问答
*
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/3/12 15:13
*/
@Override
public String completionsByDefaultModel(List<ChatMessage> messages, AIChatParams params) {
return completions(new AiragModel(), messages, params);
}
/**
* 聊天(流式)
*
* @param modelId
* @param messages
* @return
* @author chenrui
* @date 2025/2/20 21:06
*/
@Override
public TokenStream chat(String modelId, List<ChatMessage> messages) {
return chat(modelId, messages, null);
}
/**
* 聊天(流式)
*
* @param modelId
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/18 21:03
*/
@Override
public TokenStream chat(String modelId, List<ChatMessage> messages, AIChatParams params) {
AssertUtils.assertNotEmpty("至少发送一条消息", messages);
AssertUtils.assertNotEmpty("请选择模型", modelId);
AiragModel airagModel = airagModelService.getById(modelId);
return chat(airagModel, messages, params);
}
/**
* 聊天(流式)
*
* @param airagModel
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/2/24 17:29
*/
private TokenStream chat(AiragModel airagModel, List<ChatMessage> messages, AIChatParams params) {
params = mergeParams(airagModel, params);
return llmHandler.chat(messages, params);
}
/**
* 使用默认模型聊天
*
* @param messages
* @param params
* @return
* @author chenrui
* @date 2025/3/12 15:13
*/
@Override
public TokenStream chatByDefaultModel(List<ChatMessage> messages, AIChatParams params) {
return chat(new AiragModel(), messages, params);
}
/**
* 合并 airagmodel和params,params为准
*
* @param airagModel
* @param params
* @return
* @author chenrui
* @date 2025/3/11 17:45
*/
private AIChatParams mergeParams(AiragModel airagModel, AIChatParams params) {
if (null == airagModel) {
return params;
}
if (params == null) {
params = new AIChatParams();
}
params.setProvider(airagModel.getProvider());
params.setModelName(airagModel.getModelName());
params.setBaseUrl(airagModel.getBaseUrl());
if (oConvertUtils.isObjectNotEmpty(airagModel.getCredential())) {
JSONObject modelCredential = JSONObject.parseObject(airagModel.getCredential());
params.setApiKey(oConvertUtils.getString(modelCredential.getString("apiKey"), null));
params.setSecretKey(oConvertUtils.getString(modelCredential.getString("secretKey"), null));
}
if (oConvertUtils.isObjectNotEmpty(airagModel.getModelParams())) {
JSONObject modelParams = JSONObject.parseObject(airagModel.getModelParams());
if (oConvertUtils.isObjectEmpty(params.getTemperature())) {
params.setTemperature(modelParams.getDouble("temperature"));
}
if (oConvertUtils.isObjectEmpty(params.getTopP())) {
params.setTopP(modelParams.getDouble("topP"));
}
if (oConvertUtils.isObjectEmpty(params.getPresencePenalty())) {
params.setPresencePenalty(modelParams.getDouble("presencePenalty"));
}
if (oConvertUtils.isObjectEmpty(params.getFrequencyPenalty())) {
params.setFrequencyPenalty(modelParams.getDouble("frequencyPenalty"));
}
if (oConvertUtils.isObjectEmpty(params.getMaxTokens())) {
params.setMaxTokens(modelParams.getInteger("maxTokens"));
}
if (oConvertUtils.isObjectEmpty(params.getTimeout())) {
params.setMaxTokens(modelParams.getInteger("timeout"));
}
}
// RAG
List<String> knowIds = params.getKnowIds();
if (oConvertUtils.isObjectNotEmpty(knowIds)) {
QueryRouter queryRouter = embeddingHandler.getQueryRouter(knowIds, params.getTopNumber(), params.getSimilarity());
params.setQueryRouter(queryRouter);
}
return params;
}
@Override
public UserMessage buildUserMessage(String content, List<String> images) {
AssertUtils.assertNotEmpty("请输入消息内容", content);
List<Content> contents = new ArrayList<>();
contents.add(TextContent.from(content));
if (oConvertUtils.isObjectNotEmpty(images)) {
// 获取所有图片,将他们转换为ImageContent
List<ImageContent> imageContents = buildImageContents(images);
contents.addAll(imageContents);
}
return UserMessage.from(contents);
}
@Override
public List<ImageContent> buildImageContents(List<String> images) {
List<ImageContent> imageContents = new ArrayList<>();
for (String imageUrl : images) {
Matcher matcher = LLMConsts.WEB_PATTERN.matcher(imageUrl);
if (matcher.matches()) {
// 来源于网络
imageContents.add(ImageContent.from(imageUrl));
} else {
// 本地文件
String filePath = uploadpath + File.separator + imageUrl;
// 读取文件并转换为 base64 编码字符串
try {
Path path = Paths.get(filePath);
byte[] fileContent = Files.readAllBytes(path);
String base64Data = Base64.getEncoder().encodeToString(fileContent);
// 获取文件的 MIME 类型
String mimeType = Files.probeContentType(path);
// 构建 ImageContent 对象
imageContents.add(ImageContent.from(base64Data, mimeType));
} catch (IOException e) {
log.error("读取文件失败: " + filePath, e);
throw new RuntimeException("发送消息失败,读取文件异常:" + e.getMessage(), e);
}
}
}
return imageContents;
}
}

View File

@ -0,0 +1,139 @@
package org.jeecg.modules.airag.llm.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Description: 命令行执行工具类
* @Author: chenrui
* @Date: 2024/4/8 10:11
*/
@Slf4j
public class CommandExecUtil {
/**
* 执行命令行
*
* @param command
* @param args
* @return
* @throws IOException
* @author chenrui
* @date 2024/4/9 10:59
*/
public static String execCommand(String command, String[] args) throws IOException {
if (null == command || command.isEmpty()) {
throw new IllegalArgumentException("命令不能为空");
}
return execCommand(command.split(" "), args);
}
/**
* 执行命令行
*
* @param command 脚本目录
* @param args 参数
* @author chenrui
* @date 2024/4/09 10:30
*/
public static String execCommand(String[] command, String[] args) throws IOException {
if (null == command || command.length == 0) {
throw new IllegalArgumentException("命令不能为空");
}
if (null != args && args.length > 0) {
command = (String[]) ArrayUtils.addAll(command, args);
}
// windows系统处理文件夹空格问题
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
List<String> commandNew = new ArrayList<>(command.length + 2);
commandNew.addAll(Arrays.asList("cmd.exe", "/c"));
for (String tempCommand : command) {
if (tempCommand.contains(" ")) {
tempCommand = "\"" + tempCommand.replaceAll("\"", "'") + "\"";
}
commandNew.add(tempCommand);
}
command = commandNew.toArray(new String[0]);
}
Process process = null;
try {
log.debug(" =============================== Runtime command Script ===============================" );
log.debug(String.join(" ", command));
log.debug(" =============================== Runtime command Script =============================== " );
process = Runtime.getRuntime().exec(command);
try (ByteArrayOutputStream resultOutStream = new ByteArrayOutputStream();
InputStream processInStream = new BufferedInputStream(process.getInputStream())) {
new Thread(new InputStreamRunnable(process.getErrorStream(), "ErrorStream")).start();
int num;
byte[] bs = new byte[1024];
while ((num = processInStream.read(bs)) != -1) {
resultOutStream.write(bs, 0, num);
String stepMsg = new String(bs);
// log.debug("命令行日志:" + stepMsg);
if (stepMsg.contains("input any key to continue...")) {
process.destroy();
}
}
String result = resultOutStream.toString();
log.debug("执行命令完成:" + result);
return result;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
throw e;
} finally {
if (process != null) {
process.destroy();
}
}
}
/**
* exec 控制台输出获取线程类
* 使用单独的线程获取控制台输出,防止输入流阻塞
*
* @author chenrui
* @date 2024/4/09 10:30
*/
static class InputStreamRunnable implements Runnable {
BufferedReader bReader = null;
String type = null;
public InputStreamRunnable(InputStream is, String _type) {
try {
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), StandardCharsets.UTF_8));
type = _type;
} catch (Exception ex) {
}
}
@SuppressWarnings("unused")
public void run() {
String line;
int lineNum = 0;
try {
while ((line = bReader.readLine()) != null) {
lineNum++;
// Thread.sleep(200);
}
bReader.close();
} catch (Exception ignored) {
}
}
}
}

View File

@ -0,0 +1,582 @@
package org.jeecg.modules.airag.llm.handler;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiTokenizer;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.tika.parser.AutoDetectParser;
import org.jeecg.ai.factory.AiModelFactory;
import org.jeecg.ai.factory.AiModelOptions;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.*;
import org.jeecg.modules.airag.common.handler.IEmbeddingHandler;
import org.jeecg.modules.airag.common.vo.knowledge.KnowledgeSearchResult;
import org.jeecg.modules.airag.llm.config.EmbedStoreConfigBean;
import org.jeecg.modules.airag.llm.config.KnowConfigBean;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.KNOWLEDGE_DOC_TYPE_FILE;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.KNOWLEDGE_DOC_TYPE_WEB;
/**
* 向量工具类
*
* @Author: chenrui
* @Date: 2025/2/18 14:31
*/
@Slf4j
@Component
public class EmbeddingHandler implements IEmbeddingHandler {
@Autowired
EmbedStoreConfigBean embedStoreConfigBean;
@Autowired
@Lazy
private IAiragModelService airagModelService;
@Autowired
@Lazy
private IAiragKnowledgeService airagKnowledgeService;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
@Autowired
KnowConfigBean knowConfigBean;
/**
* 默认分段长度
*/
private static final int DEFAULT_SEGMENT_SIZE = 1000;
/**
* 默认分段重叠长度
*/
private static final int DEFAULT_OVERLAP_SIZE = 50;
/**
* 向量存储元数据:knowledgeId
*/
public static final String EMBED_STORE_METADATA_KNOWLEDGEID = "knowledgeId";
/**
* 向量存储元数据:docId
*/
public static final String EMBED_STORE_METADATA_DOCID = "docId";
/**
* 向量存储元数据:docName
*/
public static final String EMBED_STORE_METADATA_DOCNAME = "docName";
/**
* 向量存储缓存
*/
private static final ConcurrentHashMap<String, EmbeddingStore<TextSegment>> EMBED_STORE_CACHE = new ConcurrentHashMap<>();
/**
* 向量化文档
*
* @param knowId
* @param doc
* @return
* @author chenrui
* @date 2025/2/18 11:52
*/
public Map<String, Object> embeddingDocument(String knowId, AiragKnowledgeDoc doc) {
AiragKnowledge airagKnowledge = airagKnowledgeService.getById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
AssertUtils.assertNotEmpty("文档不能为空", doc);
// 读取文档
String content = doc.getContent();
// 向量化并存储
if (oConvertUtils.isEmpty(content)) {
switch (doc.getType()) {
case KNOWLEDGE_DOC_TYPE_FILE:
//解析文件
if (knowConfigBean.isEnableMinerU()) {
parseFileByMinerU(doc);
}
content = parseFile(doc);
break;
case KNOWLEDGE_DOC_TYPE_WEB:
// TODO author: chenrui for:读取网站内容 date:2025/2/18
break;
}
}
//update-begin---author:chenrui ---date:20250307 for[QQYUN-11443]【AI】是不是应该把标题也生成到向量库里标题一般是有意义的------------
if (oConvertUtils.isNotEmpty(doc.getTitle())) {
content = doc.getTitle() + "\n\n" + content;
}
//update-end---author:chenrui ---date:20250307 for[QQYUN-11443]【AI】是不是应该把标题也生成到向量库里标题一般是有意义的------------
// 向量化 date:2025/2/18
AiragModel model = getEmbedModelData(airagKnowledge.getEmbedId());
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除旧数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_DOCID).isEqualTo(doc.getId()));
// 分段器
DocumentSplitter splitter = DocumentSplitters.recursive(DEFAULT_SEGMENT_SIZE, DEFAULT_OVERLAP_SIZE, new OpenAiTokenizer());
// 分段并存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
Metadata metadata = Metadata.metadata(EMBED_STORE_METADATA_DOCID, doc.getId())
.put(EMBED_STORE_METADATA_KNOWLEDGEID, doc.getKnowledgeId())
.put(EMBED_STORE_METADATA_DOCNAME, FilenameUtils.getName(doc.getTitle()));
Document from = Document.from(content, metadata);
ingestor.ingest(from);
return metadata.toMap();
}
/**
* 向量查询(多知识库)
*
* @param knowIds
* @param queryText
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/18 16:52
*/
public KnowledgeSearchResult embeddingSearch(List<String> knowIds, String queryText, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowIds);
AssertUtils.assertNotEmpty("请填写查询内容", queryText);
topNumber = oConvertUtils.getInteger(topNumber, 5);
//命中的文档列表
List<Map<String, Object>> documents = new ArrayList<>(16);
for (String knowId : knowIds) {
List<Map<String, Object>> searchResp = searchEmbedding(knowId, queryText, topNumber, similarity);
if (oConvertUtils.isObjectNotEmpty(searchResp)) {
documents.addAll(searchResp);
}
}
//命中的文档内容
StringBuilder data = new StringBuilder();
// 对documents按score降序排序并取前topNumber个
List<Map<String, Object>> sortedDocuments = documents.stream()
.sorted(Comparator.comparingDouble((Map<String, Object> doc) -> (Double) doc.get("score")).reversed())
.limit(topNumber)
.peek(doc -> data.append(doc.get("content")).append("\n"))
.collect(Collectors.toList());
return new KnowledgeSearchResult(data.toString(), sortedDocuments);
}
/**
* 向量查询
*
* @param knowId
* @param queryText
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/18 16:52
*/
public List<Map<String, Object>> searchEmbedding(String knowId, String queryText, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowId);
AiragKnowledge knowledge = airagKnowledgeService.getById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", knowledge);
AssertUtils.assertNotEmpty("请填写查询内容", queryText);
AiragModel model = getEmbedModelData(knowledge.getEmbedId());
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
Embedding queryEmbedding = embeddingModel.embed(queryText).content();
topNumber = oConvertUtils.getInteger(topNumber, modelOp.getTopNumber());
similarity = oConvertUtils.getDou(similarity, modelOp.getSimilarity());
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(topNumber)
.minScore(similarity)
.filter(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId))
.build();
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
List<EmbeddingMatch<TextSegment>> relevant = embeddingStore.search(embeddingSearchRequest).matches();
List<Map<String, Object>> result = new ArrayList<>();
if (oConvertUtils.isObjectNotEmpty(relevant)) {
result = relevant.stream().map(matchRes -> {
Map<String, Object> data = new HashMap<>();
data.put("score", matchRes.score());
data.put("content", matchRes.embedded().text());
Metadata metadata = matchRes.embedded().metadata();
data.put("chunk", metadata.getInteger("index"));
data.put(EMBED_STORE_METADATA_DOCNAME, metadata.getString(EMBED_STORE_METADATA_DOCNAME));
return data;
}).collect(Collectors.toList());
}
return result;
}
/**
* 获取向量查询路由
*
* @param knowIds
* @param topNumber
* @param similarity
* @return
* @author chenrui
* @date 2025/2/20 21:03
*/
public QueryRouter getQueryRouter(List<String> knowIds, Integer topNumber, Double similarity) {
AssertUtils.assertNotEmpty("请选择知识库", knowIds);
List<ContentRetriever> retrievers = Lists.newArrayList();
for (String knowId : knowIds) {
if (oConvertUtils.isEmpty(knowId)) {
continue;
}
AiragKnowledge knowledge = airagKnowledgeService.getById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", knowledge);
AiragModel model = getEmbedModelData(knowledge.getEmbedId());
AiModelOptions modelOptions = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOptions);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
topNumber = oConvertUtils.getInteger(topNumber, 5);
similarity = oConvertUtils.getDou(similarity, 0.75);
// 构建一个嵌入存储内容检索器,用于从嵌入存储中检索内容
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(topNumber)
.minScore(similarity)
.filter(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId))
.build();
retrievers.add(contentRetriever);
}
if (retrievers.isEmpty()) {
return null;
} else {
return new DefaultQueryRouter(retrievers);
}
}
/**
* 删除向量化文档
*
* @param knowId
* @param modelId
* @author chenrui
* @date 2025/2/18 19:07
*/
public void deleteEmbedDocsByKnowId(String knowId, String modelId) {
AssertUtils.assertNotEmpty("选择知识库", knowId);
AiragModel model = getEmbedModelData(modelId);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_KNOWLEDGEID).isEqualTo(knowId));
}
/**
* 删除向量化文档
*
* @param docIds
* @param modelId
* @author chenrui
* @date 2025/2/18 19:07
*/
public void deleteEmbedDocsByDocIds(List<String> docIds, String modelId) {
AssertUtils.assertNotEmpty("选择文档", docIds);
AiragModel model = getEmbedModelData(modelId);
EmbeddingStore<TextSegment> embeddingStore = getEmbedStore(model);
// 删除数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_DOCID).isIn(docIds));
}
/**
* 查询向量模型数据
*
* @param modelId
* @return
* @author chenrui
* @date 2025/2/20 20:08
*/
private AiragModel getEmbedModelData(String modelId) {
AssertUtils.assertNotEmpty("向量模型不能为空", modelId);
AiragModel model = airagModelService.getById(modelId);
AssertUtils.assertNotEmpty("向量模型不存在", model);
AssertUtils.assertEquals("仅支持向量模型", LLMConsts.MODEL_TYPE_EMBED, model.getModelType());
return model;
}
/**
* 获取向量存储
*
* @param model
* @return
* @author chenrui
* @date 2025/2/18 14:56
*/
private EmbeddingStore<TextSegment> getEmbedStore(AiragModel model) {
AssertUtils.assertNotEmpty("未配置模型", model);
String modelId = model.getId();
String connectionInfo = embedStoreConfigBean.getHost() + embedStoreConfigBean.getPort() + embedStoreConfigBean.getDatabase();
String key = modelId + connectionInfo;
if (EMBED_STORE_CACHE.containsKey(key)) {
return EMBED_STORE_CACHE.get(key);
}
AiModelOptions modelOp = buildModelOptions(model);
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(modelOp);
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
// Connection and table parameters
.host(embedStoreConfigBean.getHost())
.port(embedStoreConfigBean.getPort())
.database(embedStoreConfigBean.getDatabase())
.user(embedStoreConfigBean.getUser())
.password(embedStoreConfigBean.getPassword())
.table(embedStoreConfigBean.getTable())
// Embedding dimension
// Required: Must match the embedding models output dimension
.dimension(embeddingModel.dimension())
// Indexing and performance options
// Enable IVFFlat index
.useIndex(true)
// Number of lists
// for IVFFlat index
.indexListSize(100)
// Table creation options
// Automatically create the table if it doesnt exist
.createTable(true)
//Dont drop the table first (set to true if you want a fresh start)
.dropTableFirst(false)
.build();
EMBED_STORE_CACHE.put(key, embeddingStore);
return embeddingStore;
}
/**
* 构造ModelOptions
*
* @param model
* @return
* @author chenrui
* @date 2025/3/11 17:45
*/
public static AiModelOptions buildModelOptions(AiragModel model) {
AiModelOptions.AiModelOptionsBuilder modelOpBuilder = AiModelOptions.builder()
.provider(model.getProvider())
.modelName(model.getModelName())
.baseUrl(model.getBaseUrl());
if (oConvertUtils.isObjectNotEmpty(model.getCredential())) {
JSONObject modelCredential = JSONObject.parseObject(model.getCredential());
modelOpBuilder.apiKey(oConvertUtils.getString(modelCredential.getString("apiKey"), null));
modelOpBuilder.secretKey(oConvertUtils.getString(modelCredential.getString("secretKey"), null));
}
modelOpBuilder.topNumber(5);
modelOpBuilder.similarity(0.75);
return modelOpBuilder.build();
}
/**
* 解析文件
*
* @param doc
* @author chenrui
* @date 2025/3/5 11:31
*/
private String parseFile(AiragKnowledgeDoc doc) {
String metadata = doc.getMetadata();
AssertUtils.assertNotEmpty("请先上传文件", metadata);
JSONObject metadataJson = JSONObject.parseObject(metadata);
if (!metadataJson.containsKey(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH)) {
throw new JeecgBootException("请先上传文件");
}
String filePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH);
AssertUtils.assertNotEmpty("请先上传文件", filePath);
// 网络资源,先下载到临时目录
filePath = ensureFile(filePath);
// 提取文档内容
File docFile = new File(filePath);
if (docFile.exists()) {
Document document = new TikaDocumentParser(AutoDetectParser::new, null, null, null).parse(docFile);
if (null != document) {
String content = document.text();
// 判断是否md文档
String fileType = FilenameUtils.getExtension(docFile.getName());
if ("md".contains(fileType)) {
// 如果是md文件查找所有图片语法如果是本地图片替换成网络图片
String baseUrl = doc.getBaseUrl() + "/sys/common/static/";
String sourcePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH);
if(oConvertUtils.isNotEmpty(sourcePath)) {
String escapedPath = uploadpath;
if (File.separator.equals("\\")){
escapedPath = uploadpath.replace("//", "\\\\");
}
sourcePath = sourcePath.replaceFirst("^" + escapedPath, "").replace("\\", "/");
baseUrl = baseUrl + sourcePath + "/";
StringBuffer sb = replaceImageUrl(content, baseUrl);
content = sb.toString();
}
}
return content;
}
}
return null;
}
@NotNull
private static StringBuffer replaceImageUrl(String content, String baseUrl) {
// 正则表达式匹配md文件中的图片语法 ![alt text](image url)
String mdImagePattern = "!\\[(.*?)]\\((.*?)(\\s*=\\d+)?\\)";
Pattern pattern = Pattern.compile(mdImagePattern);
Matcher matcher = pattern.matcher(content);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String imageUrl = matcher.group(2);
// 检查是否是本地图片路径
if (!imageUrl.startsWith("http")) {
// 替换成网络图片路径
String networkImageUrl = baseUrl + imageUrl;
matcher.appendReplacement(sb, "![" + matcher.group(1) + "](" + networkImageUrl + ")");
} else {
matcher.appendReplacement(sb, "![" + matcher.group(1) + "](" + imageUrl + ")");
}
}
matcher.appendTail(sb);
return sb;
}
/**
* 通过MinerU解析文件
*
* @param doc
* @author chenrui
* @date 2025/4/1 17:37
*/
private void parseFileByMinerU(AiragKnowledgeDoc doc) {
String metadata = doc.getMetadata();
AssertUtils.assertNotEmpty("请先上传文件", metadata);
JSONObject metadataJson = JSONObject.parseObject(metadata);
if (!metadataJson.containsKey(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH)) {
throw new JeecgBootException("请先上传文件");
}
String filePath = metadataJson.getString(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH);
AssertUtils.assertNotEmpty("请先上传文件", filePath);
filePath = ensureFile(filePath);
File docFile = new File(filePath);
String fileType = FilenameUtils.getExtension(filePath);
if (!docFile.exists()
|| "txt".equalsIgnoreCase(fileType)
|| "md".equalsIgnoreCase(fileType)) {
return ;
}
String command = "magic-pdf";
if (oConvertUtils.isNotEmpty(knowConfigBean.getCondaEnv())) {
command = "conda run -n " + knowConfigBean.getCondaEnv() + " " + command;
}
String outputPath = docFile.getParentFile().getAbsolutePath();
String[] args = {
"-p", docFile.getAbsolutePath(),
"-o", outputPath,
};
try {
String execLog = CommandExecUtil.execCommand(command, args);
log.info("执行命令行:" + command + " args:" + Arrays.toString(args) + "\n log::" + execLog);
// 如果成功,替换文件路径和静态资源路径
String fileBaseName = FilenameUtils.getBaseName(docFile.getName());
String newFileDir = outputPath + File.separator + fileBaseName + File.separator + "auto" + File.separator ;
// 先检查文件是否存在,存在才替换
File convertedFile = new File(newFileDir + fileBaseName + ".md");
if (convertedFile.exists()) {
log.info("文件转换成md成功,替换文件路径和静态资源路径");
newFileDir = newFileDir.replaceFirst("^" + uploadpath, "");
metadataJson.put(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH, newFileDir + fileBaseName + ".md");
metadataJson.put(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH, newFileDir);
doc.setMetadata(metadataJson.toJSONString());
}
} catch (IOException e) {
log.error("文件转换md失败,使用传统提取方案{}", e.getMessage(), e);
}
}
/**
* 确保文件存在
* @param filePath
* @return
* @author chenrui
* @date 2025/4/1 17:36
*/
@NotNull
private String ensureFile(String filePath) {
// 网络资源,先下载到临时目录
Matcher matcher = LLMConsts.WEB_PATTERN.matcher(filePath);
if (matcher.matches()) {
log.info("网络资源,下载到临时目录:" + filePath);
// 准备文件
String tempFilePath = uploadpath + File.separator + "tmp" + File.separator + UUIDGenerator.generate() + File.separator;
String fileName = filePath;
if (fileName.contains("?")) {
fileName = fileName.substring(0, fileName.indexOf("?"));
}
fileName = FilenameUtils.getName(fileName);
tempFilePath = tempFilePath + fileName;
FileDownloadUtils.download2DiskFromNet(filePath, tempFilePath);
filePath = tempFilePath;
} else {
//本地文件
filePath = uploadpath + File.separator + filePath;
}
return filePath;
}
}

View File

@ -0,0 +1,23 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface AiragKnowledgeDocMapper extends BaseMapper<AiragKnowledgeDoc> {
/**
* 通过主表id删除子表数据
*
* @param mainId 主表id
* @return boolean
*/
public boolean deleteByMainId(@Param("mainId") String mainId);
}

View File

@ -0,0 +1,14 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface AiragKnowledgeMapper extends BaseMapper<AiragKnowledge> {
}

View File

@ -0,0 +1,14 @@
package org.jeecg.modules.airag.llm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.llm.entity.AiragModel;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
public interface AiragModelMapper extends BaseMapper<AiragModel> {
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragKnowledgeDocMapper">
<delete id="deleteByMainId" parameterType="java.lang.String">
DELETE
FROM airag_knowledge_doc
WHERE knowledge_id = #{mainId}
</delete>
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper">
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.airag.llm.mapper.AiragModelMapper">
</mapper>

View File

@ -0,0 +1,79 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface IAiragKnowledgeDocService extends IService<AiragKnowledgeDoc> {
/**
* 重建文档
*
* @param docIds
* @return
* @author chenrui
* @date 2025/2/18 11:14
*/
Result<?> rebuildDocument(String docIds);
/**
* 添加文档
*
* @param airagKnowledgeDoc
* @return
* @author chenrui
* @date 2025/2/18 15:30
*/
Result<?> editDocument(AiragKnowledgeDoc airagKnowledgeDoc);
/**
* 通过知识库id重建文档
*
* @param knowId
* @return
* @author chenrui
* @date 2025/2/18 18:54
*/
Result<?> rebuildDocumentByKnowId(String knowId);
/**
* 通过知识库id删除文档
*
* @param knowIds
* @return
* @author chenrui
* @date 2025/2/18 18:59
*/
Result<?> removeByKnowIds(List<String> knowIds);
/**
* 通过文档id批量删除文档
*
* @param docIds
* @return
* @author chenrui
* @date 2025/2/18 19:16
*/
Result<?> removeDocByIds(List<String> docIds);
/**
* 从zip包导入文档
* @param knowId
* @param file
* @return
* @author chenrui
* @date 2025/3/20 13:50
*/
Result<?> importDocumentFromZip(String knowId, MultipartFile file);
}

View File

@ -0,0 +1,14 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
/**
* AIRag知识库
*
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
public interface IAiragKnowledgeService extends IService<AiragKnowledge> {
}

View File

@ -0,0 +1,17 @@
package org.jeecg.modules.airag.llm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import dev.langchain4j.data.message.ChatMessage;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import java.util.List;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
public interface IAiragModelService extends IService<AiragModel> {
}

View File

@ -0,0 +1,355 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.config.mqtoken.UserTokenContext;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.*;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.modules.airag.llm.consts.LLMConsts;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.entity.AiragKnowledgeDoc;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeDocMapper;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeDocService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.jeecg.modules.airag.llm.consts.LLMConsts.*;
/**
* @Description: airag知识库文档
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Slf4j
@Service
public class AiragKnowledgeDocServiceImpl extends ServiceImpl<AiragKnowledgeDocMapper, AiragKnowledgeDoc> implements IAiragKnowledgeDocService {
@Autowired
private AiragKnowledgeDocMapper airagKnowledgeDocMapper;
@Autowired
private AiragKnowledgeMapper airagKnowledgeMapper;
@Autowired
EmbeddingHandler embeddingHandler;
@Value(value = "${jeecg.path.upload:}")
private String uploadpath;
/**
* 支持的文档类型
*/
private static final List<String> SUPPORT_DOC_TYPE = Arrays.asList("txt", "pdf", "docx", "doc", "pptx", "ppt", "xlsx", "xls", "md");
/**
* 向量化线程池大小
*/
private static final int THREAD_POOL_SIZE = 10;
/**
* 向量化文档线程池
*/
private static final ExecutorService buildDocExecutorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
@Transactional(rollbackFor = {Exception.class})
@Override
public Result<?> editDocument(AiragKnowledgeDoc airagKnowledgeDoc) {
AssertUtils.assertNotEmpty("文档不能未空", airagKnowledgeDoc);
AssertUtils.assertNotEmpty("知识库不能未空", airagKnowledgeDoc.getKnowledgeId());
AssertUtils.assertNotEmpty("文档标题不能未空", airagKnowledgeDoc.getTitle());
AssertUtils.assertNotEmpty("文档类型不能未空", airagKnowledgeDoc.getType());
if (KNOWLEDGE_DOC_TYPE_TEXT.equals(airagKnowledgeDoc.getType())) {
AssertUtils.assertNotEmpty("文档内容不能为空", airagKnowledgeDoc.getContent());
}
airagKnowledgeDoc.setStatus(KNOWLEDGE_DOC_STATUS_DRAFT);
// 保存到数据库
if (this.saveOrUpdate(airagKnowledgeDoc)) {
// 重建向量
return this.rebuildDocument(airagKnowledgeDoc.getId());
} else {
return Result.error("保存失败");
}
}
@Override
public Result<?> rebuildDocumentByKnowId(String knowId) {
AssertUtils.assertNotEmpty("知识库id不能为空", knowId);
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectList(Wrappers.lambdaQuery(AiragKnowledgeDoc.class).eq(AiragKnowledgeDoc::getKnowledgeId, knowId));
if (oConvertUtils.isObjectEmpty(docList)) {
return Result.OK();
}
String docIds = docList.stream().map(AiragKnowledgeDoc::getId).collect(Collectors.joining(","));
return rebuildDocument(docIds);
}
@Transactional(rollbackFor = {java.lang.Exception.class})
@Override
public Result<?> rebuildDocument(String docIds) {
AssertUtils.assertNotEmpty("请选择要重建的文档", docIds);
List<String> docIdList = Arrays.asList(docIds.split(","));
// 查询数据
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectBatchIds(docIdList);
AssertUtils.assertNotEmpty("文档不存在", docList);
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
String baseUrl = CommonUtils.getBaseUrl(request);
// 检查状态
List<AiragKnowledgeDoc> knowledgeDocs = docList.stream()
.filter(doc -> {
//update-begin---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
if(KNOWLEDGE_DOC_STATUS_BUILDING.equalsIgnoreCase(doc.getStatus())){
Date updateTime = doc.getUpdateTime();
if (updateTime != null) {
// 向量化超过了5分钟,重新向量化
long timeDifference = System.currentTimeMillis() - updateTime.getTime();
return timeDifference > 5 * 60 * 1000;
}else{
return true;
}
} else {
return true;
}
//update-end---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
})
.peek(doc -> {
doc.setStatus(KNOWLEDGE_DOC_STATUS_BUILDING);
doc.setBaseUrl(baseUrl);
})
.collect(Collectors.toList());
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("操作成功");
}
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("操作成功");
}
// 更新状态
this.updateBatchById(knowledgeDocs);
// 异步重建文档
String tenantId = TenantContext.getTenant();
String token = TokenUtils.getTokenByRequest();
knowledgeDocs.forEach((doc) -> {
CompletableFuture.runAsync(() -> {
UserTokenContext.setToken(token);
TenantContext.setTenant(tenantId);
String knowId = doc.getKnowledgeId();
log.info("开始重建文档, 知识库id: {}, 文档id: {}", knowId, doc.getId());
doc.setStatus(KNOWLEDGE_DOC_STATUS_BUILDING);
this.updateById(doc);
//update-begin---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
try {
Map<String, Object> metadata = embeddingHandler.embeddingDocument(knowId, doc);
// 更新数据 date:2025/2/18
if (null != metadata) {
doc.setStatus(KNOWLEDGE_DOC_STATUS_COMPLETE);
this.updateById(doc);
log.info("重建文档成功, 知识库id: {}, 文档id: {}", knowId, doc.getId());
} else {
doc.setStatus(KNOWLEDGE_DOC_STATUS_DRAFT);
this.updateById(doc);
log.info("重建文档失败, 知识库id: {}, 文档id: {}", knowId, doc.getId());
}
}catch (Throwable t){
doc.setStatus(KNOWLEDGE_DOC_STATUS_DRAFT);
this.updateById(doc);
log.error("重建文档失败:" + t.getMessage() + ", 知识库id: " + knowId + ", 文档id: " + doc.getId(), t);
}
//update-end---author:chenrui ---date:20250410 for[QQYUN-11943]【ai】ai知识库 上传完文档 一直显示构建中?------------
}, buildDocExecutorService);
});
log.info("返回操作成功");
return Result.ok("操作成功");
}
@Override
public Result<?> removeByKnowIds(List<String> knowIds) {
AssertUtils.assertNotEmpty("选择知识库", knowIds);
for (String knowId : knowIds) {
AiragKnowledge airagKnowledge = airagKnowledgeMapper.selectById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
// 删除数据
embeddingHandler.deleteEmbedDocsByKnowId(knowId, airagKnowledge.getEmbedId());
airagKnowledgeDocMapper.deleteByMainId(knowId);
}
return Result.OK();
}
@Override
public Result<?> removeDocByIds(List<String> docIds) {
AssertUtils.assertNotEmpty("请选择要删除的文档", docIds);
// 查询数据
List<AiragKnowledgeDoc> docList = airagKnowledgeDocMapper.selectBatchIds(docIds);
AssertUtils.assertNotEmpty("文档不存在", docList);
// 整理数据
Map<String, List<String>> knowledgeDocs = docList.stream().collect(Collectors.groupingBy(
AiragKnowledgeDoc::getKnowledgeId,
Collectors.mapping(AiragKnowledgeDoc::getId, Collectors.toList())
));
if (oConvertUtils.isObjectEmpty(knowledgeDocs)) {
return Result.ok("success");
}
knowledgeDocs.forEach((knowId, groupedDocIds) -> {
AiragKnowledge airagKnowledge = airagKnowledgeMapper.selectById(knowId);
AssertUtils.assertNotEmpty("知识库不存在", airagKnowledge);
AssertUtils.assertNotEmpty("请先为知识库配置向量模型库", airagKnowledge.getEmbedId());
// 删除数据
embeddingHandler.deleteEmbedDocsByDocIds(groupedDocIds, airagKnowledge.getEmbedId());
airagKnowledgeDocMapper.deleteBatchIds(groupedDocIds);
});
return Result.ok("success");
}
@Transactional(rollbackFor = {java.lang.Exception.class})
@Override
public Result<?> importDocumentFromZip(String knowId, MultipartFile zipFile) {
AssertUtils.assertNotEmpty("请先选择知识库", knowId);
AssertUtils.assertNotEmpty("请上传文件", zipFile);
long startTime = System.currentTimeMillis();
log.info("开始上传知识库文档(zip), 知识库id: {}, 文件名: {}", knowId, zipFile.getOriginalFilename());
try {
String bizPath = knowId + File.separator + UUIDGenerator.generate();
String workDir = uploadpath + File.separator + bizPath + File.separator;
String sourcesPath = workDir + "files";
SsrfFileTypeFilter.checkUploadFileType(zipFile);
// 通过filePath 检查文件是不是压缩包(zip)
String zipFileName = FilenameUtils.getBaseName(zipFile.getOriginalFilename());
String fileExt = FilenameUtils.getExtension(zipFile.getOriginalFilename());
if (null == fileExt || !fileExt.equalsIgnoreCase("zip")) {
throw new JeecgBootException("请上传zip压缩包");
}
String uploadedZipPath = CommonUtils.uploadLocal(zipFile, bizPath, uploadpath);
// 解压缩文件
List<AiragKnowledgeDoc> docList = new ArrayList<>();
AtomicInteger fileCount = new AtomicInteger(0);
unzipFile(uploadpath + File.separator + uploadedZipPath, sourcesPath, uploadedFile -> {
// 仅支持txt、pdf、docx、pptx、html、md文件
String fileName = uploadedFile.getName();
if (!SUPPORT_DOC_TYPE.contains(FilenameUtils.getExtension(fileName).toLowerCase())) {
log.warn("不支持的文件类型: {}", fileName);
return;
}
String baseName = FilenameUtils.getBaseName(fileName);
AiragKnowledgeDoc doc = new AiragKnowledgeDoc();
doc.setKnowledgeId(knowId);
doc.setTitle(baseName);
doc.setType(LLMConsts.KNOWLEDGE_DOC_TYPE_FILE);
doc.setStatus(LLMConsts.KNOWLEDGE_DOC_STATUS_DRAFT);
String relativePath;
if (File.separator.equals("\\")) {
// Windows path handling
String escapedPath = uploadpath.replace("//", "\\\\");
relativePath = uploadedFile.getPath().replaceFirst("^" + escapedPath, "");
} else {
// Unix path handling
relativePath = uploadedFile.getPath().replaceFirst("^" + uploadpath, "");
}
JSONObject metadata = new JSONObject();
metadata.put(LLMConsts.KNOWLEDGE_DOC_METADATA_FILEPATH, relativePath);
metadata.put(LLMConsts.KNOWLEDGE_DOC_METADATA_SOURCES_PATH, sourcesPath);
doc.setMetadata(metadata.toJSONString());
docList.add(doc);
});
// 保存数据
this.saveBatch(docList);
// 重建文档
String docIds = docList.stream().map(AiragKnowledgeDoc::getId).filter(oConvertUtils::isObjectNotEmpty).collect(Collectors.joining(","));
rebuildDocument(docIds);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
log.info("上传知识库文档(zip)成功, 知识库id: {}, 文件名: {}, 耗时: {}ms", knowId, zipFile.getOriginalFilename(), (System.currentTimeMillis() - startTime));
return Result.ok("上传成功");
}
/**
* 解压缩文件
*
* @param zipFilePath
* @param destDir
* @param afterExtract
* @throws IOException
* @author chenrui
* @date 2025/3/20 14:37
*/
public static void unzipFile(String zipFilePath, String destDir, Consumer<File> afterExtract) throws
IOException {
// 创建目标目录
File dir = new File(destDir);
if (!dir.exists()) {
dir.mkdirs();
}
try (ZipFile zipFile = new ZipFile(zipFilePath)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
byte[] buffer = new byte[1024];
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
File newFile = new File(destDir, ze.getName());
// 预防 ZIP 路径穿越攻击
String canonicalDestDirPath = dir.getCanonicalPath();
String canonicalFilePath = newFile.getCanonicalPath();
if (!canonicalFilePath.startsWith(canonicalDestDirPath + File.separator)) {
throw new IOException("ZIP 路径穿越攻击被阻止: " + ze.getName());
}
if (ze.isDirectory()) {
newFile.mkdirs();
} else {
// 创建父目录
new File(newFile.getParent()).mkdirs();
// 读取 ZIP 文件并写入新文件
try (InputStream zis = zipFile.getInputStream(ze);
FileOutputStream fos = new FileOutputStream(newFile)) {
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
if (afterExtract != null) {
afterExtract.accept(newFile);
}
}
}
}
}
}

View File

@ -0,0 +1,18 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.mapper.AiragKnowledgeMapper;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.stereotype.Service;
/**
* @Description: AIRag知识库
* @Author: jeecg-boot
* @Date: 2025-02-18
* @Version: V1.0
*/
@Service
public class AiragKnowledgeServiceImpl extends ServiceImpl<AiragKnowledgeMapper, AiragKnowledge> implements IAiragKnowledgeService {
}

View File

@ -0,0 +1,19 @@
package org.jeecg.modules.airag.llm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.airag.llm.entity.AiragModel;
import org.jeecg.modules.airag.llm.mapper.AiragModelMapper;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.stereotype.Service;
/**
* @Description: AiRag模型配置
* @Author: jeecg-boot
* @Date: 2025-02-14
* @Version: V1.0
*/
@Service
public class AiragModelServiceImpl extends ServiceImpl<AiragModelMapper, AiragModel> implements IAiragModelService {
}

View File

@ -0,0 +1,33 @@
package org.jeecg.modules.airag.llm.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* 知识库查询返回结果
*
* @Author: chenrui
* @Date: 2025/2/18 17:53
*/
@Data
@NoArgsConstructor
public class KnowledgeSearchResult {
/**
* 命中的文档内容
*/
String data;
/**
* 命中的文档列表
*/
List<Map<String, Object>> documents;
public KnowledgeSearchResult(String data, List<Map<String, Object>> documents) {
this.data = data;
this.documents = documents;
}
}

View File

@ -0,0 +1,72 @@
server:
port: 7008
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
- org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
datasource:
druid:
stat-view-servlet:
enabled: true
loginUsername: admin
loginPassword: 123456
allow:
web-stat-filter:
enabled: true
dynamic:
druid: # 全局druid参数绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 1000
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters去掉后监控界面sql无法统计'wall'用于防火墙
# !!!!!mysql
# filters: stat,slf4j,wall
# !!!!!DM
filters: stat,slf4j
# 允许SELECT语句的WHERE子句是一个永真条件
# wall:
# selectWhereAlwayTrueCheck: false
# 打开mergeSql功能慢SQL记录
stat:
merge-sql: true
slow-sql-millis: 5000
datasource:
master:
## !!!!!MYSQL
url: jdbc:mysql://localhost:3306/jeecg-boot-dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
database: 0
host: 192.168.1.188
port: 6379
password: 'res983'
jeecg:
ai-rag:
embed-store:
host: "localhost"
port: 15432
database: "postgres"
user: "postgres"
password: "123456"
table: "embeddings"

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 -->
<property name="LOG_HOME" value="../logs" />
<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/jeecgboot-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
</encoder>
</appender>
<!-- 生成 error html格式日志开始 -->
<appender name="HTML" class="ch.qos.logback.core.FileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--设置日志级别,过滤掉info日志,只输入error日志-->
<level>ERROR</level>
</filter>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
<file>${LOG_HOME}/error-log.html</file>
</appender>
<!-- 生成 error html格式日志结束 -->
<!-- 每天生成一个html格式的日志开始 -->
<appender name="FILE_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/jeecgboot-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<MaxFileSize>10MB</MaxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%p%d%msg%M%F{32}%L</pattern>
</layout>
</encoder>
</appender>
<!-- 每天生成一个html格式的日志结束 -->
<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<logger name="logging.level.dev.langchain4j" level="DEBUG" />
<logger name="logging.level.dev.ai4j.openai4j" level="DEBUG" />
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="STDOUT" />
<!-- <appender-ref ref="FILE" />-->
<!-- <appender-ref ref="HTML" />-->
<!-- <appender-ref ref="FILE_HTML" />-->
</root>
</configuration>

View File

@ -0,0 +1,82 @@
package org.jeecg.modules.airag.test;
import dev.langchain4j.data.document.Document;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.parser.AutoDetectParser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.wildfly.common.Assert;
import java.io.File;
import java.io.IOException;
/**
* @Description: 文件解析测试
* @Author: chenrui
* @Date: 2025/2/11 16:11
*/
@Slf4j
public class TestFileParse {
@Test
public void testParseTxt() {
readFile("test.txt");
}
@Test
public void testParsePdf() {
readFile("test.pdf");
}
@Test
public void testParseMd() {
readFile("test.md");
}
@Test
public void testParseDoc() {
readFile("test.docx");
}
@Test
public void testParseDoc2003() {
readFile("test.doc");
}
@Test
public void testParseExcel() {
readFile("test.xlsx");
}
@Test
public void testParseExcel2003() {
readFile("test.xls");
}
@Test
public void testParsePPT() {
readFile("test.pptx");
}
@Test
public void testParsePPT2003() {
readFile("test.ppt");
}
private static void readFile(String filePath) {
try {
ClassPathResource resource = new ClassPathResource(filePath);
File file = resource.getFile();
TikaDocumentParser parser = new TikaDocumentParser(AutoDetectParser::new, null, null, null);
Document document = parser.parse(file);
Assert.assertNotNull(document);
System.out.println(filePath + "----" + document.text());
Assert.assertTrue(oConvertUtils.isNotEmpty(document));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,54 @@
package org.jeecg.modules.airag.test;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.junit.jupiter.api.Test;
import java.io.IOException;
/**
* @Description: 流程测试
* @Author: chenrui
* @Date: 2025/2/11 16:11
*/
@Slf4j
public class TestFlows {
@Test
public void testRunFlow(){
String id = "1889499701976358913";
// String id = "1889571074002247682"; //switch
// String id = "1889608218175463425"; //脚本
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3Mzk1NDY0NDIsInVzZXJuYW1lIjoiamVlY2cifQ.CFIV79PUYmOAiqBKT3yjwihHWwf954DvS-4oKERmJVU";
String request = request(id,token);
System.out.println(request);
}
private String request(String id,String token) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://localhost:7008/airag/airagFlow/flow/run/" + id + "?field1=%25E5%2593%2588%25E5%2593%2588&field2=%25E4%25B8%25AD%25E5%259B%25BD")
.get()
.addHeader("X-Access-Token", token)
.addHeader("Accept", "*/*")
.addHeader("Accept-Encoding", "gzip, deflate, br")
.addHeader("User-Agent", "PostmanRuntime-ApipostRuntime/1.1.0")
.addHeader("Connection", "keep-alive")
.addHeader("Cookie", "JSESSIONID=442C48D3D1D0B2878A597AB6EBF2A07E")
.build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// TODO author: chenrui for:完善用例,使用java方式调用 date:2025/2/14
}

View File

@ -0,0 +1,177 @@
//package org.jeecg.modules.airag.test;
//
//import dev.langchain4j.data.message.*;
//import dev.langchain4j.model.chat.ChatLanguageModel;
//import dev.langchain4j.model.openai.OpenAiChatModel;
//import dev.langchain4j.model.output.Response;
//import dev.langchain4j.service.AiServices;
//import dev.langchain4j.service.TokenStream;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.ai.assistant.AiChatAssistant;
//import org.jeecg.ai.factory.AiModelFactory;
//import org.jeecg.ai.factory.AiModelOptions;
//import org.jeecg.ai.handler.AIParams;
//import org.jeecg.ai.handler.LLMHandler;
//import org.jeecg.modules.airag.llm.handler.AIChatParams;
//import org.junit.Test;
//
//import java.io.IOException;
//import java.nio.file.Files;
//import java.nio.file.Paths;
//import java.util.ArrayList;
//import java.util.Base64;
//import java.util.Collections;
//import java.util.concurrent.ConcurrentHashMap;
//import java.util.concurrent.CountDownLatch;
//
///**
// * @Description: 流程测试
// * @Author: chenrui
// * @Date: 2025/2/11 16:11
// */
//@Slf4j
//public class TestLLM {
//
// @Test
// public void sendByModel() {
// String apiKey = "sk-xxx";
// String baseUrl = "https://api.v3.cm/v1";
// String modelName = "gpt-3.5-turbo";
// double temperature = 0.7;
// ChatLanguageModel llmModel = OpenAiChatModel.builder()
// .apiKey(apiKey)
// .baseUrl(baseUrl)
// .modelName(modelName)
// .temperature(temperature)
// .build();
// String hello = llmModel.generate("hello");
// System.out.println(hello);
// }
//
// @Test
// public void testChat() {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
// AiServices<AiChatAssistant> chatAssistantBuilder = AiServices.builder(AiChatAssistant.class);
// chatAssistantBuilder.chatLanguageModel(chatModel);
// AiChatAssistant chatAssistant = chatAssistantBuilder.build();
// String str = chatAssistant.chat("hello");
// System.out.println(str);
// }
//
// @Test
// public void sendImg() throws IOException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
//
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// new ImageContent(base64Data, mimeType)
// );
// Response<AiMessage> generate = chatModel.generate(userMessage);
// System.out.println(generate.content());
// }
//
// @Test
// public void testSendImsWithLLMHandler() throws IOException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
//
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// new ImageContent(base64Data, mimeType)
// );
// LLMHandler llmHandler = new LLMHandler();
// AIParams aiParams = new AIParams();
// aiParams.setProvider(AiModelFactory.AIMODEL_TYPE_OPENAI);
// aiParams.setModelName("gpt-4o-mini");
// aiParams.setApiKey("sk-xxx");
// aiParams.setBaseUrl("https://api.v3.cm/v1");
// String completions = llmHandler.completions(Collections.singletonList(userMessage), aiParams);
// System.out.println(completions);
// }
//
// @Test
// public void testChatImsWithLLMHandler() throws IOException, InterruptedException {
// AiModelOptions options = AiModelOptions.builder()
// .provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .modelName("gpt-4o-mini")
// .apiKey("sk-xxx")
// .baseUrl("https://api.v3.cm/v1")
// .build();
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(options);
//
// // 读取文件并转换为 base64 编码字符串
// byte[] fileContent = Files.readAllBytes(Paths.get("src/test/resources/test.jpg"));
// String base64Data = Base64.getEncoder().encodeToString(fileContent);
//
// // 获取文件的 MIME 类型
// String mimeType = Files.probeContentType(Paths.get("src/test/resources/test.jpg"));
//
// // 构建 ImageContent 对象
// ImageContent imageContent = new ImageContent(base64Data, mimeType);
// UserMessage userMessage = UserMessage.from(
// TextContent.from("你看到了什么"),
// // 构建 ImageContent 对象
// imageContent,
// ImageContent.from("https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/logo-qqy_1741658353407.png")
// );
// LLMHandler llmHandler = new LLMHandler();
// AIParams aiParams = new AIParams();
// aiParams.setProvider(AiModelFactory.AIMODEL_TYPE_OPENAI);
// aiParams.setModelName("gpt-4o-mini");
// aiParams.setApiKey("sk-xxx");
// aiParams.setBaseUrl("https://api.v3.cm/v1");
// TokenStream chat = llmHandler.chat(Collections.singletonList(userMessage), aiParams);
// CountDownLatch countDownLatch = new CountDownLatch(1);
// chat.onNext(s -> System.out.println(s))
// .onComplete(s -> {
// System.out.println(s);
// countDownLatch.countDown();
// })
// .onError(e -> {
// System.out.println(e.getMessage());
// countDownLatch.countDown();
// }).start();
// countDownLatch.await();
//
// }
//
//
//}

View File

@ -0,0 +1,407 @@
//package org.jeecg.modules.airag.test;
//
//import dev.langchain4j.data.document.Document;
//import dev.langchain4j.data.document.DocumentSplitter;
//import dev.langchain4j.data.document.Metadata;
//import dev.langchain4j.data.document.splitter.DocumentSplitters;
//import dev.langchain4j.data.embedding.Embedding;
//import dev.langchain4j.data.segment.TextSegment;
//import dev.langchain4j.memory.ChatMemory;
//import dev.langchain4j.memory.chat.MessageWindowChatMemory;
//import dev.langchain4j.model.chat.ChatLanguageModel;
//import dev.langchain4j.model.embedding.EmbeddingModel;
//import dev.langchain4j.model.input.PromptTemplate;
//import dev.langchain4j.model.openai.OpenAiTokenizer;
//import dev.langchain4j.rag.DefaultRetrievalAugmentor;
//import dev.langchain4j.rag.RetrievalAugmentor;
//import dev.langchain4j.rag.content.injector.DefaultContentInjector;
//import dev.langchain4j.rag.content.retriever.ContentRetriever;
//import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
//import dev.langchain4j.rag.query.router.DefaultQueryRouter;
//import dev.langchain4j.rag.query.router.QueryRouter;
//import dev.langchain4j.service.AiServices;
//import dev.langchain4j.store.embedding.EmbeddingMatch;
//import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
//import dev.langchain4j.store.embedding.EmbeddingStore;
//import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
//import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.ai.assistant.AiChatAssistant;
//import org.jeecg.ai.factory.AiModelFactory;
//import org.jeecg.ai.factory.AiModelOptions;
//import org.junit.Before;
//import org.junit.Test;
//
//import java.util.List;
//
//import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
//
///**
// * @Description: 流程测试
// * @Author: chenrui
// * @Date: 2025/2/11 16:11
// */
//@Slf4j
//public class TestVector {
//
// String openAIBaseUrl = "https://api.v3.cm/v1";
// String openAIApiKey = "sk-xxx";
//
// EmbeddingModel embeddingModel;
// EmbeddingStore<TextSegment> embeddingStore;
//
// @Before
// public void before() {
// AiModelOptions.AiModelOptionsBuilder modelOpBuilder = AiModelOptions.builder().provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
// .baseUrl(openAIBaseUrl).apiKey(openAIApiKey);
// embeddingModel = AiModelFactory.createEmbeddingModel(modelOpBuilder.build());
// embeddingStore = PgVectorEmbeddingStore.builder()
// // Connection and table parameters
// .host("localhost")
// .port(15432)
// .database("postgres")
// .user("postgres")
// .password("123456")
// .table("test_embeddings")
// // Embedding dimension
// .dimension(embeddingModel.dimension()) // Required: Must match the embedding models output dimension
// // Indexing and performance options
// .useIndex(true) // Enable IVFFlat index
// .indexListSize(100) // Number of lists for IVFFlat index
// // Table creation options
// .createTable(true) // Automatically create the table if it doesnt exist
// .dropTableFirst(false) // Dont drop the table first (set to true if you want a fresh start)
// .build();
// }
//
// @Test
// public void testSave2Vector() {
// embeddingStore.removeAll(metadataKey("id").isEqualTo("sdfsdf"));
// DocumentSplitter splitter = DocumentSplitters.recursive(200,
// 50,
// new OpenAiTokenizer());
// EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
// .documentSplitter(splitter)
// .embeddingModel(embeddingModel)
// .embeddingStore(embeddingStore)
// .build();
// Document from = Document.from(doc, Metadata.metadata("id", "sdfsdf"));
// ingestor.ingest(from);
// }
//
// @Test
// public void testQueryByVector() {
// Embedding queryEmbedding = embeddingModel.embed("全日制工作与非全日制工作有什么区别?").content();
// EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
// .queryEmbedding(queryEmbedding)
// .maxResults(5)
// .filter(metadataKey("id").isEqualTo("sdfsdf"))
// .build();
//
// List<EmbeddingMatch<TextSegment>> relevant = embeddingStore.search(embeddingSearchRequest).matches();
// for (int i = 0; i < relevant.size(); i++) {
// EmbeddingMatch<TextSegment> embeddingMatch = relevant.get(i);
// System.out.println("结果:" + i + "=================================================");
// System.out.println("分数:" + embeddingMatch.score()); // 0.8144288608390052
// System.out.println("内容:" + embeddingMatch.embedded().text()); // I like football.
// }
// }
//
//
// @Test
// public void testQueryByRAG() {
// ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
// .embeddingStore(embeddingStore)
// .embeddingModel(embeddingModel)
// .maxResults(5)
// // maxResults can also be specified dynamically depending on the query
//// .dynamicMaxResults(query -> 3)
// .minScore(0.75)
// // minScore can also be specified dynamically depending on the query
//// .dynamicMinScore(query -> 0.75)
// .filter(metadataKey("id").isEqualTo("sdfsdf"))
// // filter can also be specified dynamically depending on the query
//// .dynamicFilter(query -> {
//// String userId = getUserId(query.metadata().chatMemoryId());
//// return metadataKey("userId").isEqualTo(userId);
//// })
// .build();
//
// ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// ChatLanguageModel chatModel = AiModelFactory.createChatModel(AiModelOptions.builder()
// .baseUrl(openAIBaseUrl).apiKey(openAIApiKey).provider(AiModelFactory.AIMODEL_TYPE_OPENAI).build());
//
// QueryRouter queryRouter = new DefaultQueryRouter(contentRetriever);
// RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder().queryRouter(queryRouter)
// .contentInjector(DefaultContentInjector.builder()
// .promptTemplate(PromptTemplate.from("{{userMessage}}\n\n用以下信息回答问题:\n{{contents}}\n\n"))
// .build()).build();
// AiChatAssistant assistant = AiServices.builder(AiChatAssistant.class)
// .chatLanguageModel(chatModel)
// .chatMemory(chatMemory)
// .retrievalAugmentor(retrievalAugmentor)
// .build();
// String chat = assistant.chat("未签订劳动合同?");
// System.out.println(chat);
//
// }
//
//
// String doc = "中华人民共和国劳动合同法\n" +
// "已根据2013.07.01实施的修正案修改\n" +
// "目录\n" +
// "第一章 总则\n" +
// "第二章 劳动合同的订立\n" +
// "第三章 劳动合同的履行和变更\n" +
// "第四章 劳动合同的解除和终止\n" +
// "第五章 特别规定\n" +
// "第六章 监督检查\n" +
// "第七章 法律责任\n" +
// "第八章 附则\n" +
// "第一章 总则\n" +
// "第一条 为了完善劳动合同制度,明确劳动合同双方当事人的权利和义务,保护劳动者的合法权益,构建和发展和谐稳定的劳动关系,制定本法。\n" +
// "第二条 中华人民共和国境内的企业、个体经济组织、民办非企业单位等组织(以下称用人单位)与劳动者建立劳动关系,订立、履行、变更、解除或者终止劳动合同,适用本法。\n" +
// "国家机关、事业单位、社会团体和与其建立劳动关系的劳动者,订立、履行、变更、解除或者终止劳动合同,依照本法执行。\n" +
// "第三条 订立劳动合同,应当遵循合法、公平、平等自愿、协商一致、诚实信用的原则。\n" +
// "依法订立的劳动合同具有约束力,用人单位与劳动者应当履行劳动合同约定的义务。\n" +
// "第四条 用人单位应当依法建立和完善劳动规章制度,保障劳动者享有劳动权利、履行劳动义务。\n" +
// "用人单位在制定、修改或者决定有关劳动报酬、工作时间、休息休假、劳动安全卫生、保险福利、职工培训、劳动纪律以及劳动定额管理等直接涉及劳动者切身利益的规章制度或者重大事项时,应当经职工代表大会或者全体职工讨论,提出方案和意见,与工会或者职工代表平等协商确定。\n" +
// "在规章制度和重大事项决定实施过程中,工会或者职工认为不适当的,有权向用人单位提出,通过协商予以修改完善。\n" +
// "用人单位应当将直接涉及劳动者切身利益的规章制度和重大事项决定公示,或者告知劳动者。\n" +
// "第五条 县级以上人民政府劳动行政部门会同工会和企业方面代表,建立健全协调劳动关系三方机制,共同研究解决有关劳动关系的重大问题。\n" +
// "第六条 工会应当帮助、指导劳动者与用人单位依法订立和履行劳动合同,并与用人单位建立集体协商机制,维护劳动者的合法权益。\n" +
// "第二章 劳动合同的订立\n" +
// "第七条 用人单位自用工之日起即与劳动者建立劳动关系。用人单位应当建立职工名册备查。\n" +
// "第八条 用人单位招用劳动者时,应当如实告知劳动者工作内容、工作条件、工作地点、职业危害、安全生产状况、劳动报酬,以及劳动者要求了解的其他情况;用人单位有权了解劳动者与劳动合同直接相关的基本情况,劳动者应当如实说明。\n" +
// "第九条 用人单位招用劳动者,不得扣押劳动者的居民身份证和其他证件,不得要求劳动者提供担保或者以其他名义向劳动者收取财物。\n" +
// "第十条 建立劳动关系,应当订立书面劳动合同。\n" +
// "已建立劳动关系,未同时订立书面劳动合同的,应当自用工之日起一个月内订立书面劳动合同。\n" +
// "用人单位与劳动者在用工前订立劳动合同的,劳动关系自用工之日起建立。\n" +
// "第十一条 用人单位未在用工的同时订立书面劳动合同,与劳动者约定的劳动报酬不明确的,新招用的劳动者的劳动报酬按照集体合同规定的标准执行;没有集体合同或者集体合同未规定的,实行同工同酬。\n" +
// "第十二条 劳动合同分为固定期限劳动合同、无固定期限劳动合同和以完成一定工作任务为期限的劳动合同。\n" +
// "第十三条 固定期限劳动合同,是指用人单位与劳动者约定合同终止时间的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立固定期限劳动合同。\n" +
// "第十四条 无固定期限劳动合同,是指用人单位与劳动者约定无确定终止时间的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立无固定期限劳动合同。有下列情形之一,劳动者提出或者同意续订、订立劳动合同的,除劳动者提出订立固定期限劳动合同外,应当订立无固定期限劳动合同:\n" +
// "(一)劳动者在该用人单位连续工作满十年的;\n" +
// "(二)用人单位初次实行劳动合同制度或者国有企业改制重新订立劳动合同时,劳动者在该用人单位连续工作满十年且距法定退休年龄不足十年的;\n" +
// "(三)连续订立二次固定期限劳动合同,且劳动者没有本法第三十九条和第四十条第一项、第二项规定的情形,续订劳动合同的。\n" +
// "用人单位自用工之日起满一年不与劳动者订立书面劳动合同的,视为用人单位与劳动者已订立无固定期限劳动合同。\n" +
// "第十五条 以完成一定工作任务为期限的劳动合同,是指用人单位与劳动者约定以某项工作的完成为合同期限的劳动合同。\n" +
// "用人单位与劳动者协商一致,可以订立以完成一定工作任务为期限的劳动合同。\n" +
// "第十六条 劳动合同由用人单位与劳动者协商一致,并经用人单位与劳动者在劳动合同文本上签字或者盖章生效。\n" +
// "劳动合同文本由用人单位和劳动者各执一份。\n" +
// "第十七条 劳动合同应当具备以下条款:\n" +
// "(一)用人单位的名称、住所和法定代表人或者主要负责人;\n" +
// "(二)劳动者的姓名、住址和居民身份证或者其他有效身份证件号码;\n" +
// "(三)劳动合同期限;\n" +
// "(四)工作内容和工作地点;\n" +
// "(五)工作时间和休息休假;\n" +
// "(六)劳动报酬;\n" +
// "(七)社会保险;\n" +
// "(八)劳动保护、劳动条件和职业危害防护;\n" +
// "(九)法律、法规规定应当纳入劳动合同的其他事项。\n" +
// "劳动合同除前款规定的必备条款外,用人单位与劳动者可以约定试用期、培训、保守秘密、补充保险和福利待遇等其他事项。\n" +
// "第十八条 劳动合同对劳动报酬和劳动条件等标准约定不明确,引发争议的,用人单位与劳动者可以重新协商;协商不成的,适用集体合同规定;没有集体合同或者集体合同未规定劳动报酬的,实行同工同酬;没有集体合同或者集体合同未规定劳动条件等标准的,适用国家有关规定。\n" +
// "第十九条 劳动合同期限三个月以上不满一年的,试用期不得超过一个月;劳动合同期限一年以上不满三年的,试用期不得超过二个月;三年以上固定期限和无固定期限的劳动合同,试用期不得超过六个月。\n" +
// "同一用人单位与同一劳动者只能约定一次试用期。\n" +
// "以完成一定工作任务为期限的劳动合同或者劳动合同期限不满三个月的,不得约定试用期。\n" +
// "试用期包含在劳动合同期限内。劳动合同仅约定试用期的,试用期不成立,该期限为劳动合同期限。\n" +
// "第二十条 劳动者在试用期的工资不得低于本单位相同岗位最低档工资或者劳动合同约定工资的百分之八十,并不得低于用人单位所在地的最低工资标准。\n" +
// "第二十一条 在试用期中,除劳动者有本法第三十九条和第四十条第一项、第二项规定的情形外,用人单位不得解除劳动合同。用人单位在试用期解除劳动合同的,应当向劳动者说明理由。\n" +
// "第二十二条 用人单位为劳动者提供专项培训费用,对其进行专业技术培训的,可以与该劳动者订立协议,约定服务期。\n" +
// "劳动者违反服务期约定的,应当按照约定向用人单位支付违约金。违约金的数额不得超过用人单位提供的培训费用。用人单位要求劳动者支付的违约金不得超过服务期尚未履行部分所应分摊的培训费用。\n" +
// "用人单位与劳动者约定服务期的,不影响按照正常的工资调整机制提高劳动者在服务期期间的劳动报酬。\n" +
// "第二十三条 用人单位与劳动者可以在劳动合同中约定保守用人单位的商业秘密和与知识产权相关的保密事项。\n" +
// "对负有保密义务的劳动者,用人单位可以在劳动合同或者保密协议中与劳动者约定竞业限制条款,并约定在解除或者终止劳动合同后,在竞业限制期限内按月给予劳动者经济补偿。劳动者违反竞业限制约定的,应当按照约定向用人单位支付违约金。\n" +
// "第二十四条 竞业限制的人员限于用人单位的高级管理人员、高级技术人员和其他负有保密义务的人员。竞业限制的范围、地域、期限由用人单位与劳动者约定,竞业限制的约定不得违反法律、法规的规定。\n" +
// "在解除或者终止劳动合同后,前款规定的人员到与本单位生产或者经营同类产品、从事同类业务的有竞争关系的其他用人单位,或者自己开业生产或者经营同类产品、从事同类业务的竞业限制期限,不得超过二年。\n" +
// "第二十五条 除本法第二十二条和第二十三条规定的情形外,用人单位不得与劳动者约定由劳动者承担违约金。\n" +
// "第二十六条 下列劳动合同无效或者部分无效:\n" +
// "(一)以欺诈、胁迫的手段或者乘人之危,使对方在违背真实意思的情况下订立或者变更劳动合同的;\n" +
// "(二)用人单位免除自己的法定责任、排除劳动者权利的;\n" +
// "(三)违反法律、行政法规强制性规定的。\n" +
// "对劳动合同的无效或者部分无效有争议的,由劳动争议仲裁机构或者人民法院确认。\n" +
// "第二十七条 劳动合同部分无效,不影响其他部分效力的,其他部分仍然有效。\n" +
// "第二十八条 劳动合同被确认无效,劳动者已付出劳动的,用人单位应当向劳动者支付劳动报酬。劳动报酬的数额,参照本单位相同或者相近岗位劳动者的劳动报酬确定。\n" +
// "第三章 劳动合同的履行和变更\n" +
// "第二十九条 用人单位与劳动者应当按照劳动合同的约定,全面履行各自的义务。\n" +
// "第三十条 用人单位应当按照劳动合同约定和国家规定,向劳动者及时足额支付劳动报酬。\n" +
// "用人单位拖欠或者未足额支付劳动报酬的,劳动者可以依法向当地人民法院申请支付令,人民法院应当依法发出支付令。\n" +
// "第三十一条 用人单位应当严格执行劳动定额标准,不得强迫或者变相强迫劳动者加班。用人单位安排加班的,应当按照国家有关规定向劳动者支付加班费。\n" +
// "第三十二条 劳动者拒绝用人单位管理人员违章指挥、强令冒险作业的,不视为违反劳动合同。\n" +
// "劳动者对危害生命安全和身体健康的劳动条件,有权对用人单位提出批评、检举和控告。\n" +
// "第三十三条 用人单位变更名称、法定代表人、主要负责人或者投资人等事项,不影响劳动合同的履行。\n" +
// "第三十四条 用人单位发生合并或者分立等情况,原劳动合同继续有效,劳动合同由承继其权利和义务的用人单位继续履行。\n" +
// "第三十五条 用人单位与劳动者协商一致,可以变更劳动合同约定的内容。变更劳动合同,应当采用书面形式。\n" +
// "变更后的劳动合同文本由用人单位和劳动者各执一份。\n" +
// "第四章 劳动合同的解除和终止\n" +
// "第三十六条 用人单位与劳动者协商一致,可以解除劳动合同。\n" +
// "第三十七条 劳动者提前三十日以书面形式通知用人单位,可以解除劳动合同。劳动者在试用期内提前三日通知用人单位,可以解除劳动合同。\n" +
// "第三十八条 用人单位有下列情形之一的,劳动者可以解除劳动合同:\n" +
// "(一)未按照劳动合同约定提供劳动保护或者劳动条件的;\n" +
// "(二)未及时足额支付劳动报酬的;\n" +
// "(三)未依法为劳动者缴纳社会保险费的;\n" +
// "(四)用人单位的规章制度违反法律、法规的规定,损害劳动者权益的;\n" +
// "(五)因本法第二十六条第一款规定的情形致使劳动合同无效的;\n" +
// "(六)法律、行政法规规定劳动者可以解除劳动合同的其他情形。\n" +
// "用人单位以暴力、威胁或者非法限制人身自由的手段强迫劳动者劳动的,或者用人单位违章指挥、强令冒险作业危及劳动者人身安全的,劳动者可以立即解除劳动合同,不需事先告知用人单位。\n" +
// "第三十九条 劳动者有下列情形之一的,用人单位可以解除劳动合同:\n" +
// "(一)在试用期间被证明不符合录用条件的;\n" +
// "(二)严重违反用人单位的规章制度的;\n" +
// "(三)严重失职,营私舞弊,给用人单位造成重大损害的;\n" +
// "(四)劳动者同时与其他用人单位建立劳动关系,对完成本单位的工作任务造成严重影响,或者经用人单位提出,拒不改正的;\n" +
// "(五)因本法第二十六条第一款第一项规定的情形致使劳动合同无效的;\n" +
// "(六)被依法追究刑事责任的。\n" +
// "第四十条 有下列情形之一的,用人单位提前三十日以书面形式通知劳动者本人或者额外支付劳动者一个月工资后,可以解除劳动合同:\n" +
// "(一)劳动者患病或者非因工负伤,在规定的医疗期满后不能从事原工作,也不能从事由用人单位另行安排的工作的;\n" +
// "(二)劳动者不能胜任工作,经过培训或者调整工作岗位,仍不能胜任工作的;\n" +
// "(三)劳动合同订立时所依据的客观情况发生重大变化,致使劳动合同无法履行,经用人单位与劳动者协商,未能就变更劳动合同内容达成协议的。\n" +
// "第四十一条 有下列情形之一,需要裁减人员二十人以上或者裁减不足二十人但占企业职工总数百分之十以上的,用人单位提前三十日向工会或者全体职工说明情况,听取工会或者职工的意见后,裁减人员方案经向劳动行政部门报告,可以裁减人员:\n" +
// "(一)依照企业破产法规定进行重整的;\n" +
// "(二)生产经营发生严重困难的;\n" +
// "(三)企业转产、重大技术革新或者经营方式调整,经变更劳动合同后,仍需裁减人员的;\n" +
// "(四)其他因劳动合同订立时所依据的客观经济情况发生重大变化,致使劳动合同无法履行的。\n" +
// "裁减人员时,应当优先留用下列人员:\n" +
// "(一)与本单位订立较长期限的固定期限劳动合同的;\n" +
// "(二)与本单位订立无固定期限劳动合同的;\n" +
// "(三)家庭无其他就业人员,有需要扶养的老人或者未成年人的。\n" +
// "用人单位依照本条第一款规定裁减人员,在六个月内重新招用人员的,应当通知被裁减的人员,并在同等条件下优先招用被裁减的人员。\n" +
// "第四十二条 劳动者有下列情形之一的,用人单位不得依照本法第四十条、第四十一条的规定解除劳动合同:\n" +
// "(一)从事接触职业病危害作业的劳动者未进行离岗前职业健康检查,或者疑似职业病病人在诊断或者医学观察期间的;\n" +
// "(二)在本单位患职业病或者因工负伤并被确认丧失或者部分丧失劳动能力的;\n" +
// "(三)患病或者非因工负伤,在规定的医疗期内的;\n" +
// "(四)女职工在孕期、产期、哺乳期的;\n" +
// "(五)在本单位连续工作满十五年,且距法定退休年龄不足五年的;\n" +
// "(六)法律、行政法规规定的其他情形。\n" +
// "第四十三条 用人单位单方解除劳动合同,应当事先将理由通知工会。用人单位违反法律、行政法规规定或者劳动合同约定的,工会有权要求用人单位纠正。用人单位应当研究工会的意见,并将处理结果书面通知工会。\n" +
// "第四十四条 有下列情形之一的,劳动合同终止:\n" +
// "(一)劳动合同期满的;\n" +
// "(二)劳动者开始依法享受基本养老保险待遇的;\n" +
// "(三)劳动者死亡,或者被人民法院宣告死亡或者宣告失踪的;\n" +
// "(四)用人单位被依法宣告破产的;\n" +
// "(五)用人单位被吊销营业执照、责令关闭、撤销或者用人单位决定提前解散的;\n" +
// "(六)法律、行政法规规定的其他情形。\n" +
// "第四十五条 劳动合同期满,有本法第四十二条规定情形之一的,劳动合同应当续延至相应的情形消失时终止。但是,本法第四十二条第二项规定丧失或者部分丧失劳动能力劳动者的劳动合同的终止,按照国家有关工伤保险的规定执行。\n" +
// "第四十六条 有下列情形之一的,用人单位应当向劳动者支付经济补偿:\n" +
// "(一)劳动者依照本法第三十八条规定解除劳动合同的;\n" +
// "(二)用人单位依照本法第三十六条规定向劳动者提出解除劳动合同并与劳动者协商一致解除劳动合同的;\n" +
// "(三)用人单位依照本法第四十条规定解除劳动合同的;\n" +
// "(四)用人单位依照本法第四十一条第一款规定解除劳动合同的;\n" +
// "(五)除用人单位维持或者提高劳动合同约定条件续订劳动合同,劳动者不同意续订的情形外,依照本法第四十四条第一项规定终止固定期限劳动合同的;\n" +
// "(六)依照本法第四十四条第四项、第五项规定终止劳动合同的;\n" +
// "(七)法律、行政法规规定的其他情形。\n" +
// "第四十七条 经济补偿按劳动者在本单位工作的年限,每满一年支付一个月工资的标准向劳动者支付。六个月以上不满一年的,按一年计算;不满六个月的,向劳动者支付半个月工资的经济补偿。\n" +
// "劳动者月工资高于用人单位所在直辖市、设区的市级人民政府公布的本地区上年度职工月平均工资三倍的,向其支付经济补偿的标准按职工月平均工资三倍的数额支付,向其支付经济补偿的年限最高不超过十二年。\n" +
// "本条所称月工资是指劳动者在劳动合同解除或者终止前十二个月的平均工资。\n" +
// "第四十八条 用人单位违反本法规定解除或者终止劳动合同,劳动者要求继续履行劳动合同的,用人单位应当继续履行;劳动者不要求继续履行劳动合同或者劳动合同已经不能继续履行的,用人单位应当依照本法第八十七条规定支付赔偿金。\n" +
// "第四十九条 国家采取措施,建立健全劳动者社会保险关系跨地区转移接续制度。\n" +
// "第五十条 用人单位应当在解除或者终止劳动合同时出具解除或者终止劳动合同的证明,并在十五日内为劳动者办理档案和社会保险关系转移手续。\n" +
// "劳动者应当按照双方约定,办理工作交接。用人单位依照本法有关规定应当向劳动者支付经济补偿的,在办结工作交接时支付。\n" +
// "用人单位对已经解除或者终止的劳动合同的文本,至少保存二年备查。\n" +
// "第五章 特别规定\n" +
// "第一节 集体合同\n" +
// "第五十一条 企业职工一方与用人单位通过平等协商,可以就劳动报酬、工作时间、休息休假、劳动安全卫生、保险福利等事项订立集体合同。集体合同草案应当提交职工代表大会或者全体职工讨论通过。\n" +
// "集体合同由工会代表企业职工一方与用人单位订立;尚未建立工会的用人单位,由上级工会指导劳动者推举的代表与用人单位订立。\n" +
// "第五十二条 企业职工一方与用人单位可以订立劳动安全卫生、女职工权益保护、工资调整机制等专项集体合同。\n" +
// "第五十三条 在县级以下区域内,建筑业、采矿业、餐饮服务业等行业可以由工会与企业方面代表订立行业性集体合同,或者订立区域性集体合同。\n" +
// "第五十四条 集体合同订立后,应当报送劳动行政部门;劳动行政部门自收到集体合同文本之日起十五日内未提出异议的,集体合同即行生效。\n" +
// "依法订立的集体合同对用人单位和劳动者具有约束力。行业性、区域性集体合同对当地本行业、本区域的用人单位和劳动者具有约束力。\n" +
// "第五十五条 集体合同中劳动报酬和劳动条件等标准不得低于当地人民政府规定的最低标准;用人单位与劳动者订立的劳动合同中劳动报酬和劳动条件等标准不得低于集体合同规定的标准。\n" +
// "第五十六条 用人单位违反集体合同,侵犯职工劳动权益的,工会可以依法要求用人单位承担责任;因履行集体合同发生争议,经协商解决不成的,工会可以依法申请仲裁、提起诉讼。\n" +
// "第二节 劳务派遣\n" +
// "第五十七条 经营劳务派遣业务应当具备下列条件:\n" +
// " (一)注册资本不得少于人民币二百万元;\n" +
// " (二)有与开展业务相适应的固定的经营场所和设施;\n" +
// " (三)有符合法律、行政法规规定的劳务派遣管理制度;\n" +
// " (四)法律、行政法规规定的其他条件。\n" +
// " 经营劳务派遣业务,应当向劳动行政部门依法申请行政许可;经许可的,依法办理相应的公司登记。未经许可,任何单位和个人不得经营劳务派遣业务。\n" +
// "第五十八条 劳务派遣单位是本法所称用人单位,应当履行用人单位对劳动者的义务。劳务派遣单位与被派遣劳动者订立的劳动合同,除应当载明本法第十七条规定的事项外,还应当载明被派遣劳动者的用工单位以及派遣期限、工作岗位等情况。\n" +
// "劳务派遣单位应当与被派遣劳动者订立二年以上的固定期限劳动合同,按月支付劳动报酬;被派遣劳动者在无工作期间,劳务派遣单位应当按照所在地人民政府规定的最低工资标准,向其按月支付报酬。\n" +
// "第五十九条 劳务派遣单位派遣劳动者应当与接受以劳务派遣形式用工的单位(以下称用工单位)订立劳务派遣协议。劳务派遣协议应当约定派遣岗位和人员数量、派遣期限、劳动报酬和社会保险费的数额与支付方式以及违反协议的责任。\n" +
// "用工单位应当根据工作岗位的实际需要与劳务派遣单位确定派遣期限,不得将连续用工期限分割订立数个短期劳务派遣协议。\n" +
// "第六十条 劳务派遣单位应当将劳务派遣协议的内容告知被派遣劳动者。\n" +
// "劳务派遣单位不得克扣用工单位按照劳务派遣协议支付给被派遣劳动者的劳动报酬。\n" +
// "劳务派遣单位和用工单位不得向被派遣劳动者收取费用。\n" +
// "第六十一条 劳务派遣单位跨地区派遣劳动者的,被派遣劳动者享有的劳动报酬和劳动条件,按照用工单位所在地的标准执行。\n" +
// "第六十二条 用工单位应当履行下列义务:\n" +
// "(一)执行国家劳动标准,提供相应的劳动条件和劳动保护;\n" +
// "(二)告知被派遣劳动者的工作要求和劳动报酬;\n" +
// "(三)支付加班费、绩效奖金,提供与工作岗位相关的福利待遇;\n" +
// "(四)对在岗被派遣劳动者进行工作岗位所必需的培训;\n" +
// "(五)连续用工的,实行正常的工资调整机制。\n" +
// "用工单位不得将被派遣劳动者再派遣到其他用人单位。\n" +
// "第六十三条 被派遣劳动者享有与用工单位的劳动者同工同酬的权利。用工单位应当按照同工同酬原则,对被派遣劳动者与本单位同类岗位的劳动者实行相同的劳动报酬分配办法。用工单位无同类岗位劳动者的,参照用工单位所在地相同或者相近岗位劳动者的劳动报酬确定。\n" +
// "劳务派遣单位与被派遣劳动者订立的劳动合同和与用工单位订立的劳务派遣协议,载明或者约定的向被派遣劳动者支付的劳动报酬应当符合前款规定。\n" +
// "第六十四条 被派遣劳动者有权在劳务派遣单位或者用工单位依法参加或者组织工会,维护自身的合法权益。\n" +
// "第六十五条 被派遣劳动者可以依照本法第三十六条、第三十八条的规定与劳务派遣单位解除劳动合同。\n" +
// "被派遣劳动者有本法第三十九条和第四十条第一项、第二项规定情形的,用工单位可以将劳动者退回劳务派遣单位,劳务派遣单位依照本法有关规定,可以与劳动者解除劳动合同。\n" +
// "第六十六条 劳动合同用工是我国的企业基本用工形式。劳务派遣用工是补充形式,只能在临时性、辅助性或者替代性的工作岗位上实施。\n" +
// " 前款规定的临时性工作岗位是指存续时间不超过六个月的岗位;辅助性工作岗位是指为主营业务岗位提供服务的非主营业务岗位;替代性工作岗位是指用工单位的劳动者因脱产学习、休假等原因无法工作的一定期间内,可以由其他劳动者替代工作的岗位。\n" +
// " 用工单位应当严格控制劳务派遣用工数量,不得超过其用工总量的一定比例,具体比例由国务院劳动行政部门规定。\n" +
// "第六十七条 用人单位不得设立劳务派遣单位向本单位或者所属单位派遣劳动者。\n" +
// "第三节 非全日制用工\n" +
// "第六十八条 非全日制用工,是指以小时计酬为主,劳动者在同一用人单位一般平均每日工作时间不超过四小时,每周工作时间累计不超过二十四小时的用工形式。\n" +
// "第六十九条 非全日制用工双方当事人可以订立口头协议。\n" +
// "从事非全日制用工的劳动者可以与一个或者一个以上用人单位订立劳动合同;但是,后订立的劳动合同不得影响先订立的劳动合同的履行。\n" +
// "第七十条 非全日制用工双方当事人不得约定试用期。\n" +
// "第七十一条 非全日制用工双方当事人任何一方都可以随时通知对方终止用工。终止用工,用人单位不向劳动者支付经济补偿。\n" +
// "第七十二条 非全日制用工小时计酬标准不得低于用人单位所在地人民政府规定的最低小时工资标准。\n" +
// "非全日制用工劳动报酬结算支付周期最长不得超过十五日。\n" +
// "第六章 监督检查\n" +
// "第七十三条 国务院劳动行政部门负责全国劳动合同制度实施的监督管理。\n" +
// "县级以上地方人民政府劳动行政部门负责本行政区域内劳动合同制度实施的监督管理。\n" +
// "县级以上各级人民政府劳动行政部门在劳动合同制度实施的监督管理工作中,应当听取工会、企业方面代表以及有关行业主管部门的意见。\n" +
// "第七十四条 县级以上地方人民政府劳动行政部门依法对下列实施劳动合同制度的情况进行监督检查:\n" +
// "(一)用人单位制定直接涉及劳动者切身利益的规章制度及其执行的情况;\n" +
// "(二)用人单位与劳动者订立和解除劳动合同的情况;\n" +
// "(三)劳务派遣单位和用工单位遵守劳务派遣有关规定的情况;\n" +
// "(四)用人单位遵守国家关于劳动者工作时间和休息休假规定的情况;\n" +
// "(五)用人单位支付劳动合同约定的劳动报酬和执行最低工资标准的情况;\n" +
// "(六)用人单位参加各项社会保险和缴纳社会保险费的情况;\n" +
// "(七)法律、法规规定的其他劳动监察事项。\n" +
// "第七十五条 县级以上地方人民政府劳动行政部门实施监督检查时,有权查阅与劳动合同、集体合同有关的材料,有权对劳动场所进行实地检查,用人单位和劳动者都应当如实提供有关情况和材料。\n" +
// "劳动行政部门的工作人员进行监督检查,应当出示证件,依法行使职权,文明执法。\n" +
// "第七十六条 县级以上人民政府建设、卫生、安全生产监督管理等有关主管部门在各自职责范围内,对用人单位执行劳动合同制度的情况进行监督管理。\n" +
// "第七十七条 劳动者合法权益受到侵害的,有权要求有关部门依法处理,或者依法申请仲裁、提起诉讼。\n" +
// "第七十八条 工会依法维护劳动者的合法权益,对用人单位履行劳动合同、集体合同的情况进行监督。用人单位违反劳动法律、法规和劳动合同、集体合同的,工会有权提出意见或者要求纠正;劳动者申请仲裁、提起诉讼的,工会依法给予支持和帮助。\n" +
// "第七十九条 任何组织或者个人对违反本法的行为都有权举报,县级以上人民政府劳动行政部门应当及时核实、处理,并对举报有功人员给予奖励。\n" +
// "第七章 法律责任\n" +
// "第八十条 用人单位直接涉及劳动者切身利益的规章制度违反法律、法规规定的,由劳动行政部门责令改正,给予警告;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第八十一条 用人单位提供的劳动合同文本未载明本法规定的劳动合同必备条款或者用人单位未将劳动合同文本交付劳动者的,由劳动行政部门责令改正;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第八十二条 用人单位自用工之日起超过一个月不满一年未与劳动者订立书面劳动合同的,应当向劳动者每月支付二倍的工资。\n" +
// "用人单位违反本法规定不与劳动者订立无固定期限劳动合同的,自应当订立无固定期限劳动合同之日起向劳动者每月支付二倍的工资。\n" +
// "第八十三条 用人单位违反本法规定与劳动者约定试用期的,由劳动行政部门责令改正;违法约定的试用期已经履行的,由用人单位以劳动者试用期满月工资为标准,按已经履行的超过法定试用期的期间向劳动者支付赔偿金。\n" +
// "第八十四条 用人单位违反本法规定,扣押劳动者居民身份证等证件的,由劳动行政部门责令限期退还劳动者本人,并依照有关法律规定给予处罚。\n" +
// "用人单位违反本法规定,以担保或者其他名义向劳动者收取财物的,由劳动行政部门责令限期退还劳动者本人,并以每人五百元以上二千元以下的标准处以罚款;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "劳动者依法解除或者终止劳动合同,用人单位扣押劳动者档案或者其他物品的,依照前款规定处罚。\n" +
// "第八十五条 用人单位有下列情形之一的,由劳动行政部门责令限期支付劳动报酬、加班费或者经济补偿;劳动报酬低于当地最低工资标准的,应当支付其差额部分;逾期不支付的,责令用人单位按应付金额百分之五十以上百分之一百以下的标准向劳动者加付赔偿金:\n" +
// "(一)未按照劳动合同的约定或者国家规定及时足额支付劳动者劳动报酬的;\n" +
// "(二)低于当地最低工资标准支付劳动者工资的;\n" +
// "(三)安排加班不支付加班费的;\n" +
// "(四)解除或者终止劳动合同,未依照本法规定向劳动者支付经济补偿的。\n" +
// "第八十六条 劳动合同依照本法第二十六条规定被确认无效,给对方造成损害的,有过错的一方应当承担赔偿责任。\n" +
// "第八十七条 用人单位违反本法规定解除或者终止劳动合同的,应当依照本法第四十七条规定的经济补偿标准的二倍向劳动者支付赔偿金。\n" +
// "第八十八条 用人单位有下列情形之一的,依法给予行政处罚;构成犯罪的,依法追究刑事责任;给劳动者造成损害的,应当承担赔偿责任:\n" +
// "(一)以暴力、威胁或者非法限制人身自由的手段强迫劳动的;\n" +
// "(二)违章指挥或者强令冒险作业危及劳动者人身安全的;\n" +
// "(三)侮辱、体罚、殴打、非法搜查或者拘禁劳动者的;\n" +
// "(四)劳动条件恶劣、环境污染严重,给劳动者身心健康造成严重损害的。\n" +
// "第八十九条 用人单位违反本法规定未向劳动者出具解除或者终止劳动合同的书面证明,由劳动行政部门责令改正;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第九十条 劳动者违反本法规定解除劳动合同,或者违反劳动合同中约定的保密义务或者竞业限制,给用人单位造成损失的,应当承担赔偿责任。\n" +
// "第九十一条 用人单位招用与其他用人单位尚未解除或者终止劳动合同的劳动者,给其他用人单位造成损失的,应当承担连带赔偿责任。\n" +
// "第九十二条 违反本法规定,未经许可,擅自经营劳务派遣业务的,由劳动行政部门责令停止违法行为,没收违法所得,并处违法所得一倍以上五倍以下的罚款;没有违法所得的,可以处五万元以下的罚款。\n" +
// " 劳务派遣单位、用工单位违反本法有关劳务派遣规定的,由劳动行政部门责令限期改正;逾期不改正的,以每人五千元以上一万元以下的标准处以罚款,对劳务派遣单位,吊销其劳务派遣业务经营许可证。用工单位给被派遣劳动者造成损害的,劳务派遣单位与用工单位承担连带赔偿责任。\n" +
// "第九十三条 对不具备合法经营资格的用人单位的违法犯罪行为,依法追究法律责任;劳动者已经付出劳动的,该单位或者其出资人应当依照本法有关规定向劳动者支付劳动报酬、经济补偿、赔偿金;给劳动者造成损害的,应当承担赔偿责任。\n" +
// "第九十四条 个人承包经营违反本法规定招用劳动者,给劳动者造成损害的,发包的组织与个人承包经营者承担连带赔偿责任。\n" +
// "第九十五条 劳动行政部门和其他有关主管部门及其工作人员玩忽职守、不履行法定职责,或者违法行使职权,给劳动者或者用人单位造成损害的,应当承担赔偿责任;对直接负责的主管人员和其他直接责任人员,依法给予行政处分;构成犯罪的,依法追究刑事责任。\n" +
// "第八章 附则\n" +
// "第九十六条 事业单位与实行聘用制的工作人员订立、履行、变更、解除或者终止劳动合同,法律、行政法规或者国务院另有规定的,依照其规定;未作规定的,依照本法有关规定执行。\n" +
// "第九十七条 本法施行前已依法订立且在本法施行之日存续的劳动合同,继续履行;本法第十四条第二款第三项规定连续订立固定期限劳动合同的次数,自本法施行后续订固定期限劳动合同时开始计算。\n" +
// "本法施行前已建立劳动关系,尚未订立书面劳动合同的,应当自本法施行之日起一个月内订立。\n" +
// "本法施行之日存续的劳动合同在本法施行后解除或者终止,依照本法第四十六条规定应当支付经济补偿的,经济补偿年限自本法施行之日起计算;本法施行前按照当时有关规定,用人单位应当向劳动者支付经济补偿的,按照当时有关规定执行。\n" +
// "第九十八条 本法自2008年1月1日起施行。\n" +
// "\n";
//
//
//}

View File

@ -0,0 +1,97 @@
常见问题
1.pnpm安装依赖报错
错误: node\_modules\\vite\\node\_modules\\esbuild\\esbuild.exe ENOENT
解决方案https://blog.csdn.net/weixin_41760500/article/details/119885574命令node ./node_modules/esbuild/install.js
2.pnpm安装后访问提示缺少依赖

解决方案: https://stackoverflow.com/questions/70597494/pnpm-does-not-resolve-dependencies
3.pnpm install出现错误
错误ERR\_PNPM\_PEER\_DEP\_ISSUES Unmet peer dependencies
http://ms521.cn/index.php/Home/Index/article/aid/271
4.项目安装依赖无问题,访问页面报错
前端部分报错:
[plugin:vite:vue-jsx] Cannot find package 'C:\Users\123\Desktop\JeecgBoot-master\pincone_system\jeecgboot-vue3\node_modules\.pnpm\@vitejs+plugin-vue-jsx@3.1.0_vite@5.4.9_@types+node@20.16.13_less@4.2.0_terser@5.36.0__vue@3.5.12_typescript@4.9.5_\node_modules\@babel\plugin-transform-typescript\lib\index.js' imported from C:\Users\123\Desktop\JeecgBoot-master\pincone_system\jeecgboot-vue3\node_modules\.pnpm\@vitejs+plugin-vue-jsx@3.1.0_vite@5.4.9_@types+node@20.16.13_less@4.2.0_terser@5.36.0__vue@3.5.12_typescript@4.9.5_\node_modules\@vitejs\plugin-vue-jsx\dist\index.cjs Did you mean to import "@babel/plugin-transform-typescript/lib/index.js"? #7396
回答: 是因为项目的路径太长导致 相关问题Issues查看相关博客
• https://blog.csdn.net/weixin_43235500/article/details/142144989
• https://blog.csdn.net/qq_25996219/article/details/140328092
5. 通过npm install启动报错
建议请使用pnpm i 可以避免更多问题
错误情况:

解决:进入提示的路径 \node_modules\vite-plugin-mock\node_modules\esbuild\
执行命令: node install.js再启动就好了
6. 前端刷新进不了登录页面
报错props.ts:15 Uncaught (in promise) SyntaxError: Unexpected token '='
错误截图:
原因:谷歌浏览器版本过低,升级浏览器
比如这边版本就过低了
7.表单如何全部禁用
加上这个属性就可以了

效果

8.table列表如何自定义排序
defSort: {
column: 'id',
order: 'desc',
},
参考示例:
9.idea编写js时爆红提示statement expected
https://blog.csdn.net/mlsama/article/details/80633009
10.抽屉的setDrawerProps不好使值会还原
11. 如何删除不需要的demo制作一个精简版本
精简项目删除demo等非必须功能
12.vue3 暗黑模式下显示不完整
错误示例:
解决方案:
在样式中的字体颜色和背景颜色使用@变量名称来代替
color: @text-color;
background-color: @component-background;
[info] 通用样式变量名称可以在在目录bulid->vite->plugin->themes.ts中找到,darkModifyVars是重写antd中的样式
[info]更多样式变量名称请参考目录node_modules/es/style/themes/default.less

改造完成之后的效果
13.操作列“删除按钮”界面布局异常
相关issue
https://github.com/jeecgboot/jeecgboot-vue3/issues/458
问题截图:
解决方案找到popConfirm填写属性placement: 'left'
placement: 'left',
效果截图
14.在centos7中下载依赖pnpm i时 mozjpeg依赖下载不下来
https://github.com/jeecgboot/jeecgboot-vue3/issues/433#issuecomment-1510470534
15.日期遮挡问题
问题截图:
下拉显示组件,页面滚动时,页面出现错位遮挡问题
解决方案:将组件挂载到父节点上
getPopupContainer: (node) => node.parentNode,
效果截图
16.nextTick作用
在 Vue 3 中nextTick 方法用于在 DOM 更新之后执行回调函数。它的作用是在下次 DOM 更新循环结束后执行一些操作,以确保你在操作更新的 DOM 元素时能够获取到最新的结果。nextTick 方法可以用于以下情况:
1 在更新数据后立即操作 DOM 元素。
2 在更新组件后执行某些逻辑或触发一些副作用。
3 在更新后获取更新后的 DOM 元素的尺寸或位置等信息。
使用nextTick 方法有两种方式1.使用回调函数:
nextTick(()=>
//在DOM更新后执行的操作
}):
2.使用Promise:
nextTick().then(()=>
//在DOM更新后执行的操作
})
无论使用哪种方式传入的回调函数或Promisel回调都会在下一次DOM更新周期之后被调用。这样可以确保在数据变化后Vue已经完成了相应的DOM更新。
需要注意的是nextTick 方法是异步执行的因此不能保证回调函数会立即执行。如果需要等待nextTick执行完成可以使用await关键字或者. then()方法来等待Promise的完成。
17. 一个页面多个表格,列的展示会互相影响
[info] 相关issue
https://github.com/jeecgboot/jeecgboot-vue3/issues/1064
[info]问题截图
[info] 解决方案在不同的tableProps下设置不同的checkKey即可解决
tableSetting: { cacheKey: 'depart_user_departInfo' },
18. 通过npm install启动报错

可以使用这个命令:
npm install --ignore-scripts

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jeecg-boot-module</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-module-demo</artifactId>
<dependencies>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,68 @@
//package org.jeecg.modules.demo.cloud.controller;
//
//import com.alibaba.csp.sentinel.annotation.SentinelResource;
//import io.swagger.annotations.Api;
//import io.swagger.annotations.ApiOperation;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.common.api.vo.Result;
//import org.jeecg.common.system.api.ISysBaseAPI;
//import org.jeecg.common.system.vo.DictModel;
//import org.springframework.beans.factory.annotation.Autowired;
//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.util.List;
//
///**
// *
// */
//@Slf4j
//@Api(tags = "【微服务】单元测试")
//@RestController
//@RequestMapping("/test")
//public class JcloudDemoFeignController {
// @Resource
// private ISysBaseAPI sysBaseApi;
//// @Autowired
//// private ErpHelloApi erpHelloApi;
//
// /**
// * 测试
// *
// * @return
// */
// @GetMapping("/callSystem")
// //@SentinelResource(value = "remoteDict",fallback = "getDefaultHandler")
// @ApiOperation(value = "通过feign调用system服务", notes = "测试jeecg-demo服务是否通过fegin调用system服务接口")
// public Result getRemoteDict() {
// List<DictModel> list = sysBaseApi.queryAllDict();
// return Result.OK(list);
// }
//
//
//// /**
//// * 测试调用 erp 微服务接口
//// * 【如何测试通过archetype生成微服务模块快速集成测试】
//// * https://help.jeecg.com/java/springcloud/archetype.html
//// * @return
//// */
//// @GetMapping("/callErp")
//// @ApiOperation(value = "测试feign erp", notes = "测试feign erp")
//// public Result callErp() {
//// log.info("call erp 服务");
//// String res = erpHelloApi.callHello();
//// return Result.OK(res);
//// }
//
// /**
// * 熔断,默认回调函数
// *
// * @return
// */
// public Result<Object> getDefaultHandler() {
// log.info("测试JcloudDemoController-remoteDict 熔断降级");
// return Result.error("测试JcloudDemoController-remoteDict 熔断降级");
// }
//
//}

View File

@ -0,0 +1,33 @@
package org.jeecg.modules.demo.cloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.demo.cloud.service.JcloudDemoService;
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 jakarta.annotation.Resource;
/**
* 服务端提供方——feign接口
* 【提供给system-start调用测试看feign是否畅通】
* @author: jeecg-boot
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class JcloudDemoProviderController {
@Resource
private JcloudDemoService jcloudDemoService;
@GetMapping("/getMessage")
public String getMessage(@RequestParam(name = "name") String name) {
String msg = jcloudDemoService.getMessage(name);
log.info(" 微服务被调用:{} ",msg);
return msg;
}
}

View File

@ -0,0 +1,17 @@
package org.jeecg.modules.demo.cloud.service;
import org.jeecg.common.api.vo.Result;
/**
* @Description: JcloudDemoService接口
* @author: jeecg-boot
*/
public interface JcloudDemoService {
/**
* 获取信息(测试)
* @param name 姓名
* @return "Hello" + name
*/
String getMessage(String name);
}

View File

@ -0,0 +1,18 @@
package org.jeecg.modules.demo.cloud.service.impl;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.demo.cloud.service.JcloudDemoService;
import org.springframework.stereotype.Service;
/**
* @Description: JcloudDemoServiceImpl实现类
* @author: jeecg-boot
*/
@Service
public class JcloudDemoServiceImpl implements JcloudDemoService {
@Override
public String getMessage(String name) {
String resMsg = "Hello我是jeecg-demo服务节点收到你的消息"+ name +"";
return resMsg;
}
}

View File

@ -0,0 +1,74 @@
//
//package org.jeecg.modules.demo.cloud.xxljob;
//
//import com.xxl.job.core.biz.model.ReturnT;
//import com.xxl.job.core.handler.annotation.XxlJob;
//import lombok.extern.slf4j.Slf4j;
//import org.jeecg.common.config.mqtoken.UserTokenContext;
//import org.jeecg.common.constant.CommonConstant;
//import org.jeecg.common.system.api.ISysBaseAPI;
//import org.jeecg.common.system.util.JwtUtil;
//import org.jeecg.common.util.RedisUtil;
//import org.jeecg.common.util.SpringContextUtils;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Component;
//
//
///**
// * xxl-job定时任务测试
// */
//@Slf4j
//@Component
//public class TestJobHandler {
// @Autowired
// ISysBaseAPI sysBaseApi;
//
// /**
// * 简单任务
// *
// * 测试无token调用feign接口
// *
// * @param params
// * @return
// */
//
// @XxlJob(value = "testJob")
// public ReturnT<String> demoJobHandler(String params) {
// //1.生成临时令牌Token到线程中
// UserTokenContext.setToken(getTemporaryToken());
//
// log.info("我是 jeecg-demo 服务里的定时任务 testJob , 我执行了...............................");
// log.info("我调用 jeecg-system 服务的字典接口:{}",sysBaseApi.queryAllDict());
// //。。。此处可以写多个feign接口调用
//
// //2.使用完删除临时令牌Token
// UserTokenContext.remove();
// return ReturnT.SUCCESS;
// }
//
// public void init() {
// log.info("init");
// }
//
// public void destroy() {
// log.info("destory");
// }
//
// /**
// * 获取临时令牌
// *
// * 模拟登陆接口,获取模拟 Token
// * @return
// */
// public static String getTemporaryToken() {
// RedisUtil redisUtil = SpringContextUtils.getBean(RedisUtil.class);
// // 模拟登录生成Token
// String token = JwtUtil.sign("??", "??");
// // 设置Token缓存有效时间为 5 分钟
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, 5 * 60 * 1000);
// return token;
// }
//
//}
//

View File

@ -0,0 +1,95 @@
package org.jeecg.modules.demo.gpt.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.chatgpt.service.AiChatService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
//update-begin---author:chenrui ---date:20240110 for[QQYUN-5509]AI生成表结构和软文------------
/**
* @Description: chatGpt接口
* @Author: chenrui
* @Date: 2024/1/9 16:30
*/
@Tag(name = "AI接口")
@RestController
@RequestMapping("/test/ai")
@Slf4j
public class AiController {
private static final String CACHE_PREFIX = "ai:resp:";
@Autowired
AiChatService aiChatService;
/**
* 通过AI生成模块表设计
* @param descr 描述
* @return
* @author chenrui
* @date 2024/1/9 20:12
*/
@AutoLog(value = "通过AI生成模块表设计")
@PostMapping(value = "/gen/schema/modules")
@Operation(summary = "通过AI生成模块表设计")
public Result<String> genSchemaModules(@RequestParam(name = "prompt", required = true) String prompt) {
String result = aiChatService.genSchemaModules(prompt);
return Result.ok(result);
}
/**
* 通过AI生成软文
* @param descr 描述
* @return
* @author chenrui
* @date 2024/1/9 20:12
*/
@AutoLog(value = "通过AI生成软文")
@PostMapping(value = "/gen/article")
@Operation(summary = "通过AI生成软文")
public Result<String> genArticle(@RequestParam(name = "prompt", required = true) String prompt) {
String result = aiChatService.genArticleWithMd(prompt);
return Result.ok(result);
}
/**
* 向AI提问
* @param message
* @return
* @author chenrui
* @date 2024/1/15 19:11
*/
@AutoLog(value = "向AI提问")
@PostMapping(value = "/completions")
@Operation(summary = "向AI提问")
public Result<?> completions(@RequestParam(name = "message", required = true) String message) {
String result = aiChatService.completions(message);
return Result.ok(result);
}
/**
* 让AI生成图片
* @param prompt
* @return
* @author chenrui
* @date 2024/1/15 19:11
*/
@AutoLog(value = "让AI生成图片")
@PostMapping(value = "/gen/image")
@Operation(summary = "让AI生成图片")
public Result<?> genImage(@RequestParam(name = "prompt", required = true) String prompt) {
String result = aiChatService.imageGenerate(prompt);
return Result.ok(result);
}
}
//update-end---author:chenrui ---date:20240110 for//update-begin---author:chenrui ---date:20240110 for[QQYUN-5509]AI生成表结构和软文------------------------

View File

@ -0,0 +1,220 @@
package org.jeecg.modules.demo.mock;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.jeecg.common.api.vo.Result;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description: MockController
* @author: jeecg-boot
*/
@RestController
@RequestMapping("/mock/api")
@Slf4j
public class MockController {
private final String JSON_PATH = "classpath:org/jeecg/modules/demo/mock/json";
/**
* 通用json访问接口
* 格式: http://localhost:8080/jeecg-boot/api/json/{filename}
* @param filename
* @return
*/
@RequestMapping(value = "/json/{filename}", method = RequestMethod.GET)
public String getJsonData(@PathVariable("filename") String filename) {
String jsonpath = "classpath:org/jeecg/modules/demo/mock/json/"+filename+".json";
return readJson(jsonpath);
}
@GetMapping(value = "/asynTreeList")
public Result asynTreeList(String id) {
String json = readJson(JSON_PATH + "/asyn_tree_list_" + id + ".json");
return Result.OK(JSON.parseArray(json));
}
@GetMapping(value = "/user")
public String user() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/user.json");
}
/**
* 老的登录获取用户信息接口
* @return
*/
@GetMapping(value = "/user/info")
public String userInfo() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/user_info.json");
}
@GetMapping(value = "/role")
public String role() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/role.json");
}
@GetMapping(value = "/service")
public String service() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/service.json");
}
@GetMapping(value = "/permission")
public String permission() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/permission.json");
}
@GetMapping(value = "/permission/no-pager")
public String permissionNoPage() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/permission_no_page.json");
}
/**
* 省市县
*/
@GetMapping(value = "/area")
public String area() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/area.json");
}
/**
* 测试报表数据
*/
@GetMapping(value = "/report/getYearCountInfo")
public String getYearCountInfo() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
}
@GetMapping(value = "/report/getMonthCountInfo")
public String getMonthCountInfo() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
}
@GetMapping(value = "/report/getCntrNoCountInfo")
public String getCntrNoCountInfo() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
}
@GetMapping(value = "/report/getCabinetCountInfo")
public String getCabinetCountInfo() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/getCntrNoCountInfo.json");
}
@GetMapping(value = "/report/getTubiao")
public String getTubiao() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/getTubiao.json");
}
/**
* 实时磁盘监控
* @param request
* @param response
* @return
*/
@GetMapping("/queryDiskInfo")
public Result<List<Map<String,Object>>> queryDiskInfo(HttpServletRequest request, HttpServletResponse response){
Result<List<Map<String,Object>>> res = new Result<>();
try {
// 当前文件系统类
FileSystemView fsv = FileSystemView.getFileSystemView();
// 列出所有windows 磁盘
File[] fs = File.listRoots();
log.info("查询磁盘信息:"+fs.length+"");
List<Map<String,Object>> list = new ArrayList<>();
for (int i = 0; i < fs.length; i++) {
if(fs[i].getTotalSpace()==0) {
continue;
}
Map<String,Object> map = new HashMap<>(5);
map.put("name", fsv.getSystemDisplayName(fs[i]));
map.put("max", fs[i].getTotalSpace());
map.put("rest", fs[i].getFreeSpace());
map.put("restPPT", fs[i].getFreeSpace()*100/fs[i].getTotalSpace());
list.add(map);
log.info(map.toString());
}
res.setResult(list);
res.success("查询成功");
} catch (Exception e) {
res.error500("查询失败"+e.getMessage());
}
return res;
}
//-------------------------------------------------------------------------------------------
/**
* 工作台首页的数据
* @return
*/
@GetMapping(value = "/list/search/projects")
public String projects() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_projects.json");
}
@GetMapping(value = "/workplace/activity")
public String activity() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_activity.json");
}
@GetMapping(value = "/workplace/teams")
public String teams() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_teams.json");
}
@GetMapping(value = "/workplace/radar")
public String radar() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/workplace_radar.json");
}
@GetMapping(value = "/task/process")
public String taskProcess() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/task_process.json");
}
//-------------------------------------------------------------------------------------------
//author:lvdandan-----date20190315---for:添加数据日志json----
/**
* 数据日志
*/
public String sysDataLogJson() {
return readJson("classpath:org/jeecg/modules/demo/mock/json/sysdatalog.json");
}
//author:lvdandan-----date20190315---for:添加数据日志json----
//--update-begin--author:wangshuai-----date20201023---for:返回用户信息json数据----
/**
* 用户信息
*/
@GetMapping(value = "/getUserInfo")
public String getUserInfo(){
return readJson("classpath:org/jeecg/modules/demo/mock/json/userinfo.json");
}
//--update-end--author:wangshuai-----date20201023---for:返回用户信息json数据----
/**
* 读取json格式文件
* @param jsonSrc
* @return
*/
private String readJson(String jsonSrc) {
String json = "";
try {
//File jsonFile = ResourceUtils.getFile(jsonSrc);
//json = FileUtils.re.readFileToString(jsonFile);
//换个写法解决springboot读取jar包中文件的问题
InputStream stream = getClass().getClassLoader().getResourceAsStream(jsonSrc.replace("classpath:", ""));
json = IOUtils.toString(stream,"UTF-8");
} catch (IOException e) {
log.error(e.getMessage(),e);
}
return json;
}
}

View File

@ -0,0 +1,36 @@
[
{"value": "110000", "label": "北京市"},
{"value": "120000", "label": "天津市"},
{"value": "130000", "label": "河北省"},
{"value": "140000", "label": "山西省"},
{"value": "150000", "label": "内蒙古自治区"},
{"value": "210000", "label": "辽宁省"},
{"value": "220000", "label": "吉林省"},
{"value": "230000", "label": "黑龙江省"},
{"value": "310000", "label": "上海市"},
{"value": "320000", "label": "江苏省"},
{"value": "330000", "label": "浙江省"},
{"value": "340000", "label": "安徽省"},
{"value": "350000", "label": "福建省"},
{"value": "360000", "label": "江西省"},
{"value": "370000", "label": "山东省"},
{"value": "410000", "label": "河南省"},
{"value": "420000", "label": "湖北省"},
{"value": "430000", "label": "湖南省"},
{"value": "440000", "label": "广东省"},
{"value": "450000", "label": "广西壮族自治区"},
{"value": "460000", "label": "海南省"},
{"value": "500000", "label": "重庆市"},
{"value": "510000", "label": "四川省"},
{"value": "520000", "label": "贵州省"},
{"value": "530000", "label": "云南省"},
{"value": "540000", "label": "西藏自治区"},
{"value": "610000", "label": "陕西省"},
{"value": "620000", "label": "甘肃省"},
{"value": "630000", "label": "青海省"},
{"value": "640000", "label": "宁夏回族自治区"},
{"value": "650000", "label": "新疆维吾尔自治区"},
{"value": "710000", "label": "台湾省"},
{"value": "810000", "label": "香港特别行政区"},
{"value": "820000", "label": "澳门特别行政区"}
]

View File

@ -0,0 +1,23 @@
[
{
"id": 1,
"name": "首页",
"component": "dashboard/Analysis",
"orderNum": 1,
"hasChildren": false
},
{
"id": 2,
"name": "常见案例",
"component": "layouts/RouteView",
"orderNum": 2,
"hasChildren": true
},
{
"id": 3,
"name": "系统监控",
"component": "layouts/RouteView",
"orderNum": 3,
"hasChildren": true
}
]

View File

@ -0,0 +1,23 @@
[
{
"id": 11,
"name": "首页",
"component": "dashboard/Analysis",
"orderNum": 1,
"hasChildren": false
},
{
"id": 12,
"name": "系统管理",
"component": "layouts/RouteView",
"orderNum": 2,
"hasChildren": true
},
{
"id": 13,
"name": "常见案例",
"component": "layouts/RouteView",
"orderNum": 3,
"hasChildren": true
}
]

View File

@ -0,0 +1,23 @@
[
{
"id": 21,
"name": "弹框选择Demo",
"component": "jeecg/SelectDemo",
"orderNum": 1,
"hasChildren": false
},
{
"id": 22,
"name": "单表模型示例",
"component": "jeecg/JeecgDemoList",
"orderNum": 2,
"hasChildren": false
},
{
"id": 23,
"name": "一对多Tab示例",
"component": "jeecg/tablist/JeecgOrderDMainList",
"orderNum": 3,
"hasChildren": false
}
]

View File

@ -0,0 +1,23 @@
[
{
"id": 31,
"name": "性能监控",
"component": "layouts/RouteView",
"orderNum": 1,
"hasChildren": true
},
{
"id": 32,
"name": "在线文档",
"component": "layouts/IframePageView",
"orderNum": 2,
"hasChildren": false
},
{
"id": 33,
"name": "工作台",
"component": "dashboard/Workplace",
"orderNum": 3,
"hasChildren": false
}
]

View File

@ -0,0 +1,23 @@
[
{
"id": 311,
"name": "Redis监控",
"component": "modules/monitor/RedisInfo",
"orderNum": 1,
"hasChildren": false
},
{
"id": 312,
"name": "JVM信息",
"component": "modules/monitor/JvmInfo",
"orderNum": 2,
"hasChildren": false
},
{
"id": 313,
"name": "Tomcat信息",
"component": "modules/monitor/TomcatInfo",
"orderNum": 3,
"hasChildren": false
}
]

View File

@ -0,0 +1,63 @@
{
"success": true,
"message": "查询成功",
"code": null,
"result": [
{
"resultIndex": 0,
"yearcount": 623,
"year": 2016,
"month": "四月",
"monthcount": 3255,
"classifyname": "证明类",
"cntrnocount": 24,
"cabinetname": "一号柜",
"cabinetcocunt": 12
},
{
"resultIndex": 1,
"yearcount": 243,
"year": 2017,
"month": "五月",
"monthcount": 5673,
"classifyname": "产权类",
"cntrnocount": 52,
"cabinetname": "二号柜",
"cabinetcocunt": 52
},
{
"resultIndex": 2,
"yearcount": 345,
"year": 2018,
"month": "六月",
"monthcount": 2673,
"classifyname": "知识类",
"cntrnocount": 85,
"cabinetname": "三号柜",
"cabinetcocunt": 24
},
{
"resultIndex": 3,
"yearcount": 452,
"year": 2019,
"month": "七月",
"monthcount": 2341,
"classifyname": "技术类",
"cntrnocount": 67,
"cabinetname": "四号柜",
"cabinetcocunt": 45
},
{
"resultIndex": 4,
"yearcount": 645,
"year": 2020,
"month": "八月",
"monthcount": 7473,
"classifyname": "工具类",
"cntrnocount": 93,
"cabinetname": "五号柜",
"cabinetcocunt": 94
}
],
"timestamp": 1554285003594
}

View File

@ -0,0 +1,10 @@
{"data":
[
{"day": "星期一", "step": 1234, "assess": "良"},
{"day": "星期二", "step": 1884, "assess": "优"},
{"day": "星期三", "step": 1671, "assess": "良+"},
{"day": "星期四", "step": 2197, "assess": "优+"},
{"day": "星期五", "step": 1342, "assess": "中"},
{"day": "星期六", "step": 545, "assess": "差"},
{"day": "星期日", "step": 244, "assess": "极差"}
]}

View File

@ -0,0 +1,50 @@
{
"status": 200,
"success": true,
"message": "ok",
"result": {
"data": [
{
"id": 0,
"x": "1",
"y": 889
},
{
"id": 1,
"x": "2",
"y": 341
},
{
"id": 2,
"x": "3",
"y": 1028
},
{
"id": 3,
"x": "4",
"y": 1168
},
{
"id": 4,
"x": "5",
"y": 653
},
{
"id": 5,
"x": "6",
"y": 863
},
{
"id": 6,
"x": "7",
"y": 421
},
{
"id": 7,
"x": "8",
"y": 1320
}
]
},
"timestamp": 1554950583837
}

View File

@ -0,0 +1,156 @@
{
"message": "",
"result": {
"data": [
{
"id": "marketing",
"name": "营销管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": null,
"parents": null,
"type": null,
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "member",
"name": "会员管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "menu",
"name": "菜单管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"import",
"get",
"update"
]
},
{
"id": "order",
"name": "订单管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "permission",
"name": "权限管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get",
"update",
"delete"
]
},
{
"id": "role",
"name": "角色管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get",
"update",
"delete"
]
},
{
"id": "test",
"name": "测试权限",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get"
]
},
{
"id": "user",
"name": "用户管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"describe\":\"新增\",\"defaultCheck\":false},{\"action\":\"get\",\"describe\":\"查询\",\"defaultCheck\":false}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get"
]
}
],
"pageSize": 10,
"pageNo": 0,
"totalPage": 1,
"totalCount": 5
},
"status": 200,
"timestamp": 1537082021471
}

View File

@ -0,0 +1,150 @@
{
"message": "",
"result": [
{
"id": "marketing",
"name": "营销管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": null,
"parents": null,
"type": null,
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "member",
"name": "会员管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "menu",
"name": "菜单管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"import",
"get",
"update"
]
},
{
"id": "order",
"name": "订单管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"query",
"get",
"update",
"delete"
]
},
{
"id": "permission",
"name": "权限管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get",
"update",
"delete"
]
},
{
"id": "role",
"name": "角色管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get",
"update",
"delete"
]
},
{
"id": "test",
"name": "测试权限",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get"
]
},
{
"id": "user",
"name": "用户管理",
"describe": null,
"status": 1,
"actionData": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"export\",\"defaultCheck\":false,\"describe\":\"导出\"}]",
"sptDaTypes": null,
"optionalFields": "[]",
"parents": null,
"type": "default",
"deleted": 0,
"actions": [
"add",
"get"
]
}
],
"status": 200,
"timestamp": 1537082021471
}

View File

@ -0,0 +1,608 @@
{
"message": "",
"result": {
"data": [
{
"id": "admin",
"name": "管理员",
"describe": "拥有所有权限",
"status": 1,
"creatorId": "system",
"createTime": 1497160610259,
"deleted": 0,
"permissions": [
{
"roleId": "admin",
"permissionId": "comment",
"permissionName": "评论管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "member",
"permissionName": "会员管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "menu",
"permissionName": "菜单管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "order",
"permissionName": "订单管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "permission",
"permissionName": "权限管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "role",
"permissionName": "角色管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "test",
"permissionName": "测试权限",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "user",
"permissionName": "用户管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"},{\"action\":\"export\",\"defaultCheck\":false,\"describe\":\"导出\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
},
{
"action": "export",
"describe": "导出",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
}
]
},
{
"id": "svip",
"name": "SVIP",
"describe": "超级会员",
"status": 1,
"creatorId": "system",
"createTime": 1532417744846,
"deleted": 0,
"permissions": [
{
"roleId": "admin",
"permissionId": "comment",
"permissionName": "评论管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "member",
"permissionName": "会员管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "menu",
"permissionName": "菜单管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "order",
"permissionName": "订单管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "permission",
"permissionName": "权限管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "role",
"permissionName": "角色管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "test",
"permissionName": "测试权限",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "user",
"permissionName": "用户管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"},{\"action\":\"export\",\"defaultCheck\":false,\"describe\":\"导出\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
}
]
},
{
"id": "user",
"name": "普通会员",
"describe": "普通用户,只能查询",
"status": 1,
"creatorId": "system",
"createTime": 1497160610259,
"deleted": 0,
"permissions": [
{
"roleId": "user",
"permissionId": "comment",
"permissionName": "评论管理",
"actions": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"}]",
"actionEntitySet": [
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "marketing",
"permissionName": "营销管理",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "member",
"permissionName": "会员管理",
"actions": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"}]",
"actionEntitySet": [
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "menu",
"permissionName": "菜单管理",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "order",
"permissionName": "订单管理",
"actions": "[{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"}]",
"actionEntitySet": [
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "permission",
"permissionName": "权限管理",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "role",
"permissionName": "角色管理",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "test",
"permissionName": "测试权限",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
},
{
"roleId": "user",
"permissionId": "user",
"permissionName": "用户管理",
"actions": "[]",
"actionEntitySet": [],
"actionList": null,
"dataAccess": null
}
]
}
],
"pageSize": 10,
"pageNo": 0,
"totalPage": 1,
"totalCount": 5
},
"status": 200,
"timestamp": 1537079497645
}

View File

@ -0,0 +1,103 @@
{
"message": "",
"result": {
"pageSize": 10,
"pageNo": 0,
"totalCount": 57,
"totalPage": 6,
"data": [
{
"key": 1,
"no": "No 1",
"description": "这是一段描述",
"callNo": 127,
"status": 2,
"updatedAt": "1970-06-24 11:51:20",
"editable": false
},
{
"key": 2,
"no": "No 2",
"description": "这是一段描述",
"callNo": 573,
"status": 2,
"updatedAt": "1994-12-11 00:37:35",
"editable": false
},
{
"key": 3,
"no": "No 3",
"description": "这是一段描述",
"callNo": 869,
"status": 2,
"updatedAt": "2013-11-11 08:04:03",
"editable": false
},
{
"key": 4,
"no": "No 4",
"description": "这是一段描述",
"callNo": 26,
"status": 2,
"updatedAt": "1990-11-04 15:41:42",
"editable": false
},
{
"key": 5,
"no": "No 5",
"description": "这是一段描述",
"callNo": 20,
"status": 2,
"updatedAt": "1970-01-05 11:04:56",
"editable": false
},
{
"key": 6,
"no": "No 6",
"description": "这是一段描述",
"callNo": 675,
"status": 2,
"updatedAt": "1983-06-06 04:09:04",
"editable": false
},
{
"key": 7,
"no": "No 7",
"description": "这是一段描述",
"callNo": 512,
"status": 3,
"updatedAt": "1996-08-26 21:47:44",
"editable": false
},
{
"key": 8,
"no": "No 8",
"description": "这是一段描述",
"callNo": 962,
"status": 2,
"updatedAt": "2004-08-15 23:15:22",
"editable": false
},
{
"key": 9,
"no": "No 9",
"description": "这是一段描述",
"callNo": 318,
"status": 3,
"updatedAt": "1988-08-10 14:36:35",
"editable": false
},
{
"key": 10,
"no": "No 10",
"description": "这是一段描述",
"callNo": 789,
"status": 0,
"updatedAt": "1988-12-27 23:39:41",
"editable": false
}
]
},
"status": 200,
"timestamp": 1534955098193
}

View File

@ -0,0 +1,12 @@
{
"mobilePhone":"1872222222",
"officePhone":"1222222",
"email":"",
"createDate":"Jun 23, 2016 12:00:00 PM",
"sex":"1",
"depId":"402880e447e99cf10147e9a03b320003",
"userName":"9001",
"status":"1",
"content":"111",
"id":"4028ef81550c1a7901550c1cd6e70001"
}

View File

@ -0,0 +1,55 @@
{
"success": true,
"message": "操作成功!",
"code": 0,
"result": {
"records": [
{
"taskId": "48701",
"name": "start",
"taskBeginTime": "2019-03-07 09:33:04",
"taskEndTime": "2019-03-08 04:03:01",
"principal": "测试体验账号",
"result": "已完成"
},
{
"taskId": "48702",
"name": "部门领导审批",
"taskBeginTime": "2019-03-07 09:33:04",
"taskEndTime": "2019-03-08 04:03:01",
"principal": "测试体验账号",
"result": "已完成"
},
{
"taskId": "48703",
"name": "调整申请",
"taskBeginTime": "2019-03-07 09:33:04",
"taskEndTime": "2019-03-08 04:03:01",
"principal": "测试体验账号",
"result": "已完成"
},
{
"taskId": "48704",
"name": "人事审批",
"taskBeginTime": "2019-03-07 09:33:04",
"taskEndTime": "2019-03-08 04:03:01",
"principal": "测试体验账号",
"result": "已完成"
},
{
"taskId": "48705",
"name": "end",
"taskBeginTime": "2019-03-07 09:33:04",
"taskEndTime": "2019-03-08 04:03:01",
"principal": "测试体验账号",
"result": "已完成"
}
],
"total": 0,
"size": 10,
"current": 1,
"searchCount": true,
"pages": 0
},
"timestamp": 1551922394641
}

View File

@ -0,0 +1,407 @@
{
"message": "",
"result": {
"id": "4291d7da9005377ec9aec4a71ea837f",
"name": "天野远子",
"username": "admin",
"password": "",
"avatar": "/avatar2.jpg",
"status": 1,
"telephone": "",
"lastLoginIp": "27.154.74.117",
"lastLoginTime": 1534837621348,
"creatorId": "admin",
"createTime": 1497160610259,
"merchantCode": "TLif2btpzg079h15bk",
"deleted": 0,
"roleId": "admin",
"role": {
"id": "admin",
"name": "管理员",
"describe": "拥有所有权限",
"status": 1,
"creatorId": "system",
"createTime": 1497160610259,
"deleted": 0,
"permissions": [
{
"roleId": "admin",
"permissionId": "dashboard",
"permissionName": "仪表盘",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "exception",
"permissionName": "异常页面权限",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "result",
"permissionName": "结果权限",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "profile",
"permissionName": "详细页权限",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "table",
"permissionName": "表格权限",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "form",
"permissionName": "表单权限",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "order",
"permissionName": "订单管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "permission",
"permissionName": "权限管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "role",
"permissionName": "角色管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "table",
"permissionName": "桌子管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"query\",\"defaultCheck\":false,\"describe\":\"查询\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "query",
"describe": "查询",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
},
{
"roleId": "admin",
"permissionId": "user",
"permissionName": "用户管理",
"actions": "[{\"action\":\"add\",\"defaultCheck\":false,\"describe\":\"新增\"},{\"action\":\"import\",\"defaultCheck\":false,\"describe\":\"导入\"},{\"action\":\"get\",\"defaultCheck\":false,\"describe\":\"详情\"},{\"action\":\"update\",\"defaultCheck\":false,\"describe\":\"修改\"},{\"action\":\"delete\",\"defaultCheck\":false,\"describe\":\"删除\"},{\"action\":\"export\",\"defaultCheck\":false,\"describe\":\"导出\"}]",
"actionEntitySet": [
{
"action": "add",
"describe": "新增",
"defaultCheck": false
},
{
"action": "import",
"describe": "导入",
"defaultCheck": false
},
{
"action": "get",
"describe": "详情",
"defaultCheck": false
},
{
"action": "update",
"describe": "修改",
"defaultCheck": false
},
{
"action": "delete",
"describe": "删除",
"defaultCheck": false
},
{
"action": "export",
"describe": "导出",
"defaultCheck": false
}
],
"actionList": null,
"dataAccess": null
}
]
}
},
"status": 200,
"timestamp": 1534844188679
}

View File

@ -0,0 +1,40 @@
{
"data": [
{
"department": "技术部",
"post": "主管",
"data": "2020年6月25日",
"name": "张三",
"sex": "男",
"birth": "2020年6月25日",
"political": "党员",
"office": "法院",
"nation": "汉",
"health": "良好",
"register": "农民",
"education": "本科",
"major": "计算机",
"gdata": "2020年6月25日",
"mailbox": "123@qq.com",
"telphone": "18034569685",
"homephone": "",
"pworktime": "669633555222",
"entrytime": "2020年6月25日",
"school": "北京邮电大学",
"iDCard": "523698541123333",
"party": "2020年6月25日 上海",
"marital": "已婚",
"children": "有",
"hukoustreet": "北京市朝阳区亚运村街道",
"hukounum": "050000",
"hukoudi": "北京市朝阳区亚运村亚运村小区19号楼7单元901",
"Currentdi": "北京市朝阳区亚运村亚运村小区19号楼7单元901",
"Currentnum": "050000",
"socialsecurity": "是",
"providentfund": "是",
"hobby": "看书",
"sbtype": "城镇社保",
"archivesdi": "北京市朝阳区社保局"
}
]
}

View File

@ -0,0 +1,85 @@
{
"message": "",
"result": [
{
"id": 1,
"user": {
"nickname": "Barbara Lee",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"
},
"project": {
"name": "白鹭酱油开发组",
"action": "更新",
"event": "番组计划"
},
"time": "2018-08-23 14:47:00"
},
{
"id": 1,
"user": {
"nickname": "蓝莓酱",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png"
},
"project": {
"name": "白鹭酱油开发组",
"action": "更新",
"event": "番组计划"
},
"time": "2018-08-23 09:35:37"
},
{
"id": 1,
"user": {
"nickname": "Brian Young",
"avatar": "http://dummyimage.com/64x64"
},
"project": {
"name": "白鹭酱油开发组",
"action": "创建",
"event": "番组计划"
},
"time": "2017-05-27 00:00:00"
},
{
"id": 1,
"user": {
"nickname": "曲丽丽",
"avatar": "http://dummyimage.com/64x64"
},
"project": {
"name": "高逼格设计天团",
"action": "更新",
"event": "六月迭代"
},
"time": "2018-08-23 14:47:00"
},
{
"id": 1,
"user": {
"nickname": "Dorothy Thompson",
"avatar": "http://dummyimage.com/64x64"
},
"project": {
"name": "高逼格设计天团",
"action": "created",
"event": "六月迭代"
},
"time": "2018-08-23 14:47:00"
},
{
"id": 1,
"user": {
"nickname": "曲丽丽",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"
},
"project": {
"name": "高逼格设计天团",
"action": "created",
"event": "六月迭代"
},
"time": "2018-08-23 14:47:00"
}
],
"status": 200,
"timestamp": 0
}

View File

@ -0,0 +1,61 @@
{
"message": "",
"result": {
"data": [
{
"id": 1,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png",
"title": "Alipay",
"description": "那是一种内在的东西, 他们到达不了,也无法触及的",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
},
{
"id": 2,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png",
"title": "Angular",
"description": "希望是一个好东西,也许是最好的,好东西是不会消亡的",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
},
{
"id": 3,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png",
"title": "Ant Design",
"description": "城镇中有那么多的酒馆,她却偏偏走进了我的酒馆",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
},
{
"id": 4,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png",
"title": "Ant Design Pro",
"description": "那时候我只会想自己想要什么,从不想自己拥有什么",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
},
{
"id": 5,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png",
"title": "Bootstrap",
"description": "凛冬将至",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
},
{
"id": 6,
"cover": "https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png",
"title": "Vue",
"description": "生命就像一盒巧克力,结果往往出人意料",
"status": 1,
"updatedAt": "2018-07-26 00:00:00"
}
],
"pageSize": 10,
"pageNo": 1,
"totalPage": 6,
"totalCount": 57
},
"status": 200,
"timestamp": 1534955098193
}

View File

@ -0,0 +1,43 @@
{
"message": "",
"result": [
{
"item": "引用",
"个人": 70,
"团队": 30,
"部门": 40
},
{
"item": "口碑",
"个人": 60,
"团队": 70,
"部门": 40
},
{
"item": "产量",
"个人": 50,
"团队": 60,
"部门": 40
},
{
"item": "贡献",
"个人": 40,
"团队": 50,
"部门": 40
},
{
"item": "热度",
"个人": 60,
"团队": 70,
"部门": 40
},
{
"item": "引用",
"个人": 70,
"团队": 50,
"部门": 40
}
],
"status": 200,
"timestamp": 1534955098193
}

View File

@ -0,0 +1,32 @@
{
"message": "",
"result": [
{
"id": 1,
"name": "科学搬砖组",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"
},
{
"id": 2,
"name": "程序员日常",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png"
},
{
"id": 1,
"name": "设计天团",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png"
},
{
"id": 1,
"name": "中二少女团",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png"
},
{
"id": 1,
"name": "骗你学计算机",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png"
}
],
"status": 200,
"timestamp": 0
}

View File

@ -0,0 +1,414 @@
package org.jeecg.modules.demo.mock.vxe.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.VxeSocketConst;
import org.jeecg.common.system.query.MatchTypeEnum;
import org.jeecg.common.system.query.QueryCondition;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.demo.mock.vxe.entity.MockEntity;
import org.jeecg.modules.demo.mock.vxe.websocket.VxeSocket;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.*;
/**
* @Description: VxeMockController
* @author: jeecg-boot
*/
@RestController
@RequestMapping("/mock/vxe")
@Slf4j
public class VxeMockController {
/**
* 模拟更改状态
*
* @param id
* @param status
* @return
*/
@GetMapping("/change1")
public Result mockChange1(@RequestParam("id") String id, @RequestParam("status") String status) {
/* id 为 行的idrowId只要获取到rowId那么只需要调用 VXESocket.sendMessageToAll() 即可 */
// 封装行数据
JSONObject rowData = new JSONObject();
// 这个字段就是要更改的行数据ID
rowData.put("id", id);
// 这个字段就是要更改的列的key和具体的值
rowData.put("status", status);
// 模拟更改数据
this.mockChange(rowData);
return Result.ok();
}
/**
* 模拟更改拖轮状态
*
* @param id
* @param tug_status
* @return
*/
@Deprecated
@GetMapping("/change2")
public Result mockChange2(@RequestParam("id") String id,@RequestParam("tug_status") String tugStatus) {
/* id 为 行的idrowId只要获取到rowId那么只需要调用 VXESocket.sendMessageToAll() 即可 */
// 封装行数据
JSONObject rowData = new JSONObject();
// 这个字段就是要更改的行数据ID
rowData.put("id", id);
// 这个字段就是要更改的列的key和具体的值
JSONObject status = JSON.parseObject(tugStatus);
rowData.put("tug_status", status);
// 模拟更改数据
this.mockChange(rowData);
return Result.ok();
}
/**
* 模拟更改进度条状态
*
* @param id
* @param progress
* @return
*/
@GetMapping("/change3")
public Result mockChange3(@RequestParam("id") String id, @RequestParam("progress") String progress) {
/* id 为 行的idrowId只要获取到rowId那么只需要调用 VXESocket.sendMessageToAll() 即可 */
// 封装行数据
JSONObject rowData = new JSONObject();
// 这个字段就是要更改的行数据ID
rowData.put("id", id);
// 这个字段就是要更改的列的key和具体的值
rowData.put("progress", progress);
// 模拟更改数据
this.mockChange(rowData);
return Result.ok();
}
private void mockChange(JSONObject rowData) {
// 封装socket数据
JSONObject socketData = new JSONObject();
// 这里的 socketKey 必须要和调度计划页面上写的 socketKey 属性保持一致
socketData.put("socketKey", "page-dispatch");
// 这里的 args 必须得是一个数组下标0是行数据下标1是caseId一般不用传
socketData.put("args", new Object[]{rowData, ""});
// 封装消息字符串,这里的
// type 必须是 VXESocketConst.TYPE_UVT
String message = VxeSocket.packageMessage(VxeSocketConst.TYPE_UVT, socketData);
// 调用 sendMessageToAll 发送给所有在线的用户
VxeSocket.sendMessageToAll(message);
}
/**
* 模拟更改【大船待审】状态
*
* @param status
* @return
*/
@GetMapping("/change4")
public Result mockChange4(@RequestParam("status") String status) {
// 封装socket数据
JSONObject socketData = new JSONObject();
// 这里的 key 是前端注册时使用的key必须保持一致
socketData.put("key", "dispatch-dcds-status");
// 这里的 args 必须得是一个数组,每一位都是注册方法的参数,按顺序传递
socketData.put("args", new Object[]{status});
// 封装消息字符串,这里的 type 必须是 VXESocketConst.TYPE_UVT
String message = VxeSocket.packageMessage(VxeSocketConst.TYPE_CSD, socketData);
// 调用 sendMessageToAll 发送给所有在线的用户
VxeSocket.sendMessageToAll(message);
return Result.ok();
}
/**
* 【模拟】即时保存单行数据
*
* @param rowData 行数据,实际使用时可以替换成一个实体类
*/
@PutMapping("/immediateSaveRow")
public Result mockImmediateSaveRow(@RequestBody JSONObject rowData) throws Exception {
System.out.println("即时保存.rowData" + rowData.toJSONString());
// 延时1.5秒,模拟网慢堵塞真实感
Thread.sleep(500);
return Result.ok();
}
/**
* 【模拟】即时保存整个表格的数据
*
* @param tableData 表格数据实际使用时可以替换成一个List实体类
*/
@PostMapping("/immediateSaveAll")
public Result mockImmediateSaveAll(@RequestBody JSONArray tableData) throws Exception {
// 【注】:
// 1、tableData里包含该页所有的数据
// 2、如果你实现了“即时保存”那么除了新增的数据其他的都是已经保存过的了
// 不需要再进行一次update操作了所以可以在前端传数据的时候就遍历判断一下
// 只传新增的数据给后台insert即可否者将会造成性能上的浪费。
// 3、新增的行是没有id的通过这一点就可以判断是否是新增的数据
System.out.println("即时保存.tableData" + tableData.toJSONString());
// 延时1.5秒,模拟网慢堵塞真实感
Thread.sleep(1000);
return Result.ok();
}
/**
* 获取模拟数据
*
* @param pageNo 页码
* @param pageSize 页大小
* @param parentId 父ID不传则查询顶级
* @return
*/
@GetMapping("/getData")
public Result getMockData(
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
// 父级id根据父级id查询子级如果为空则查询顶级
@RequestParam(name = "parentId", required = false) String parentId
) {
// 模拟JSON数据路径
String path = "classpath:org/jeecg/modules/demo/mock/vxe/json/dlglong.json";
// 读取JSON数据
JSONArray dataList = readJsonData(path);
if (dataList == null) {
return Result.error("读取数据失败!");
}
IPage<JSONObject> page = this.queryDataPage(dataList, parentId, pageNo, pageSize);
return Result.ok(page);
}
/**
* 获取模拟“调度计划”页面的数据
*
* @param pageNo 页码
* @param pageSize 页大小
* @param parentId 父ID不传则查询顶级
* @return
*/
@GetMapping("/getDdjhData")
public Result getMockDdjhData(
// SpringMVC 会自动将参数注入到实体里
MockEntity mockEntity,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
// 父级id根据父级id查询子级如果为空则查询顶级
@RequestParam(name = "parentId", required = false) String parentId,
@RequestParam(name = "status", required = false) String status,
// 高级查询条件
@RequestParam(name = "superQueryParams", required = false) String superQueryParams,
// 高级查询模式
@RequestParam(name = "superQueryMatchType", required = false) String superQueryMatchType,
HttpServletRequest request
) {
// 获取查询条件(前台传递的查询参数)
Map<String, String[]> parameterMap = request.getParameterMap();
// 遍历输出到控制台
System.out.println("\ngetDdjhData - 普通查询条件:");
for (String key : parameterMap.keySet()) {
System.out.println("-- " + key + ": " + JSON.toJSONString(parameterMap.get(key)));
}
// 输出高级查询
try {
System.out.println("\ngetDdjhData - 高级查询条件:");
// 高级查询模式
MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryMatchType);
if (matchType == null) {
System.out.println("-- 高级查询模式:不识别(" + superQueryMatchType + "");
} else {
System.out.println("-- 高级查询模式:" + matchType.getValue());
}
superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
List<QueryCondition> conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
if (conditions != null) {
for (QueryCondition condition : conditions) {
System.out.println("-- " + JSON.toJSONString(condition));
}
} else {
System.out.println("-- 没有传递任何高级查询条件");
}
System.out.println();
} catch (Exception e) {
log.error("-- 高级查询操作失败:" + superQueryParams, e);
e.printStackTrace();
}
/* 注:实际使用中不用写上面那种繁琐的代码,这里只是为了直观的输出到控制台里而写的示例,
使用下面这种写法更简洁方便 */
// 封装成 MyBatisPlus 能识别的 QueryWrapper可以直接使用这个对象进行SQL筛选条件拼接
// 这个方法也会自动封装高级查询条件但是高级查询参数名必须是superQueryParams和superQueryMatchType
QueryWrapper<MockEntity> queryWrapper = QueryGenerator.initQueryWrapper(mockEntity, parameterMap);
System.out.println("queryWrapper " + queryWrapper.getCustomSqlSegment());
// 模拟JSON数据路径
String path = "classpath:org/jeecg/modules/demo/mock/vxe/json/ddjh.json";
String statusValue = "8";
if (statusValue.equals(status)) {
path = "classpath:org/jeecg/modules/demo/mock/vxe/json/ddjh_s8.json";
}
// 读取JSON数据
JSONArray dataList = readJsonData(path);
if (dataList == null) {
return Result.error("读取数据失败!");
}
IPage<JSONObject> page = this.queryDataPage(dataList, parentId, pageNo, pageSize);
// 逐行查询子表数据,用于计算拖轮状态
List<JSONObject> records = page.getRecords();
for (JSONObject record : records) {
Map<String, Integer> tugStatusMap = new HashMap<>(5);
String id = record.getString("id");
// 查询出主表的拖轮
String tugMain = record.getString("tug");
// 判断是否有值
if (StringUtils.isNotBlank(tugMain)) {
// 拖轮根据分号分割
String[] tugs = tugMain.split(";");
// 查询子表数据
List<JSONObject> subRecords = this.queryDataPage(dataList, id, null, null).getRecords();
// 遍历子表和拖轮数据,找出进行计算反推拖轮状态
for (JSONObject subData : subRecords) {
String subTug = subData.getString("tug");
if (StringUtils.isNotBlank(subTug)) {
for (String tug : tugs) {
if (tug.equals(subTug)) {
// 计算拖轮状态逻辑
int statusCode = 0;
/* 如果有发船时间、作业开始时间、作业结束时间、回船时间,则主表中的拖轮列中的每个拖轮背景色要即时变色 */
// 有发船时间,状态 +1
String departureTime = subData.getString("departure_time");
if (StringUtils.isNotBlank(departureTime)) {
statusCode += 1;
}
// 有作业开始时间,状态 +1
String workBeginTime = subData.getString("work_begin_time");
if (StringUtils.isNotBlank(workBeginTime)) {
statusCode += 1;
}
// 有作业结束时间,状态 +1
String workEndTime = subData.getString("work_end_time");
if (StringUtils.isNotBlank(workEndTime)) {
statusCode += 1;
}
// 有回船时间,状态 +1
String returnTime = subData.getString("return_time");
if (StringUtils.isNotBlank(returnTime)) {
statusCode += 1;
}
// 保存拖轮状态key是拖轮的值value是状态前端根据不同的状态码显示不同的颜色这个颜色也可以后台计算完之后返回给前端直接使用
tugStatusMap.put(tug, statusCode);
break;
}
}
}
}
}
// 新加一个字段用于保存拖轮状态,不要直接覆盖原来的,这个字段可以不保存到数据库里
record.put("tug_status", tugStatusMap);
}
page.setRecords(records);
return Result.ok(page);
}
/**
* 模拟查询数据可以根据父ID查询可以分页
*
* @param dataList 数据列表
* @param parentId 父ID
* @param pageNo 页码
* @param pageSize 页大小
* @return
*/
private IPage<JSONObject> queryDataPage(JSONArray dataList, String parentId, Integer pageNo, Integer pageSize) {
// 根据父级id查询子级
JSONArray dataDb = dataList;
if (StringUtils.isNotBlank(parentId)) {
JSONArray results = new JSONArray();
List<String> parentIds = Arrays.asList(parentId.split(","));
this.queryByParentId(dataDb, parentIds, results);
dataDb = results;
}
// 模拟分页实际中应用SQL自带的分页
List<JSONObject> records = new ArrayList<>();
IPage<JSONObject> page;
long beginIndex, endIndex;
// 如果任意一个参数为null则不分页
if (pageNo == null || pageSize == null) {
page = new Page<>(0, dataDb.size());
beginIndex = 0;
endIndex = dataDb.size();
} else {
page = new Page<>(pageNo, pageSize);
beginIndex = page.offset();
endIndex = page.offset() + page.getSize();
}
for (long i = beginIndex; (i < endIndex && i < dataDb.size()); i++) {
JSONObject data = dataDb.getJSONObject((int) i);
data = JSON.parseObject(data.toJSONString());
// 不返回 children
data.remove("children");
records.add(data);
}
page.setRecords(records);
page.setTotal(dataDb.size());
return page;
}
private void queryByParentId(JSONArray dataList, List<String> parentIds, JSONArray results) {
for (int i = 0; i < dataList.size(); i++) {
JSONObject data = dataList.getJSONObject(i);
JSONArray children = data.getJSONArray("children");
// 找到了该父级
if (parentIds.contains(data.getString("id"))) {
if (children != null) {
// addAll 的目的是将多个子表的数据合并在一起
results.addAll(children);
}
} else {
if (children != null) {
queryByParentId(children, parentIds, results);
}
}
}
results.addAll(new JSONArray());
}
private JSONArray readJsonData(String path) {
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(path.replace("classpath:", ""));
if (stream != null) {
String json = IOUtils.toString(stream, "UTF-8");
return JSON.parseArray(json);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package org.jeecg.modules.demo.mock.vxe.entity;
import lombok.Data;
/**
* 模拟实体
* @author: jeecg-boot
*/
@Data
public class MockEntity {
/**
* id
*/
private String id;
/**
* 父级ID
*/
private String parentId;
/**
* 状态
*/
private String status;
/* -- 省略其他字段 -- */
}

View File

@ -0,0 +1,217 @@
package org.jeecg.modules.demo.mock.vxe.websocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.VxeSocketConst;
import org.springframework.stereotype.Component;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* vxe WebSocket用于实现实时无痕刷新的功能
* @author: jeecg-boot
*/
@Slf4j
@Component
@ServerEndpoint("/vxeSocket/{userId}/{pageId}")
public class VxeSocket {
/**
* 当前 session
*/
private Session session;
/**
* 当前用户id
*/
private String userId;
/**
* 页面id用于标识同一用户不同页面的数据
*/
private String pageId;
/**
* 当前socket唯一id
*/
private String socketId;
/**
* 用户连接池包含单个用户的所有socket连接
* 因为一个用户可能打开多个页面,多个页面就会有多个连接;
* key是userIdvalue是Map对象子Map的key是pageIdvalue是VXESocket对象
*/
private static Map<String, Map<String, VxeSocket>> userPool = new HashMap<>();
/**
* 连接池包含所有WebSocket连接
* key是socketIdvalue是VXESocket对象
*/
private static Map<String, VxeSocket> socketPool = new HashMap<>();
/**
* 获取某个用户所有的页面
*/
public static Map<String, VxeSocket> getUserPool(String userId) {
return userPool.computeIfAbsent(userId, k -> new HashMap<>(5));
}
/**
* 向当前用户发送消息
*
* @param message 消息内容
*/
public void sendMessage(String message) {
try {
this.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
log.error("【vxeSocket】消息发送失败" + e.getMessage());
}
}
/**
* 封装消息json
*
* @param data 消息内容
*/
public static String packageMessage(String type, Object data) {
JSONObject message = new JSONObject();
message.put(VxeSocketConst.TYPE, type);
message.put(VxeSocketConst.DATA, data);
return message.toJSONString();
}
/**
* 向指定用户的所有页面发送消息
*
* @param userId 接收消息的用户ID
* @param message 消息内容
*/
public static void sendMessageTo(String userId, String message) {
Collection<VxeSocket> values = getUserPool(userId).values();
if (values.size() > 0) {
for (VxeSocket socketItem : values) {
socketItem.sendMessage(message);
}
} else {
log.warn("【vxeSocket】消息发送失败userId\"" + userId + "\"不存在或未在线!");
}
}
/**
* 向指定用户的指定页面发送消息
*
* @param userId 接收消息的用户ID
* @param message 消息内容
*/
public static void sendMessageTo(String userId, String pageId, String message) {
VxeSocket socketItem = getUserPool(userId).get(pageId);
if (socketItem != null) {
socketItem.sendMessage(message);
} else {
log.warn("【vxeSocket】消息发送失败userId\"" + userId + "\"的pageId\"" + pageId + "\"不存在或未在线!");
}
}
/**
* 向多个用户的所有页面发送消息
*
* @param userIds 接收消息的用户ID数组
* @param message 消息内容
*/
public static void sendMessageTo(String[] userIds, String message) {
for (String userId : userIds) {
VxeSocket.sendMessageTo(userId, message);
}
}
/**
* 向所有用户的所有页面发送消息
*
* @param message 消息内容
*/
public static void sendMessageToAll(String message) {
for (VxeSocket socketItem : socketPool.values()) {
socketItem.sendMessage(message);
}
}
/**
* websocket 开启连接
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId, @PathParam("pageId") String pageId) {
try {
this.userId = userId;
this.pageId = pageId;
this.socketId = userId + pageId;
this.session = session;
socketPool.put(this.socketId, this);
getUserPool(userId).put(this.pageId, this);
log.info("【vxeSocket】有新的连接总数为:" + socketPool.size());
} catch (Exception ignored) {
}
}
/**
* websocket 断开连接
*/
@OnClose
public void onClose() {
try {
socketPool.remove(this.socketId);
getUserPool(this.userId).remove(this.pageId);
log.info("【vxeSocket】连接断开总数为:" + socketPool.size());
} catch (Exception ignored) {
}
}
/**
* websocket 收到消息
*/
@OnMessage
public void onMessage(String message) {
// log.info("【vxeSocket】onMessage:" + message);
JSONObject json;
try {
json = JSON.parseObject(message);
} catch (Exception e) {
log.warn("【vxeSocket】收到不合法的消息:" + message);
return;
}
String type = json.getString(VxeSocketConst.TYPE);
switch (type) {
// 心跳检测
case VxeSocketConst.TYPE_HB:
this.sendMessage(VxeSocket.packageMessage(type, true));
break;
// 更新form数据
case VxeSocketConst.TYPE_UVT:
this.handleUpdateForm(json);
break;
default:
log.warn("【vxeSocket】收到不识别的消息类型:" + type);
break;
}
}
/**
* 处理 UpdateForm 事件
*/
private void handleUpdateForm(JSONObject json) {
// 将事件转发给所有人
JSONObject data = json.getJSONObject(VxeSocketConst.DATA);
VxeSocket.sendMessageToAll(VxeSocket.packageMessage(VxeSocketConst.TYPE_UVT, data));
}
}

View File

@ -0,0 +1,117 @@
package org.jeecg.modules.demo.online;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.util.oConvertUtils;
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 java.util.ArrayList;
import java.util.List;
/**
* Online表单开发 demo 示例
*
* @author sunjianlei
* @date 2021-12-16
*/
@Slf4j
@RestController("onlCgformDemoController")
@RequestMapping("/demo/online/cgform")
public class OnlCgformDemoController {
/**
* Online表单 http 增强list增强示例
* @param params
* @return
*/
@PostMapping("/enhanceJavaListHttp")
public Result<?> enhanceJavaListHttp(@RequestBody JSONObject params) {
log.info(" --- params" + params.toJSONString());
JSONArray dataList = params.getJSONArray("dataList");
List<DictModel> dict = virtualDictData();
for (int i = 0; i < dataList.size(); i++) {
JSONObject record = dataList.getJSONObject(i);
String province = record.getString("province");
if (province == null) {
continue;
}
String text = dict.stream()
.filter(p -> province.equals(p.getValue()))
.map(DictModel::getText)
.findAny()
.orElse(province);
record.put("province", text);
}
Result<?> res = Result.OK(dataList);
res.setCode(1);
return res;
}
/**
* 模拟字典数据
*
* @return
*/
private List<DictModel> virtualDictData() {
List<DictModel> dict = new ArrayList<>();
dict.add(new DictModel("bj", "北京"));
dict.add(new DictModel("sd", "山东"));
dict.add(new DictModel("ah", "安徽"));
return dict;
}
/**
* Online表单 http 增强add、edit增强示例
* @param params
* @return
*/
@PostMapping("/enhanceJavaHttp")
public Result<?> enhanceJavaHttp(@RequestBody JSONObject params) {
log.info(" --- params" + params.toJSONString());
String tableName = params.getString("tableName");
JSONObject record = params.getJSONObject("record");
/*
* 业务场景一: 获取提交表单数据,进行其他业务关联操作
* (比如:根据入库单,同步更改库存)
*/
log.info(" --- tableName" + tableName);
log.info(" --- 行数据:" + record.toJSONString());
/*
* 业务场景二: 保存数据之前进行数据的校验
* 直接返回错误状态即可
*/
String phone = record.getString("phone");
if (oConvertUtils.isEmpty(phone)) {
return Result.error("手机号不能为空!");
}
/*
* 业务场景三: 保存数据之对数据的处理
* 直接操作 record 即可
*/
record.put("phone", "010-" + phone);
/* 其他业务场景自行实现 */
// 返回场景一: 不对 record 做任何修改的情况下,可以直接返回 code
// 返回 0 = 丢弃当前数据
// 返回 1 = 新增当前数据
// 返回 2 = 修改当前数据 TODO存疑
// return Result.OK(1);
// 返回场景二: 需要对 record 做修改的情况下需要返回一个JSONObject对象或者Map也行
JSONObject res = new JSONObject();
res.put("code", 1);
// 将 record 返回以进行修改
res.put("record", record);
// TODO 不要 code 的概念
return Result.OK(res);
}
}

View File

@ -0,0 +1,63 @@
package org.jeecg.modules.demo.test.controller;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.demo.test.entity.JeecgDemo;
import org.jeecg.modules.demo.test.service.IJeecgDemoService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 大屏预览入口
* @Author: scott
* @Date:2019-12-12
* @Version:V1.0
*/
@Slf4j
@Controller
@RequestMapping("/test/bigScreen/templat")
public class BigScreenTemplatController extends JeecgController<JeecgDemo, IJeecgDemoService> {
/**
* @param modelAndView
* @return
*/
@RequestMapping("/html")
public ModelAndView ftl(ModelAndView modelAndView) {
modelAndView.setViewName("demo3");
List<String> userList = new ArrayList<String>();
userList.add("admin");
userList.add("user1");
userList.add("user2");
log.info("--------------test--------------");
modelAndView.addObject("userList", userList);
return modelAndView;
}
/**
* 生产销售监控模版
* @param modelAndView
* @return
*/
@RequestMapping("/index1")
public ModelAndView index1(ModelAndView modelAndView) {
modelAndView.setViewName("/bigscreen/template1/index");
return modelAndView;
}
/**
* 智慧物流监控模版
* @param modelAndView
* @return
*/
@RequestMapping("/index2")
public ModelAndView index2(ModelAndView modelAndView) {
modelAndView.setViewName("/bigscreen/template2/index");
return modelAndView;
}
}

View File

@ -0,0 +1,491 @@
package org.jeecg.modules.demo.test.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.aspect.annotation.PermissionData;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.demo.test.entity.JeecgDemo;
import org.jeecg.modules.demo.test.service.IJeecgDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import reactor.core.publisher.Mono;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @Description: 单表示例
* @Author: jeecg-boot
* @Date:2018-12-29
* @Version:V2.0
*/
@Slf4j
@Tag(name = "单表DEMO")
@RestController
@RequestMapping("/test/jeecgDemo")
public class JeecgDemoController extends JeecgController<JeecgDemo, IJeecgDemoService> {
@Autowired
private IJeecgDemoService jeecgDemoService;
@Autowired
private RedisUtil redisUtil;
/**
* 分页列表查询
*
* @param jeecgDemo
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@Operation(summary = "获取所有Demo数据列表")
@GetMapping(value = "/list")
@PermissionData(pageComponent = "jeecg/JeecgDemoList")
public Result<?> list(JeecgDemo jeecgDemo, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<JeecgDemo> queryWrapper = QueryGenerator.initQueryWrapper(jeecgDemo, req.getParameterMap());
queryWrapper.orderByDesc("create_time");
Page<JeecgDemo> page = new Page<JeecgDemo>(pageNo, pageSize);
IPage<JeecgDemo> pageList = jeecgDemoService.page(page, queryWrapper);
log.info("查询当前页:" + pageList.getCurrent());
log.info("查询当前页数量:" + pageList.getSize());
log.info("查询结果数量:" + pageList.getRecords().size());
log.info("数据总数:" + pageList.getTotal());
return Result.OK(pageList);
}
/**
* 添加
*
* @param jeecgDemo
* @return
*/
@PostMapping(value = "/add")
@AutoLog(value = "添加测试DEMO")
@Operation(summary = "添加DEMO")
public Result<?> add(@RequestBody JeecgDemo jeecgDemo) {
jeecgDemoService.save(jeecgDemo);
return Result.OK("添加成功!");
}
/**
* 编辑
*
* @param jeecgDemo
* @return
*/
@AutoLog(value = "编辑DEMO", operateType = CommonConstant.OPERATE_TYPE_3)
@Operation(summary = "编辑DEMO")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<?> edit(@RequestBody JeecgDemo jeecgDemo) {
jeecgDemoService.updateById(jeecgDemo);
return Result.OK("更新成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@AutoLog(value = "删除测试DEMO")
@DeleteMapping(value = "/delete")
@Operation(summary = "通过ID删除DEMO")
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
jeecgDemoService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
@Operation(summary = "批量删除DEMO")
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.jeecgDemoService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
@Operation(summary = "通过ID查询DEMO")
public Result<?> queryById(@Parameter(name = "id", description = "示例id", required = true) @RequestParam(name = "id", required = true) String id) {
JeecgDemo jeecgDemo = jeecgDemoService.getById(id);
return Result.OK(jeecgDemo);
}
/**
* 导出excel
*
* @param request
*/
@RequestMapping(value = "/exportXls")
@PermissionData(pageComponent = "jeecg/JeecgDemoList")
public ModelAndView exportXls(HttpServletRequest request, JeecgDemo jeecgDemo) {
//获取导出表格字段
String exportFields = jeecgDemoService.getExportFields();
//分sheet导出表格字段
return super.exportXlsSheet(request, jeecgDemo, JeecgDemo.class, "单表模型",exportFields,500);
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, JeecgDemo.class);
}
// =====Redis 示例===============================================================================================
/**
* redis操作 -- set
*/
@GetMapping(value = "/redisSet")
public void redisSet() {
redisUtil.set("name", "张三" + DateUtils.now());
}
/**
* redis操作 -- get
*/
@GetMapping(value = "/redisGet")
public String redisGet() {
return (String) redisUtil.get("name");
}
/**
* redis操作 -- setObj
*/
@GetMapping(value = "/redisSetObj")
public void redisSetObj() {
JeecgDemo p = new JeecgDemo();
p.setAge(10);
p.setBirthday(new Date());
p.setContent("hello");
p.setName("张三");
p.setSex("");
redisUtil.set("user-zdh", p);
}
/**
* redis操作 -- setObj
*/
@GetMapping(value = "/redisGetObj")
public Object redisGetObj() {
return redisUtil.get("user-zdh");
}
/**
* redis操作 -- get
*/
@GetMapping(value = "/redis/{id}")
public JeecgDemo redisGetJeecgDemo(@PathVariable("id") String id) {
JeecgDemo t = jeecgDemoService.getByIdCacheable(id);
log.info(t.toString());
return t;
}
// ===Freemaker示例================================================================================
/**
* freemaker方式 【页面路径: src/main/resources/templates】
*
* @param modelAndView
* @return
*/
@IgnoreAuth
@RequestMapping("/html")
public ModelAndView ftl(ModelAndView modelAndView) {
modelAndView.setViewName("demo3");
List<String> userList = new ArrayList<String>();
userList.add("admin");
userList.add("user1");
userList.add("user2");
log.info("--------------test--------------");
modelAndView.addObject("userList", userList);
return modelAndView;
}
// ==========================================动态表单 JSON接收测试===========================================
/**
* online新增数据
*/
@PostMapping(value = "/testOnlineAdd")
public Result<?> testOnlineAdd(@RequestBody JSONObject json) {
log.info(json.toJSONString());
return Result.OK("添加成功!");
}
/*----------------------------------------外部获取权限示例------------------------------------*/
/**
* 【数据权限示例 - 编程】mybatisPlus java类方式加载权限
*
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/mpList")
@PermissionData(pageComponent = "jeecg/JeecgDemoList")
public Result<?> loadMpPermissonList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>();
//编程方式给queryWrapper装载数据权限规则
QueryGenerator.installAuthMplus(queryWrapper, JeecgDemo.class);
Page<JeecgDemo> page = new Page<JeecgDemo>(pageNo, pageSize);
IPage<JeecgDemo> pageList = jeecgDemoService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 【数据权限示例 - 编程】mybatis xml方式加载权限
*
* @param jeecgDemo
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/sqlList")
@PermissionData(pageComponent = "jeecg/JeecgDemoList")
public Result<?> loadSqlPermissonList(JeecgDemo jeecgDemo, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
IPage<JeecgDemo> pageList = jeecgDemoService.queryListWithPermission(pageSize, pageNo);
return Result.OK(pageList);
}
/*----------------------------------------外部获取权限示例------------------------------------*/
/**
* online api增强 列表
* @param params
* @return
*/
@PostMapping("/enhanceJavaListHttp")
public Result enhanceJavaListHttp(@RequestBody JSONObject params) {
log.info(" =========================================================== ");
log.info("params: " + params.toJSONString());
log.info("params.tableName: " + params.getString("tableName"));
log.info("params.json: " + params.getJSONObject("json").toJSONString());
JSONArray dataList = params.getJSONArray("dataList");
log.info("params.dataList: " + dataList.toJSONString());
log.info(" =========================================================== ");
return Result.OK(dataList);
}
/**
* online api增强 表单
* @param params
* @return
*/
@PostMapping("/enhanceJavaFormHttp")
public Result enhanceJavaFormHttp(@RequestBody JSONObject params) {
log.info(" =========================================================== ");
log.info("params: " + params.toJSONString());
log.info("params.tableName: " + params.getString("tableName"));
log.info("params.json: " + params.getJSONObject("json").toJSONString());
log.info(" =========================================================== ");
return Result.OK("1");
}
@GetMapping(value = "/hello")
public String hello(HttpServletRequest req) {
return "hello world!";
}
// =====Vue3 Native 原生页面示例===============================================================================================
@GetMapping(value = "/oneNative/list")
public Result oneNativeList(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize){
Object oneNative = redisUtil.get("one-native");
JSONArray data = new JSONArray();
if(null != oneNative){
JSONObject nativeObject = (JSONObject) oneNative;
data = nativeObject.getJSONArray("data");
}
IPage<JSONObject> objectPage = queryDataPage(data, pageNo, pageSize);
return Result.OK(objectPage);
}
@PostMapping("/oneNative/add")
public Result<String> oneNativeAdd(@RequestBody JSONObject jsonObject){
Object oneNative = redisUtil.get("one-native");
JSONObject nativeObject = new JSONObject();
JSONArray data = new JSONArray();
if(null != oneNative){
nativeObject = (JSONObject) oneNative;
data = nativeObject.getJSONArray("data");
}
jsonObject.put("id", UUIDGenerator.generate());
data.add(jsonObject);
nativeObject.put("data",data);
redisUtil.set("one-native",nativeObject);
return Result.OK("添加成功");
}
@PutMapping("/oneNative/edit")
public Result<String> oneNativeEdit(@RequestBody JSONObject jsonObject){
JSONObject oneNative = (JSONObject)redisUtil.get("one-native");
JSONArray data = oneNative.getJSONArray("data");
data = getNativeById(data,jsonObject);
oneNative.put("data", data);
redisUtil.set("one-native", oneNative);
return Result.OK("修改成功");
}
@DeleteMapping("/oneNative/delete")
public Result<String> oneNativeDelete(@RequestParam(name = "ids") String ids){
Object oneNative = redisUtil.get("one-native");
if(null != oneNative){
JSONObject nativeObject = (JSONObject) oneNative;
JSONArray data = nativeObject.getJSONArray("data");
data = deleteNativeById(data,ids);
nativeObject.put("data",data);
redisUtil.set("one-native",nativeObject);
}
return Result.OK("删除成功");
}
/**
* 获取redis对应id的数据
* @param data
* @param jsonObject
* @return
*/
public JSONArray getNativeById(JSONArray data,JSONObject jsonObject){
String dbId = "id";
String id = jsonObject.getString(dbId);
for (int i = 0; i < data.size(); i++) {
if(id.equals(data.getJSONObject(i).getString(dbId))){
data.set(i,jsonObject);
break;
}
}
return data;
}
/**
* 删除redis中包含的id数据
* @param data
* @param ids
* @return
*/
public JSONArray deleteNativeById(JSONArray data,String ids){
String dbId = "id";
for (int i = 0; i < data.size(); i++) {
//如果id包含直接清除data中的数据
if(ids.contains(data.getJSONObject(i).getString(dbId))){
data.fluentRemove(i);
}
//判断data的长度是否还剩1位
if(data.size() == 1 && ids.contains(data.getJSONObject(0).getString(dbId))){
data.fluentRemove(0);
}
}
return data;
}
/**
* 模拟查询数据可以根据父ID查询可以分页
*
* @param dataList 数据列表
* @param pageNo 页码
* @param pageSize 页大小
* @return
*/
private IPage<JSONObject> queryDataPage(JSONArray dataList, Integer pageNo, Integer pageSize) {
// 根据父级id查询子级
JSONArray dataDb = dataList;
// 模拟分页实际中应用SQL自带的分页
List<JSONObject> records = new ArrayList<>();
IPage<JSONObject> page;
long beginIndex, endIndex;
// 如果任意一个参数为null则不分页
if (pageNo == null || pageSize == null) {
page = new Page<>(0, dataDb.size());
beginIndex = 0;
endIndex = dataDb.size();
} else {
page = new Page<>(pageNo, pageSize);
beginIndex = page.offset();
endIndex = page.offset() + page.getSize();
}
for (long i = beginIndex; (i < endIndex && i < dataDb.size()); i++) {
JSONObject data = dataDb.getJSONObject((int) i);
data = JSON.parseObject(data.toJSONString());
// 不返回 children
data.remove("children");
records.add(data);
}
page.setRecords(records);
page.setTotal(dataDb.size());
return page;
}
// =====Vue3 Native 原生页面示例===============================================================================================
/**
* 获取创建人
* @return
*/
@GetMapping(value = "/groupList")
public Result<?> groupList() {
return Result.ok(jeecgDemoService.getCreateByList());
}
/**
* 测试Mono对象
* @return
*/
@Operation(summary = "Mono测试")
@GetMapping(value ="/test")
public Mono<String> test() {
//解决shiro报错No SecurityManager accessible to the calling code, either bound to the org.apache.shiro
// https://blog.csdn.net/Japhet_jiu/article/details/131177210
DefaultSecurityManager securityManager = new DefaultSecurityManager();
SecurityUtils.setSecurityManager(securityManager);
return Mono.just("测试");
}
}

View File

@ -0,0 +1,49 @@
package org.jeecg.modules.demo.test.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.demo.test.entity.JeecgDemo;
import org.jeecg.modules.demo.test.service.IJeecgDemoService;
import org.jeecg.modules.demo.test.service.IJeecgDynamicDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Description: 动态数据源测试
* @Author: zyf
* @Date:2020-04-21
*/
@Slf4j
@Tag(name = "动态数据源测试")
@RestController
@RequestMapping("/test/dynamic")
public class JeecgDynamicDataController extends JeecgController<JeecgDemo, IJeecgDemoService> {
@Autowired
private IJeecgDynamicDataService jeecgDynamicDataService;
/**
* 动态切换数据源
* @return
*/
@PostMapping(value = "/test1")
@AutoLog(value = "动态切换数据源")
@Operation(summary = "动态切换数据源")
public Result<List<JeecgDemo>> selectSpelByKey(@RequestParam(required = false) String dsName) {
List<JeecgDemo> list = jeecgDynamicDataService.selectSpelByKey(dsName);
return Result.OK(list);
}
}

View File

@ -0,0 +1,256 @@
package org.jeecg.modules.demo.test.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.demo.test.entity.JeecgOrderCustomer;
import org.jeecg.modules.demo.test.entity.JeecgOrderMain;
import org.jeecg.modules.demo.test.entity.JeecgOrderTicket;
import org.jeecg.modules.demo.test.service.IJeecgOrderCustomerService;
import org.jeecg.modules.demo.test.service.IJeecgOrderMainService;
import org.jeecg.modules.demo.test.service.IJeecgOrderTicketService;
import org.jeecg.modules.demo.test.vo.JeecgOrderMainPage;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @Description: 一对多示例ERP TAB风格
* @Author: ZhiLin
* @Date: 2019-02-20
* @Version: v2.0
*/
@Slf4j
@RestController
@RequestMapping("/test/order")
public class JeecgOrderErpMainController {
@Autowired
private IJeecgOrderMainService jeecgOrderMainService;
@Autowired
private IJeecgOrderCustomerService jeecgOrderCustomerService;
@Autowired
private IJeecgOrderTicketService jeecgOrderTicketService;
/**
* 分页列表查询
*
* @param jeecgOrderMain
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/orderList")
public Result<?> respondePagedData(JeecgOrderMain jeecgOrderMain,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<JeecgOrderMain> queryWrapper = QueryGenerator.initQueryWrapper(jeecgOrderMain, req.getParameterMap());
Page<JeecgOrderMain> page = new Page<JeecgOrderMain>(pageNo, pageSize);
IPage<JeecgOrderMain> pageList = jeecgOrderMainService.page(page, queryWrapper);
return Result.ok(pageList);
}
/**
* 添加
*
* @param jeecgOrderMainPage
* @return
*/
@PostMapping(value = "/add")
public Result<?> add(@RequestBody JeecgOrderMainPage jeecgOrderMainPage) {
JeecgOrderMain jeecgOrderMain = new JeecgOrderMain();
BeanUtils.copyProperties(jeecgOrderMainPage, jeecgOrderMain);
jeecgOrderMainService.save(jeecgOrderMain);
return Result.ok("添加成功!");
}
/**
* 编辑
*
* @param jeecgOrderMainPage
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<?> edit(@RequestBody JeecgOrderMainPage jeecgOrderMainPage) {
JeecgOrderMain jeecgOrderMain = new JeecgOrderMain();
BeanUtils.copyProperties(jeecgOrderMainPage, jeecgOrderMain);
jeecgOrderMainService.updateById(jeecgOrderMain);
return Result.ok("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
jeecgOrderMainService.delMain(id);
return Result.ok("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.jeecgOrderMainService.removeByIds(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
JeecgOrderMain jeecgOrderMain = jeecgOrderMainService.getById(id);
return Result.ok(jeecgOrderMain);
}
/**
* 通过id查询
*
* @param jeecgOrderCustomer
* @return
*/
@GetMapping(value = "/listOrderCustomerByMainId")
public Result<?> queryOrderCustomerListByMainId(JeecgOrderCustomer jeecgOrderCustomer,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<JeecgOrderCustomer> queryWrapper = QueryGenerator.initQueryWrapper(jeecgOrderCustomer, req.getParameterMap());
Page<JeecgOrderCustomer> page = new Page<JeecgOrderCustomer>(pageNo, pageSize);
IPage<JeecgOrderCustomer> pageList = jeecgOrderCustomerService.page(page, queryWrapper);
return Result.ok(pageList);
}
/**
* 通过id查询
*
* @param jeecgOrderTicket
* @return
*/
@GetMapping(value = "/listOrderTicketByMainId")
public Result<?> queryOrderTicketListByMainId(JeecgOrderTicket jeecgOrderTicket,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<JeecgOrderTicket> queryWrapper = QueryGenerator.initQueryWrapper(jeecgOrderTicket, req.getParameterMap());
Page<JeecgOrderTicket> page = new Page<JeecgOrderTicket>(pageNo, pageSize);
IPage<JeecgOrderTicket> pageList = jeecgOrderTicketService.page(page, queryWrapper);
return Result.ok(pageList);
}
/**
* 添加
*
* @param jeecgOrderCustomer
* @return
*/
@PostMapping(value = "/addCustomer")
public Result<?> addCustomer(@RequestBody JeecgOrderCustomer jeecgOrderCustomer) {
jeecgOrderCustomerService.save(jeecgOrderCustomer);
return Result.ok("添加成功!");
}
/**
* 编辑
*
* @param jeecgOrderCustomer
* @return
*/
@RequestMapping(value = "/editCustomer", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<?> editCustomer(@RequestBody JeecgOrderCustomer jeecgOrderCustomer) {
jeecgOrderCustomerService.updateById(jeecgOrderCustomer);
return Result.ok("添加成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/deleteCustomer")
public Result<?> deleteCustomer(@RequestParam(name = "id", required = true) String id) {
jeecgOrderCustomerService.removeById(id);
return Result.ok("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatchCustomer")
public Result<?> deleteBatchCustomer(@RequestParam(name = "ids", required = true) String ids) {
this.jeecgOrderCustomerService.removeByIds(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
/**
* 添加
*
* @param jeecgOrderTicket
* @return
*/
@PostMapping(value = "/addTicket")
public Result<?> addTicket(@RequestBody JeecgOrderTicket jeecgOrderTicket) {
jeecgOrderTicketService.save(jeecgOrderTicket);
return Result.ok("添加成功!");
}
/**
* 编辑
*
* @param jeecgOrderTicket
* @return
*/
@RequestMapping(value = "/editTicket", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<?> editTicket(@RequestBody JeecgOrderTicket jeecgOrderTicket) {
jeecgOrderTicketService.updateById(jeecgOrderTicket);
return Result.ok("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/deleteTicket")
public Result<?> deleteTicket(@RequestParam(name = "id", required = true) String id) {
jeecgOrderTicketService.removeById(id);
return Result.ok("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatchTicket")
public Result<?> deleteBatchTicket(@RequestParam(name = "ids", required = true) String ids) {
this.jeecgOrderTicketService.removeByIds(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
}

View File

@ -0,0 +1,255 @@
package org.jeecg.modules.demo.test.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.demo.test.entity.JeecgDemo;
import org.jeecg.modules.demo.test.entity.JeecgOrderCustomer;
import org.jeecg.modules.demo.test.entity.JeecgOrderMain;
import org.jeecg.modules.demo.test.entity.JeecgOrderTicket;
import org.jeecg.modules.demo.test.service.IJeecgDemoService;
import org.jeecg.modules.demo.test.service.IJeecgOrderCustomerService;
import org.jeecg.modules.demo.test.service.IJeecgOrderMainService;
import org.jeecg.modules.demo.test.service.IJeecgOrderTicketService;
import org.jeecg.modules.demo.test.vo.JeecgOrderMainPage;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Description: 一对多示例JEditableTable行编辑
* @Author: jeecg-boot
* @Date:2019-02-15
* @Version: V2.0
*/
@RestController
@RequestMapping("/test/jeecgOrderMain")
@Slf4j
public class JeecgOrderMainController extends JeecgController<JeecgOrderMain, IJeecgOrderMainService> {
@Autowired
private IJeecgOrderMainService jeecgOrderMainService;
@Autowired
private IJeecgOrderCustomerService jeecgOrderCustomerService;
@Autowired
private IJeecgOrderTicketService jeecgOrderTicketService;
/**
* 分页列表查询
*
* @param jeecgOrderMain
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<?> queryPageList(JeecgOrderMain jeecgOrderMain, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
QueryWrapper<JeecgOrderMain> queryWrapper = QueryGenerator.initQueryWrapper(jeecgOrderMain, req.getParameterMap());
Page<JeecgOrderMain> page = new Page<JeecgOrderMain>(pageNo, pageSize);
IPage<JeecgOrderMain> pageList = jeecgOrderMainService.page(page, queryWrapper);
return Result.ok(pageList);
}
/**
* 添加
*
* @param jeecgOrderMainPage
* @return
*/
@PostMapping(value = "/add")
public Result<?> add(@RequestBody JeecgOrderMainPage jeecgOrderMainPage) {
JeecgOrderMain jeecgOrderMain = new JeecgOrderMain();
BeanUtils.copyProperties(jeecgOrderMainPage, jeecgOrderMain);
jeecgOrderMainService.saveMain(jeecgOrderMain, jeecgOrderMainPage.getJeecgOrderCustomerList(), jeecgOrderMainPage.getJeecgOrderTicketList());
return Result.ok("添加成功!");
}
/**
* 编辑
*
* @param jeecgOrderMainPage
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<?> eidt(@RequestBody JeecgOrderMainPage jeecgOrderMainPage) {
JeecgOrderMain jeecgOrderMain = new JeecgOrderMain();
BeanUtils.copyProperties(jeecgOrderMainPage, jeecgOrderMain);
jeecgOrderMainService.updateCopyMain(jeecgOrderMain, jeecgOrderMainPage.getJeecgOrderCustomerList(), jeecgOrderMainPage.getJeecgOrderTicketList());
return Result.ok("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
jeecgOrderMainService.delMain(id);
return Result.ok("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.jeecgOrderMainService.delBatchMain(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
JeecgOrderMain jeecgOrderMain = jeecgOrderMainService.getById(id);
return Result.ok(jeecgOrderMain);
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryOrderCustomerListByMainId")
public Result<?> queryOrderCustomerListByMainId(@RequestParam(name = "id", required = true) String id) {
List<JeecgOrderCustomer> jeecgOrderCustomerList = jeecgOrderCustomerService.selectCustomersByMainId(id);
return Result.ok(jeecgOrderCustomerList);
}
/**
* 通过id查询
*
* @param id
* @return
*/
@GetMapping(value = "/queryOrderTicketListByMainId")
public Result<?> queryOrderTicketListByMainId(@RequestParam(name = "id", required = true) String id) {
List<JeecgOrderTicket> jeecgOrderTicketList = jeecgOrderTicketService.selectTicketsByMainId(id);
return Result.ok(jeecgOrderTicketList);
}
/**
* 导出excel
*
* @param request
*/
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, JeecgOrderMain jeecgOrderMain) {
// Step.1 组装查询条件
QueryWrapper<JeecgOrderMain> queryWrapper = QueryGenerator.initQueryWrapper(jeecgOrderMain, request.getParameterMap());
//Step.2 AutoPoi 导出Excel
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
//获取当前用户
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
List<JeecgOrderMainPage> pageList = new ArrayList<JeecgOrderMainPage>();
List<JeecgOrderMain> jeecgOrderMainList = jeecgOrderMainService.list(queryWrapper);
for (JeecgOrderMain orderMain : jeecgOrderMainList) {
JeecgOrderMainPage vo = new JeecgOrderMainPage();
BeanUtils.copyProperties(orderMain, vo);
// 查询机票
List<JeecgOrderTicket> jeecgOrderTicketList = jeecgOrderTicketService.selectTicketsByMainId(orderMain.getId());
vo.setJeecgOrderTicketList(jeecgOrderTicketList);
// 查询客户
List<JeecgOrderCustomer> jeecgOrderCustomerList = jeecgOrderCustomerService.selectCustomersByMainId(orderMain.getId());
vo.setJeecgOrderCustomerList(jeecgOrderCustomerList);
pageList.add(vo);
}
// 导出文件名称
mv.addObject(NormalExcelConstants.FILE_NAME, "一对多订单示例");
// 注解对象Class
mv.addObject(NormalExcelConstants.CLASS, JeecgOrderMainPage.class);
// 自定义表格参数
mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("自定义导出Excel内容标题", "导出人:" + sysUser.getRealname(), "自定义Sheet名字"));
// 导出数据列表
mv.addObject(NormalExcelConstants.DATA_LIST, pageList);
return mv;
}
/**
* 通过excel导入数据
*
* @param request
* @param
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
// 获取上传文件对象
MultipartFile file = entity.getValue();
ImportParams params = new ImportParams();
params.setTitleRows(2);
params.setHeadRows(2);
params.setNeedSave(true);
try {
List<JeecgOrderMainPage> list = ExcelImportUtil.importExcel(file.getInputStream(), JeecgOrderMainPage.class, params);
for (JeecgOrderMainPage page : list) {
JeecgOrderMain po = new JeecgOrderMain();
BeanUtils.copyProperties(page, po);
jeecgOrderMainService.saveMain(po, page.getJeecgOrderCustomerList(), page.getJeecgOrderTicketList());
}
return Result.ok("文件导入成功!");
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.error("文件导入失败:" + e.getMessage());
} finally {
try {
file.getInputStream().close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return Result.error("文件导入失败!");
}
}

View File

@ -0,0 +1,245 @@
package org.jeecg.modules.demo.test.controller;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.demo.test.entity.JoaDemo;
import org.jeecg.modules.demo.test.service.IJoaDemoService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
/**
* @Description: 流程测试
* @Author: jeecg-boot
* @Date: 2019-05-14
* @Version: V1.0
*/
@RestController
@RequestMapping("/test/joaDemo")
@Slf4j
public class JoaDemoController {
@Autowired
private IJoaDemoService joaDemoService;
/**
* 分页列表查询
* @param joaDemo
* @param pageNo
* @param pageSize
* @param req
* @return
*/
@GetMapping(value = "/list")
public Result<IPage<JoaDemo>> queryPageList(JoaDemo joaDemo,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
Result<IPage<JoaDemo>> result = new Result<IPage<JoaDemo>>();
QueryWrapper<JoaDemo> queryWrapper = QueryGenerator.initQueryWrapper(joaDemo, req.getParameterMap());
Page<JoaDemo> page = new Page<JoaDemo>(pageNo, pageSize);
IPage<JoaDemo> pageList = joaDemoService.page(page, queryWrapper);
result.setSuccess(true);
result.setResult(pageList);
return result;
}
/**
* 添加
* @param joaDemo
* @return
*/
@PostMapping(value = "/add")
public Result<JoaDemo> add(@RequestBody JoaDemo joaDemo) {
Result<JoaDemo> result = new Result<JoaDemo>();
try {
joaDemoService.save(joaDemo);
result.success("添加成功!");
} catch (Exception e) {
log.error(e.getMessage(),e);
result.error500("操作失败");
}
return result;
}
/**
* 编辑
* @param joaDemo
* @return
*/
@PutMapping(value = "/edit")
public Result<JoaDemo> edit(@RequestBody JoaDemo joaDemo) {
Result<JoaDemo> result = new Result<JoaDemo>();
JoaDemo joaDemoEntity = joaDemoService.getById(joaDemo.getId());
if(joaDemoEntity==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaDemoService.updateById(joaDemo);
//TODO 返回false说明什么
if(ok) {
result.success("修改成功!");
}
}
return result;
}
/**
* 通过id删除
* @param id
* @return
*/
@DeleteMapping(value = "/delete")
public Result<JoaDemo> delete(@RequestParam(name="id",required=true) String id) {
Result<JoaDemo> result = new Result<JoaDemo>();
JoaDemo joaDemo = joaDemoService.getById(id);
if(joaDemo==null) {
result.error500("未找到对应实体");
}else {
boolean ok = joaDemoService.removeById(id);
if(ok) {
result.success("删除成功!");
}
}
return result;
}
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping(value = "/deleteBatch")
public Result<JoaDemo> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
Result<JoaDemo> result = new Result<JoaDemo>();
if(ids==null || "".equals(ids.trim())) {
result.error500("参数不识别!");
}else {
this.joaDemoService.removeByIds(Arrays.asList(ids.split(",")));
result.success("删除成功!");
}
return result;
}
/**
* 通过id查询
* @param id
* @return
*/
@GetMapping(value = "/queryById")
public Result<JoaDemo> queryById(@RequestParam(name="id",required=true) String id) {
Result<JoaDemo> result = new Result<JoaDemo>();
JoaDemo joaDemo = joaDemoService.getById(id);
if(joaDemo==null) {
result.error500("未找到对应实体");
}else {
result.setResult(joaDemo);
result.setSuccess(true);
}
return result;
}
/**
* 导出excel
*
* @param request
* @param response
*/
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, HttpServletResponse response) {
// Step.1 组装查询条件
QueryWrapper<JoaDemo> queryWrapper = null;
try {
String paramsStr = request.getParameter("paramsStr");
if (oConvertUtils.isNotEmpty(paramsStr)) {
String deString = URLDecoder.decode(paramsStr, "UTF-8");
JoaDemo joaDemo = JSON.parseObject(deString, JoaDemo.class);
queryWrapper = QueryGenerator.initQueryWrapper(joaDemo, request.getParameterMap());
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//Step.2 AutoPoi 导出Excel
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
List<JoaDemo> pageList = joaDemoService.list(queryWrapper);
//导出文件名称
mv.addObject(NormalExcelConstants.FILE_NAME, "流程测试列表");
mv.addObject(NormalExcelConstants.CLASS, JoaDemo.class);
mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("流程测试列表数据", "导出人:Jeecg", "导出信息"));
mv.addObject(NormalExcelConstants.DATA_LIST, pageList);
return mv;
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
// 获取上传文件对象
MultipartFile file = entity.getValue();
ImportParams params = new ImportParams();
params.setTitleRows(2);
params.setHeadRows(1);
params.setNeedSave(true);
try {
List<JoaDemo> listJoaDemos = ExcelImportUtil.importExcel(file.getInputStream(), JoaDemo.class, params);
for (JoaDemo joaDemoExcel : listJoaDemos) {
joaDemoService.save(joaDemoExcel);
}
return Result.ok("文件导入成功!数据行数:" + listJoaDemos.size());
} catch (Exception e) {
log.error(e.getMessage(),e);
return Result.error("文件导入失败:"+e.getMessage());
} finally {
try {
file.getInputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return Result.ok("文件导入失败!");
}
}

Some files were not shown because too many files have changed in this diff Show More