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
|
||||
|
||||
- Application Interface: http://localhost:8080/api/doc.html
|
||||
|
||||
+39
-17
@@ -9,25 +9,34 @@
|
||||
<version>3.2.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>com.wonder</groupId>
|
||||
<artifactId>bi-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>bi-backend</name>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
</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>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
@@ -51,17 +60,29 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</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>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -71,13 +92,14 @@
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||
<dependency>
|
||||
<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
|
||||
|
||||
@@ -3,10 +3,12 @@ package com.wonder.bi.bizmq;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 用于创建测试程序用到的交换机和队列(只用在程序启动前执行一次)
|
||||
*/
|
||||
@Slf4j
|
||||
public class BiInitMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -15,18 +17,22 @@ public class BiInitMain {
|
||||
factory.setHost("localhost");
|
||||
factory.setVirtualHost("/bi");
|
||||
factory.setUsername("wonder");
|
||||
factory.setPassword("Dn293830");
|
||||
factory.setPassword("dn293830");
|
||||
Connection connection = factory.newConnection();
|
||||
Channel channel = connection.createChannel();
|
||||
String EXCHANGE_NAME = BiMqConstant.BI_EXCHANGE_NAME;
|
||||
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
|
||||
log.info("交换机创建成功");
|
||||
|
||||
// 创建队列,随机分配一个队列名称
|
||||
String queueName = BiMqConstant.BI_QUEUE_NAME;
|
||||
channel.queueDeclare(queueName, true, false, false, null);
|
||||
log.info("队列创建成功");
|
||||
channel.queueBind(queueName, EXCHANGE_NAME, BiMqConstant.BI_ROUTING_KEY);
|
||||
log.info("队列绑定成功");
|
||||
connection.close();
|
||||
} 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()
|
||||
.setDatabase(database)
|
||||
.setAddress("redis://" + host + ":" + port)
|
||||
.setPassword(password);
|
||||
.setPassword(password)
|
||||
.setConnectTimeout(10000);
|
||||
RedissonClient redisson = Redisson.create(config);
|
||||
return redisson;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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.Configuration;
|
||||
|
||||
|
||||
@@ -32,4 +32,26 @@ public interface CommonConstant {
|
||||
" \"{前端 Echarts V5 的 option 配置对象的json代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\\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.exception.BusinessException;
|
||||
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.RedisLimiterManager;
|
||||
import com.wonder.bi.model.dto.chart.*;
|
||||
@@ -54,8 +56,14 @@ public class ChartController {
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
// @Resource
|
||||
// private OpenAiManager openAiManager;
|
||||
|
||||
@Resource
|
||||
private OpenAiManager openAiManager;
|
||||
private ChartAgentManager chartAgent;
|
||||
|
||||
@Resource
|
||||
private InsightAgentManager insightAgent;
|
||||
|
||||
@Resource
|
||||
private RedisLimiterManager redisLimiterManager;
|
||||
@@ -293,17 +301,15 @@ public class ChartController {
|
||||
String csvData = ExcelUtils.excelToCsv(multipartFile);
|
||||
userInput.append(csvData).append("\n");
|
||||
|
||||
String result = openAiManager.doChat(userInput.toString());
|
||||
String[] splits = Arrays.stream(result.split("【【【【【"))
|
||||
.filter(s -> !s.equals("\n") && !s.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
// String result = openAiManager.doChat(userInput.toString());
|
||||
|
||||
|
||||
// if (splits.length < 3) {
|
||||
// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
|
||||
// }
|
||||
|
||||
String genChart = splits[0].trim();
|
||||
String genResult = splits[1].trim();
|
||||
String genChart = chartAgent.getChartJson(userInput.toString());
|
||||
String genResult = insightAgent.getAnalysisInsight(userInput.toString());
|
||||
// 插入到数据库
|
||||
Chart chart = new Chart();
|
||||
chart.setName(name);
|
||||
@@ -354,24 +360,6 @@ public class ChartController {
|
||||
// 限流判断,每个用户一个限流器
|
||||
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();
|
||||
userInput.append("分析需求:").append("\n");
|
||||
@@ -410,14 +398,14 @@ public class ChartController {
|
||||
return;
|
||||
}
|
||||
// 调用 AI
|
||||
String result = openAiManager.doChat(userInput.toString());
|
||||
String[] splits = result.split("【【【【【");
|
||||
if (splits.length < 3) {
|
||||
handleChartUpdateError(chart.getId(), "AI 生成错误");
|
||||
return;
|
||||
}
|
||||
String genChart = splits[1].trim();
|
||||
String genResult = splits[2].trim();
|
||||
// String result = openAiManager.doChat(userInput.toString());
|
||||
// String[] splits = result.split("【【【【【");
|
||||
// if (splits.length < 3) {
|
||||
// handleChartUpdateError(chart.getId(), "AI 生成错误");
|
||||
// return;
|
||||
// }
|
||||
String genChart = chartAgent.getChartJson(userInput.toString());
|
||||
String genResult = insightAgent.getAnalysisInsight(userInput.toString());
|
||||
Chart updateChartResult = new Chart();
|
||||
updateChartResult.setId(chart.getId());
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(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();
|
||||
if (ip.equals("127.0.0.1")) {
|
||||
// 根据网卡取本机配置的 IP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.wonder.bi.utils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
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:
|
||||
application:
|
||||
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
|
||||
mvc:
|
||||
pathmatch:
|
||||
@@ -14,8 +39,8 @@ spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://47.121.181.112:3306/bi
|
||||
username: wonder
|
||||
password: Dn293830
|
||||
username: root
|
||||
password: dn293830
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
druid:
|
||||
initial-size: 5
|
||||
@@ -40,8 +65,8 @@ spring:
|
||||
database: 1
|
||||
host: 47.121.181.112
|
||||
port: 6379
|
||||
timeout: 5000
|
||||
password: Dn293830@redis
|
||||
password: dn293830
|
||||
|
||||
|
||||
# 文件上传
|
||||
servlet:
|
||||
@@ -51,7 +76,7 @@ spring:
|
||||
rabbitmq:
|
||||
host: localhost
|
||||
port: 5672
|
||||
password: Dn293830
|
||||
password: dn293830
|
||||
username: wonder
|
||||
virtual-host: /bi
|
||||
server:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# 成绩统计规则集
|
||||
|
||||
- 约束:当涉及到“成绩”“绩点”关键词时应用此规则集
|
||||
- 约定:默认成绩分层 1:3:5:1,分别为优秀、良好、合格、待改进
|
||||
@@ -13,6 +13,6 @@ class MyMessageProducerTest {
|
||||
|
||||
@Test
|
||||
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
|
||||
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 name="file" label="原始数据">
|
||||
<Upload name="file" maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
|
||||
<Button icon={<UploadOutlined />}>上传 Excel 文件</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ const AddChartAsync: React.FC = () => {
|
||||
</Form.Item>
|
||||
<Form.Item name="file" label="原始数据">
|
||||
<Upload name="file" maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
|
||||
<Button icon={<UploadOutlined />}>上传 Excel 文件</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user