Compare commits
11
Commits
2ba2711bf1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67fbfc8ee6 | ||
|
|
dc14fb3974 | ||
|
|
329452e6bc | ||
|
|
d1c52e2bf6 | ||
|
|
53d45c8e0d | ||
|
|
4119fdc148 | ||
|
|
2df11e2f37 | ||
|
|
cf5fbc15a3 | ||
|
|
a96e229db4 | ||
|
|
71198858f5 | ||
|
|
a203b4a684 |
Vendored
BIN
Binary file not shown.
@@ -10,6 +10,18 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
|
||||||
|
1. 配置 `application.yml` 文件
|
||||||
|
- MySQL
|
||||||
|
- Redis
|
||||||
|
- RabbitMq
|
||||||
|
- Mybatis
|
||||||
|
- LLM
|
||||||
|
- aliyun oss
|
||||||
|
2. 执行 `create_table.sql` 数据库初始化
|
||||||
|
3. 执行 `BiInitMain` 消息队列初始化
|
||||||
|
|
||||||
> Dev
|
> Dev
|
||||||
|
|
||||||
- Application Interface: http://localhost:8080/api/doc.html
|
- Application Interface: http://localhost:8080/api/doc.html
|
||||||
|
|||||||
+39
-17
@@ -9,25 +9,34 @@
|
|||||||
<version>3.2.2</version>
|
<version>3.2.2</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.wonder</groupId>
|
<groupId>com.wonder</groupId>
|
||||||
<artifactId>bi-backend</artifactId>
|
<artifactId>bi-backend</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>bi-backend</name>
|
<name>bi-backend</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-bom</artifactId>
|
||||||
|
<version>3.5.14</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
|
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.rabbitmq</groupId>
|
|
||||||
<artifactId>amqp-client</artifactId>
|
|
||||||
<version>5.17.0</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- https://github.com/redisson/redisson#quick-start -->
|
<!-- https://github.com/redisson/redisson#quick-start -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.redisson</groupId>
|
<groupId>org.redisson</groupId>
|
||||||
@@ -51,17 +60,29 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.mybatis.spring.boot</groupId>
|
|
||||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
|
||||||
<version>3.0.3</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
<version>3.5.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||||
|
<version>1.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-markdown-document-reader</artifactId>
|
||||||
|
<version>1.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- redis -->
|
<!-- redis -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -71,13 +92,14 @@
|
|||||||
<groupId>org.springframework.session</groupId>
|
<groupId>org.springframework.session</groupId>
|
||||||
<artifactId>spring-session-data-redis</artifactId>
|
<artifactId>spring-session-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
<version>3.0.3</version>
|
<version>4.4.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
# 建表脚本
|
# 建表脚本
|
||||||
# @author <a href="https://github.com/liyupi">程序员鱼皮</a>
|
|
||||||
# @from <a href="https://yupi.icu">编程导航知识星球</a>
|
|
||||||
|
|
||||||
-- 创建库
|
-- 创建库
|
||||||
create database if not exists yubi;
|
create database if not exists bi;
|
||||||
|
|
||||||
-- 切换库
|
-- 切换库
|
||||||
use yubi;
|
use bi;
|
||||||
|
|
||||||
-- 用户表
|
-- 用户表
|
||||||
create table if not exists user
|
create table if not exists user
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package com.wonder.bi.bizmq;
|
|||||||
import com.rabbitmq.client.Channel;
|
import com.rabbitmq.client.Channel;
|
||||||
import com.rabbitmq.client.Connection;
|
import com.rabbitmq.client.Connection;
|
||||||
import com.rabbitmq.client.ConnectionFactory;
|
import com.rabbitmq.client.ConnectionFactory;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于创建测试程序用到的交换机和队列(只用在程序启动前执行一次)
|
* 用于创建测试程序用到的交换机和队列(只用在程序启动前执行一次)
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class BiInitMain {
|
public class BiInitMain {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -15,18 +17,22 @@ public class BiInitMain {
|
|||||||
factory.setHost("localhost");
|
factory.setHost("localhost");
|
||||||
factory.setVirtualHost("/bi");
|
factory.setVirtualHost("/bi");
|
||||||
factory.setUsername("wonder");
|
factory.setUsername("wonder");
|
||||||
factory.setPassword("Dn293830");
|
factory.setPassword("dn293830");
|
||||||
Connection connection = factory.newConnection();
|
Connection connection = factory.newConnection();
|
||||||
Channel channel = connection.createChannel();
|
Channel channel = connection.createChannel();
|
||||||
String EXCHANGE_NAME = BiMqConstant.BI_EXCHANGE_NAME;
|
String EXCHANGE_NAME = BiMqConstant.BI_EXCHANGE_NAME;
|
||||||
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
|
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
|
||||||
|
log.info("交换机创建成功");
|
||||||
|
|
||||||
// 创建队列,随机分配一个队列名称
|
// 创建队列,随机分配一个队列名称
|
||||||
String queueName = BiMqConstant.BI_QUEUE_NAME;
|
String queueName = BiMqConstant.BI_QUEUE_NAME;
|
||||||
channel.queueDeclare(queueName, true, false, false, null);
|
channel.queueDeclare(queueName, true, false, false, null);
|
||||||
|
log.info("队列创建成功");
|
||||||
channel.queueBind(queueName, EXCHANGE_NAME, BiMqConstant.BI_ROUTING_KEY);
|
channel.queueBind(queueName, EXCHANGE_NAME, BiMqConstant.BI_ROUTING_KEY);
|
||||||
|
log.info("队列绑定成功");
|
||||||
|
connection.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.wonder.bi.bizmq;
|
|
||||||
|
|
||||||
import com.rabbitmq.client.Channel;
|
|
||||||
import com.rabbitmq.client.Connection;
|
|
||||||
import com.rabbitmq.client.ConnectionFactory;
|
|
||||||
import com.wonder.bi.config.RabbitMqConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于创建测试程序用到的交换机和队列(只用在程序启动前执行一次)
|
|
||||||
*/
|
|
||||||
public class MqInitMain {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
try {
|
|
||||||
ConnectionFactory factory = RabbitMqConfig.newConnectionFactory();
|
|
||||||
Connection connection = factory.newConnection();
|
|
||||||
Channel channel = connection.createChannel();
|
|
||||||
String EXCHANGE_NAME = "code_exchange";
|
|
||||||
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
|
|
||||||
|
|
||||||
// 创建队列,随机分配一个队列名称
|
|
||||||
String queueName = "code_queue";
|
|
||||||
channel.queueDeclare(queueName, true, false, false, null);
|
|
||||||
channel.queueBind(queueName, EXCHANGE_NAME, "my_routingKey");
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.wonder.bi.bizmq;
|
|
||||||
|
|
||||||
import com.rabbitmq.client.Channel;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
|
||||||
import org.springframework.amqp.support.AmqpHeaders;
|
|
||||||
import org.springframework.messaging.handler.annotation.Header;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class MyMessageConsumer {
|
|
||||||
|
|
||||||
// 指定程序监听的消息队列和确认机制
|
|
||||||
@SneakyThrows
|
|
||||||
@RabbitListener(queues = {"code_queue"}, ackMode = "MANUAL")
|
|
||||||
public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
|
||||||
log.info("receiveMessage message = {}", message);
|
|
||||||
channel.basicAck(deliveryTag, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.wonder.bi.config;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import springfox.documentation.builders.ApiInfoBuilder;
|
|
||||||
import springfox.documentation.builders.PathSelectors;
|
|
||||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
|
||||||
import springfox.documentation.spi.DocumentationType;
|
|
||||||
import springfox.documentation.spring.web.plugins.Docket;
|
|
||||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Knife4j 接口文档配置
|
|
||||||
* https://doc.xiaominfo.com/knife4j/documentation/get_start.html
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableSwagger2
|
|
||||||
@Profile({"dev", "test"})
|
|
||||||
public class Knife4jConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Docket defaultApi2() {
|
|
||||||
return new Docket(DocumentationType.SWAGGER_2)
|
|
||||||
.apiInfo(new ApiInfoBuilder()
|
|
||||||
.title("Interface Documentation")
|
|
||||||
.description("bi-backend")
|
|
||||||
.version("1.1")
|
|
||||||
.build())
|
|
||||||
.select()
|
|
||||||
.apis(RequestHandlerSelectors.basePackage("com.wonder.bi.controller"))
|
|
||||||
.paths(PathSelectors.any())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,8 @@ public class RedissonConfig {
|
|||||||
config.useSingleServer()
|
config.useSingleServer()
|
||||||
.setDatabase(database)
|
.setDatabase(database)
|
||||||
.setAddress("redis://" + host + ":" + port)
|
.setAddress("redis://" + host + ":" + port)
|
||||||
.setPassword(password);
|
.setPassword(password)
|
||||||
|
.setConnectTimeout(10000);
|
||||||
RedissonClient redisson = Redisson.create(config);
|
RedissonClient redisson = Redisson.create(config);
|
||||||
return redisson;
|
return redisson;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.wonder.bi.config;
|
package com.wonder.bi.config;
|
||||||
|
|
||||||
import org.apache.poi.ss.formula.functions.T;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|||||||
@@ -32,4 +32,26 @@ public interface CommonConstant {
|
|||||||
" \"{前端 Echarts V5 的 option 配置对象的json代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\\n\" +\n" +
|
" \"{前端 Echarts V5 的 option 配置对象的json代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\\n\" +\n" +
|
||||||
" \"【【【【【\\n\" +\n" +
|
" \"【【【【【\\n\" +\n" +
|
||||||
" \"{明确的数据分析结论、越详细越好,不要生成多余的注释}\"";
|
" \"{明确的数据分析结论、越详细越好,不要生成多余的注释}\"";
|
||||||
|
|
||||||
|
String CHART_AGENT_PROMPT = """
|
||||||
|
你是一个数据分析师和前端开发专家,我会按照以下固定格式给你提供内容:
|
||||||
|
分析需求:
|
||||||
|
{数据分析的需求或者目标}
|
||||||
|
原始数据
|
||||||
|
{csv格式的原始数据,用,作为分隔符}
|
||||||
|
请根据这两部分内容,按照以下指定格式生成内容
|
||||||
|
{前端 Echarts V5 的 option 配置对象的json代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}""";
|
||||||
|
|
||||||
|
String INSIGHT_AGENT_PROMPT = """
|
||||||
|
你是一个数据分析师,我会按照以下固定格式给你提供内容:
|
||||||
|
分析需求:
|
||||||
|
{数据分析的需求或者目标}
|
||||||
|
原始数据
|
||||||
|
{csv格式的原始数据,用,作为分隔符}
|
||||||
|
规则集
|
||||||
|
{可选,可能为空}
|
||||||
|
请根据以上内容,按照以下结构生成内容,格式是 MarkDown
|
||||||
|
{Insight: 见解}
|
||||||
|
{Rules: 使用到的规则集}
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import com.wonder.bi.constant.CommonConstant;
|
|||||||
import com.wonder.bi.constant.UserConstant;
|
import com.wonder.bi.constant.UserConstant;
|
||||||
import com.wonder.bi.exception.BusinessException;
|
import com.wonder.bi.exception.BusinessException;
|
||||||
import com.wonder.bi.exception.ThrowUtils;
|
import com.wonder.bi.exception.ThrowUtils;
|
||||||
|
import com.wonder.bi.manager.ChartAgentManager;
|
||||||
|
import com.wonder.bi.manager.InsightAgentManager;
|
||||||
import com.wonder.bi.manager.OpenAiManager;
|
import com.wonder.bi.manager.OpenAiManager;
|
||||||
import com.wonder.bi.manager.RedisLimiterManager;
|
import com.wonder.bi.manager.RedisLimiterManager;
|
||||||
import com.wonder.bi.model.dto.chart.*;
|
import com.wonder.bi.model.dto.chart.*;
|
||||||
@@ -54,8 +56,14 @@ public class ChartController {
|
|||||||
@Resource
|
@Resource
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
|
// @Resource
|
||||||
|
// private OpenAiManager openAiManager;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private OpenAiManager openAiManager;
|
private ChartAgentManager chartAgent;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private InsightAgentManager insightAgent;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisLimiterManager redisLimiterManager;
|
private RedisLimiterManager redisLimiterManager;
|
||||||
@@ -293,17 +301,15 @@ public class ChartController {
|
|||||||
String csvData = ExcelUtils.excelToCsv(multipartFile);
|
String csvData = ExcelUtils.excelToCsv(multipartFile);
|
||||||
userInput.append(csvData).append("\n");
|
userInput.append(csvData).append("\n");
|
||||||
|
|
||||||
String result = openAiManager.doChat(userInput.toString());
|
// String result = openAiManager.doChat(userInput.toString());
|
||||||
String[] splits = Arrays.stream(result.split("【【【【【"))
|
|
||||||
.filter(s -> !s.equals("\n") && !s.isEmpty())
|
|
||||||
.toArray(String[]::new);
|
|
||||||
|
|
||||||
// if (splits.length < 3) {
|
// if (splits.length < 3) {
|
||||||
// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
|
// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
String genChart = splits[0].trim();
|
String genChart = chartAgent.getChartJson(userInput.toString());
|
||||||
String genResult = splits[1].trim();
|
String genResult = insightAgent.getAnalysisInsight(userInput.toString());
|
||||||
// 插入到数据库
|
// 插入到数据库
|
||||||
Chart chart = new Chart();
|
Chart chart = new Chart();
|
||||||
chart.setName(name);
|
chart.setName(name);
|
||||||
@@ -354,24 +360,6 @@ public class ChartController {
|
|||||||
// 限流判断,每个用户一个限流器
|
// 限流判断,每个用户一个限流器
|
||||||
redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());
|
redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());
|
||||||
|
|
||||||
// final String prompt = "你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容:\n" +
|
|
||||||
// "分析需求:\n" +
|
|
||||||
// "{数据分析的需求或者目标}\n" +
|
|
||||||
// "原始数据:\n" +
|
|
||||||
// "{csv格式的原始数据,用,作为分隔符}\n" +
|
|
||||||
// "请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释)\n" +
|
|
||||||
// "【【【【【\n" +
|
|
||||||
// "{前端 Echarts V5 的 option 配置对象js代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\n" +
|
|
||||||
// "【【【【【\n" +
|
|
||||||
// "{明确的数据分析结论、越详细越好,不要生成多余的注释}";
|
|
||||||
// 分析需求:
|
|
||||||
// 分析网站用户的增长情况
|
|
||||||
// 原始数据:
|
|
||||||
// 日期,用户数
|
|
||||||
// 1号,10
|
|
||||||
// 2号,20
|
|
||||||
// 3号,30
|
|
||||||
|
|
||||||
// 构造用户输入
|
// 构造用户输入
|
||||||
StringBuilder userInput = new StringBuilder();
|
StringBuilder userInput = new StringBuilder();
|
||||||
userInput.append("分析需求:").append("\n");
|
userInput.append("分析需求:").append("\n");
|
||||||
@@ -410,14 +398,14 @@ public class ChartController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 调用 AI
|
// 调用 AI
|
||||||
String result = openAiManager.doChat(userInput.toString());
|
// String result = openAiManager.doChat(userInput.toString());
|
||||||
String[] splits = result.split("【【【【【");
|
// String[] splits = result.split("【【【【【");
|
||||||
if (splits.length < 3) {
|
// if (splits.length < 3) {
|
||||||
handleChartUpdateError(chart.getId(), "AI 生成错误");
|
// handleChartUpdateError(chart.getId(), "AI 生成错误");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
String genChart = splits[1].trim();
|
String genChart = chartAgent.getChartJson(userInput.toString());
|
||||||
String genResult = splits[2].trim();
|
String genResult = insightAgent.getAnalysisInsight(userInput.toString());
|
||||||
Chart updateChartResult = new Chart();
|
Chart updateChartResult = new Chart();
|
||||||
updateChartResult.setId(chart.getId());
|
updateChartResult.setId(chart.getId());
|
||||||
updateChartResult.setGenChart(genChart);
|
updateChartResult.setGenChart(genChart);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.wonder.bi.manager;
|
||||||
|
|
||||||
|
import com.wonder.bi.constant.CommonConstant;
|
||||||
|
import com.wonder.bi.utils.JsonUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ChartAgentManager {
|
||||||
|
|
||||||
|
private ChatClient client;
|
||||||
|
|
||||||
|
public ChartAgentManager(ChatClient.Builder chatClientBuilder) {
|
||||||
|
this.client = chatClientBuilder
|
||||||
|
.defaultSystem(CommonConstant.CHART_AGENT_PROMPT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChartJson(String message) {
|
||||||
|
String content = client
|
||||||
|
.prompt(message)
|
||||||
|
.call()
|
||||||
|
.content();
|
||||||
|
log.info(content);
|
||||||
|
JsonUtils.isValidJson(content);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.wonder.bi.manager;
|
||||||
|
|
||||||
|
import com.wonder.bi.constant.CommonConstant;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class InsightAgentManager {
|
||||||
|
|
||||||
|
private ChatClient client;
|
||||||
|
|
||||||
|
public InsightAgentManager(ChatClient.Builder chatClientBuilder) {
|
||||||
|
this.client = chatClientBuilder
|
||||||
|
.defaultSystem(CommonConstant.INSIGHT_AGENT_PROMPT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnalysisInsight(String message) {
|
||||||
|
String content = client
|
||||||
|
.prompt(message)
|
||||||
|
.call()
|
||||||
|
.content();
|
||||||
|
log.info(content);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnalysisInsight(String message, String rules) {
|
||||||
|
String content = client
|
||||||
|
.prompt(message + "\n【Rules】" + rules)
|
||||||
|
.call()
|
||||||
|
.content();
|
||||||
|
log.info(content);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.wonder.bi.rag;
|
||||||
|
|
||||||
|
import org.springframework.ai.document.Document;
|
||||||
|
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
|
||||||
|
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MyMarkdownReader {
|
||||||
|
|
||||||
|
private final Resource[] docs;
|
||||||
|
|
||||||
|
MyMarkdownReader() throws IOException {
|
||||||
|
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||||
|
this.docs = resolver.getResources("classpath:rag/*.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Document> loadMarkdown() {
|
||||||
|
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
|
||||||
|
.withHorizontalRuleCreateDocument(true)
|
||||||
|
.withIncludeCodeBlock(false)
|
||||||
|
.withIncludeBlockquote(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MarkdownDocumentReader reader = new MarkdownDocumentReader(docs[0], config);
|
||||||
|
return reader.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.wonder.bi.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.wonder.bi.common.ErrorCode;
|
||||||
|
import com.wonder.bi.exception.BusinessException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class JsonUtils {
|
||||||
|
|
||||||
|
public static boolean isValidJson(String input) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
mapper.readTree(input);
|
||||||
|
return true;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new BusinessException(ErrorCode.OPERATION_ERROR, "LLM JSON 输出错误,请重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,13 +19,13 @@ public class NetUtils {
|
|||||||
*/
|
*/
|
||||||
public static String getIpAddress(HttpServletRequest request) {
|
public static String getIpAddress(HttpServletRequest request) {
|
||||||
String ip = request.getHeader("x-forwarded-for");
|
String ip = request.getHeader("x-forwarded-for");
|
||||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
ip = request.getHeader("Proxy-Client-IP");
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
}
|
}
|
||||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
}
|
}
|
||||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
ip = request.getRemoteAddr();
|
ip = request.getRemoteAddr();
|
||||||
if (ip.equals("127.0.0.1")) {
|
if (ip.equals("127.0.0.1")) {
|
||||||
// 根据网卡取本机配置的 IP
|
// 根据网卡取本机配置的 IP
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.wonder.bi.utils;
|
package com.wonder.bi.utils;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
|
# springdoc-openapi项目配置
|
||||||
|
springdoc:
|
||||||
|
swagger-ui:
|
||||||
|
path: /swagger-ui.html
|
||||||
|
tags-sorter: alpha
|
||||||
|
operations-sorter: alpha
|
||||||
|
api-docs:
|
||||||
|
path: /v3/api-docs
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
packages-to-scan: com.wonder.bi.controller
|
||||||
|
# knife4j的增强配置,不需要增强可以不配
|
||||||
|
knife4j:
|
||||||
|
enable: true
|
||||||
|
setting:
|
||||||
|
language: zh_cn
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: bi-backend
|
name: bi-backend
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
base-url: https://api.siliconflow.cn
|
||||||
|
api-key: sk-idvvtxhxwaulcmvoshpvouuenezzttqgaeqwbdcucqmvfncm
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: Qwen/Qwen3-Coder-30B-A3B-Instruct
|
||||||
# 支持 swagger3
|
# 支持 swagger3
|
||||||
mvc:
|
mvc:
|
||||||
pathmatch:
|
pathmatch:
|
||||||
@@ -14,8 +39,8 @@ spring:
|
|||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
url: jdbc:mysql://47.121.181.112:3306/bi
|
url: jdbc:mysql://47.121.181.112:3306/bi
|
||||||
username: wonder
|
username: root
|
||||||
password: Dn293830
|
password: dn293830
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
druid:
|
druid:
|
||||||
initial-size: 5
|
initial-size: 5
|
||||||
@@ -40,8 +65,8 @@ spring:
|
|||||||
database: 1
|
database: 1
|
||||||
host: 47.121.181.112
|
host: 47.121.181.112
|
||||||
port: 6379
|
port: 6379
|
||||||
timeout: 5000
|
password: dn293830
|
||||||
password: Dn293830@redis
|
|
||||||
|
|
||||||
# 文件上传
|
# 文件上传
|
||||||
servlet:
|
servlet:
|
||||||
@@ -51,7 +76,7 @@ spring:
|
|||||||
rabbitmq:
|
rabbitmq:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 5672
|
port: 5672
|
||||||
password: Dn293830
|
password: dn293830
|
||||||
username: wonder
|
username: wonder
|
||||||
virtual-host: /bi
|
virtual-host: /bi
|
||||||
server:
|
server:
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# 成绩统计规则集
|
||||||
|
|
||||||
|
- 约束:当涉及到“成绩”“绩点”关键词时应用此规则集
|
||||||
|
- 约定:默认成绩分层 1:3:5:1,分别为优秀、良好、合格、待改进
|
||||||
@@ -13,6 +13,6 @@ class MyMessageProducerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sendMessage() {
|
void sendMessage() {
|
||||||
myMessageProducer.sendMessage("code_exchange", "my_routingKey", "你好呀");
|
myMessageProducer.sendMessage("amq.direct", "email.#", "hello@example.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.wonder.bi.manager;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class ChartAgentManagerTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ChartAgentManager chartAgent;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getChartJson() {
|
||||||
|
String chartJson = chartAgent.getChartJson("分析需求:\n" +
|
||||||
|
"分析网站用户的增长情况\n" +
|
||||||
|
"原始数据:\n" +
|
||||||
|
"日期,用户数\n" +
|
||||||
|
"1号,10\n" +
|
||||||
|
"2号,20\n" +
|
||||||
|
"3号,30\n");
|
||||||
|
Assertions.assertNotNull(chartJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.wonder.bi.manager;
|
||||||
|
|
||||||
|
import com.wonder.bi.rag.MyMarkdownReader;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.ai.document.Document;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class InsightAgentManagerTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private InsightAgentManager insightAgent;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MyMarkdownReader reader;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnalysisInsight() {
|
||||||
|
String insight = insightAgent.getAnalysisInsight("分析需求:\n" +
|
||||||
|
"分析网站用户的增长情况\n" +
|
||||||
|
"原始数据:\n" +
|
||||||
|
"日期,用户数\n" +
|
||||||
|
"1号,10\n" +
|
||||||
|
"2号,20\n" +
|
||||||
|
"3号,30\n",
|
||||||
|
"定义增长 50% 及以上为快速增长");
|
||||||
|
Assertions.assertNotNull(insight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnalysisInsightWithRag() {
|
||||||
|
List<Document> documents = reader.loadMarkdown();
|
||||||
|
StringBuilder ragText = new StringBuilder();
|
||||||
|
for (Document document: documents) {
|
||||||
|
ragText.append(document.toString());
|
||||||
|
}
|
||||||
|
String insight = insightAgent.getAnalysisInsight("分析需求:\n" +
|
||||||
|
"分析班级成绩分布\n" +
|
||||||
|
"原始数据:\n" +
|
||||||
|
"序号,分数\n" +
|
||||||
|
"1号,90\n" +
|
||||||
|
"2号,90\n" +
|
||||||
|
"3号,80\n" +
|
||||||
|
"4号,20\n" +
|
||||||
|
"5号,40\n" +
|
||||||
|
"6号,89\n" +
|
||||||
|
"7号,59\n",
|
||||||
|
ragText.toString());
|
||||||
|
Assertions.assertNotNull(insight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,6 @@ class OosManagerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void putObject() {
|
void putObject() {
|
||||||
// oosManager.putObject("test", "E:\\Doc\\7B\\yubi-backend-5f672bb635e37361d5a1ae0e5403e96f36317fd0\\src\\main\\resources\\banner.txt");
|
oosManager.putObject("test", "D:\\Desktop\\1.csv");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.wonder.bi.rag;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.ai.document.Document;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class MyMarkdownReaderTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MyMarkdownReader markdownReader;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadMarkdown() {
|
||||||
|
List<Document> documents = markdownReader.loadMarkdown();
|
||||||
|
for (Document document : documents) {
|
||||||
|
System.out.println(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.wonder.bi.utils;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class JsonUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isValidJson() {
|
||||||
|
boolean validJson = JsonUtils.isValidJson("{\"name\": \"John\", \"age\": 30, \"isStudent\": false}");
|
||||||
|
assertTrue(validJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,7 +81,7 @@ const AddChart: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="file" label="原始数据">
|
<Form.Item name="file" label="原始数据">
|
||||||
<Upload name="file" maxCount={1}>
|
<Upload name="file" maxCount={1}>
|
||||||
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
|
<Button icon={<UploadOutlined />}>上传 Excel 文件</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const AddChartAsync: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="file" label="原始数据">
|
<Form.Item name="file" label="原始数据">
|
||||||
<Upload name="file" maxCount={1}>
|
<Upload name="file" maxCount={1}>
|
||||||
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
|
<Button icon={<UploadOutlined />}>上传 Excel 文件</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user