Compare commits

...
11 Commits
28 changed files with 396 additions and 163 deletions
BIN
View File
Binary file not shown.
+12
View File
@@ -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
View File
@@ -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>
+2 -4
View File
@@ -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;
+30 -5
View File
@@ -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:
+4
View File
@@ -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);
}
}
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>