该项目是全程跟着鱼皮,还未进行功能扩展。为了方便日后复习,打算将一些重要的步骤记录下来。
该笔记着重记录后端代码,前端了解大致方向即可
项目概述
BI:即商业智能:Business Intelligence
过去
需要手动导入数据、选择要分析的字段和图表,并由专业的数据分析师完成分析,最后得出结论。
现在
用户只需输入想要分析的目标,并上传原始数据,系统将利用 AI 自动生成可视化图表。这样,会比较方便
端口占用
Windows Hyper-V 虚拟化平台占用了端口
什么是Hyper-V :
为了使硬件虚拟化,即创建一个没有物理形式的硬件环境,你需要在物理计算机和虚拟机之间有一个中介。这个接口被称为管理程序。物理主机系统可以映射到共享主机硬件(父分区)的多个虚拟客户系统(子分区)。微软已经创建了自己的管理程序 Hyper-V,它包含在 Windows 10、11 或 Windows 8 的专业版本中。该软件也被安装在 Windows Server 中。
第一种方法:
查看被占用的端口,然后选择一个没被占用的端口启动项目。cmd命令打开输入
netsh interface ipv4 show excludedportrange protocol=tcp
第二种方法:
直接禁用Hyper-V(不推荐)
前端结构介绍
大概了解即可,不用刻意背,用着用着就懂了
检查提交的代码,是否规范(代码校验的小工) | |
umi 配置,包含由,构路建等配置 | |
整个项目所用的框架,Ant Design Pro 的核心配置 | |
Ant Design Pro 默认设置,可以更改页面样式 | |
一个示例的数据(没用,删掉) | |
代理,方便本地开发(一般用不到,不用删) | |
定义网页路由(就是输入哪个网址,跳转到哪个页面) | |
本地模拟数据(删掉) | |
icon目录中存放了页面的图标(删掉) | |
框架自动生成的隐藏文件(不用管) | |
业务通用组件 | |
国际化资源(删掉) | |
业务页面入口和常用模板 | |
后台接口服务 | |
可以把自带的删掉,这里先删除 swagger目录,ant-design-pro 先不删,现在项目里很多内容用到它,等后面改完再删。 | |
控制页面访问权限 | |
整个 Ant Design Pro 框架的主要文件 | |
全局样式 | |
全局 JS | |
开发 app 或 h5 网页指定多种不同的配置。(删掉)例如:生成项目名称、项目图标尺寸等...一般打包的时候用到 | |
控制前端页面发送请求的配置。例如:你向哪个地址发送请求,请求发送错误,怎么处理等..... | |
h5 网页离线时优化页面的体验 | |
测试工具(删掉) | |
全是未引用(删掉) | |
保证前端项目代码规范。在 webstorm 中可以按[Ctrl+Alt+L]格式化代码,可以让代码看起来更美观。 | |
单元测试框架(删掉) | |
控制语法 | |
整个项目的默认文档 |
模板优化:
替换logo
public目录下的一些图片是ant design pro自带的图片,我们只要把对应文件后缀和名称的图片覆盖后,就可以更改成我们自己的图片
favicon:网站标签图表、logo:登入页面的图标
美化配置
修改标题
ctrl + shift + f 全局搜索 Ant Design
ctrl + shift + r 全局替换
像网页上的一些东西,都可以用这种类似的方法修改,包括底下的导航栏都行
开发登入页
在:src/pages/User/Login/index.tsx操作,其他没用的都可以删掉
介绍大致框架
前端不会,大概知道每个地方干啥的就好了,具体实现以后再说
智能分析业务流程
用户输入
a. 分析目标
b. 上传原始数据(excel)
c. 更精细化地控制图表:比如图表类型、图表名称等
后端校验
a. 校验用户的输入否合法(比如长度)
b. 成本控制(次数统计和校验、鉴权等)
- 把处理后的excel数据输入给 AI 模型(调用 Al 接口,星球内部同学可以免费使用 Al 接口),让 AI 模型给我们提供图表信息、结论代码
- 图表信息(是一段 json 配置,是一段代码)、结论文本在前端进行展示
代码编写
FileController里有一个uploadFile,用来上传文件,我们可以将他改造成我们需要的上传excel,并对其进行解析。将整个方法复制到ChartController里,并重命名为GenChartByAi
缺失字段如何补充?
红色框框内是我们要补充的字段,填写好后复制
直接粘贴进去
然后在实体类补上缺失的参数,如果我们的model/dto/chart包下的各种请求参数需要的话,也要补上
注意:mapper层里的sql语句也要记得补上
该字段相关的业务逻辑要补上(与名字相关的是模糊查询)
对输入进行简单的判断(不要相信前端的限制,后端也要编写对应代码)
/**
* 智能分析
*
* @param multipartFile
* @param genChartByAiRequest
* @param request
* @return
*/
@PostMapping("/gen")
public BaseResponse<String> genChartByAi(@RequestPart("file") MultipartFile multipartFile,
GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {
String name = genChartByAiRequest.getName();
String goal = genChartByAiRequest.getGoal();
String chartType = genChartByAiRequest.getChartType();
// 校验
// 如果分析目标为空,就抛出请求参数错误异常,并给出提示
ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
// 如果名称不为空,并且名称长度大于100,就抛出异常,并给出提示
ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
// 读取到用户上传的 excel 文件,进行一个处理
User loginUser = userService.getLoginUser(request);
// 文件目录:根据业务、用户来划分
String uuid = RandomStringUtils.randomAlphanumeric(8);
String filename = uuid + "-" + multipartFile.getOriginalFilename();
File file = null;
try {
// 返回可访问地址
return ResultUtils.success("");
} catch (Exception e) {
// log.error("file upload error, filepath = " + filepath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
} finally {
if (file != null) {
// 删除临时文件
boolean delete = file.delete();
if (!delete) {
// log.error("file delete error, filepath = {}", filepath);
}
}
}
}
如何写好一个好的promt
Al提词技巧1:持续输入,持续优化
第一次问: 我给你三行数据,请帮我分析网站的增长趋势,数据如下:
第一行:日期: 1号, 用户数: 10人
第二行:日期: 2号,用户数: 20人
第三行:日期: 3号,用户数: 30人
AI提词技巧2:数据压缩(内容压缩,比如把很长的内容提取关键词,也可以让Al来做)
第二次问:我给你三行数据,请帮我分析网站的增长趋势,数据如下:
表头:日期,用户数
1号,10
2号,20
3号,30
第三次问: 我给你三行数据,请帮我用最少的字数压缩这段数据
第一行:日期: 1号, 用户数: 10人
第二行:日期: 2号,用户数: 20人
第三行:日期: 3号,用户数: 30人
Al回答: 日期1-3号,用户数分别为10/20/30人。
用 chatGPT 也好,还是用鱼聪明,无论使用哪种 AI 模型,输入都是数据而非文件。那怎么输入数据呢?并且输入的字数也会有限制。
所以我们要尽可能地压缩数据,读取excel文件,转化成csv格式
excel转csv格式
基础模板
package com.ptu.api.utils;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* EasyExcel 测试
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@SpringBootTest
public class EasyExcelTest {
@Test
/**
* 该Java代码从类路径加载名为“网站数据.xlsx”的文件,并使用EasyExcel库读取其中的数据。主要功能包括:
* 指定Excel类型为XLSX。
* 默认读取第一个工作表(sheet)。
* 设置表头行号为0,表示第一行是表头。
* 同步读取Excel数据到一个包含多行数据的List中,每行数据以Map形式存储,键为列索引,值为单元格内容。
*/
public void doImport() throws FileNotFoundException {
/**
* 该Java代码段使用EasyExcel库从上传的多部分文件流中读取Excel数据(格式为XLSX),并执行以下操作:
* 读取第一个工作表。
* 设置表头行号为0(即第一行为表头)。
* 同步执行读取操作,并将结果存储在list变量中。
*/
List<Map<Integer, String>> list = null;
list = EasyExcel.read(ResourceUtils.getFile("classpath:网站数据.xlsx"))
.excelType(ExcelTypeEnum.XLSX)
.sheet()
.headRowNumber(0)
.doReadSync();
//最终的数据
StringBuilder sb = new StringBuilder();
Map<Integer, String> headerMap = list.get(0);
List<String> headList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
/**
* 使用 StringUtils.join(headList, ",") 将 headList(第一行的标题)用逗号连接成一个字符串。
* 连接结果追加到 sb 对象中。
* 在追加的内容后添加换行符。
*/
sb.append(StringUtils.join(headList,",")).append("\n");
for(int i = 1;i < list.size();i++){
Map<Integer, String> dataMap = list.get(i);
List<String> dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
sb.append(StringUtils.join(dataList)).append("\n");
}
System.out.println(sb.toString());
}
}
基础模块优化
为了满足业务需求,可以对遍历到的数据进行过滤,这里对表格里删除的数据,但是读取过程依然会读出来(这是没有必要的)
package com.yupi.springbootinit.utils;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Excel 相关工具类
*/
public class ExcelUtils {
public static String excelToCsv(MultipartFile multipartFile) {
File file = null;
try {
file = ResourceUtils.getFile("classpath:网站数据.xlsx");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 读取数据
List<Map<Integer, String>> list = EasyExcel.read(file)
.excelType(ExcelTypeEnum.XLSX)
.sheet()
.headRowNumber(0)
.doReadSync();
// 如果数据为空
if (CollUtil.isEmpty(list)) {
return "";
}
// 转换为 csv
// 读取表头(第一行)
LinkedHashMap<Integer, String> headerMap = (LinkedHashMap) list.get(0);
List<String> headerList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
System.out.println(StringUtils.join(headerList,","));
// 读取数据(读取完表头之后,从第一行开始读取)
for (int i = 1; i < list.size(); i++) {
LinkedHashMap<Integer, String> dataMap = (LinkedHashMap) list.get(i);
List<String> dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
System.out.println(StringUtils.join(dataList,","));
}
System.out.println(list);
return "";
}
public static void main(String[] args) {
excelToCsv(null);
}
}
创建工具类
package com.ptu.api.utils;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class ExcelUtils {
public static String excelToCsv(MultipartFile multipartFile){
/**
* 该Java代码段使用EasyExcel库从上传的多部分文件流中读取Excel数据(格式为XLSX),并执行以下操作:
* 读取第一个工作表。
* 设置表头行号为0(即第一行为表头)。
* 同步执行读取操作,并将结果存储在list变量中。
*/
List<Map<Integer, String>> list = null;
try {
list = EasyExcel.read(multipartFile.getInputStream())
.excelType(ExcelTypeEnum.XLSX)
.sheet()
.headRowNumber(0)
.doReadSync();
} catch (IOException e) {
log.error("表格处理错误",e);
}
if (CollUtil.isEmpty(list)){
return "";
}
//最终的数据
StringBuilder sb = new StringBuilder();
Map<Integer, String> headerMap = list.get(0);
List<String> headList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
sb.append(StringUtils.join(headList,",")).append("\n");
for(int i = 1;i < list.size();i++){
Map<Integer, String> dataMap = list.get(i);
List<String> dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
sb.append(StringUtils.join(dataList)).append("\n");
}
return sb.toString();
}
public static void main(String[] args) {
excelToCsv(null);
}
}
调用AI
输入:
系统预设(提前告诉他职责、功能回复格式要求) + 分析目标 + 压缩后的数据
最简单的系统预设:
你是一个数据分析师,接下来我会给你我的分析目标和原始数据,请告诉我分析结论。
AI提词技巧3:
在系统(模型)层面做预设效果一般来说,蚍直接拼接在用户消息里效果更好一些。
AI提词技巧4:
除了系统预设外,额外关联一问一答两条消息,相当于给Al一个提示。
/**
* 智能分析
*
* @param multipartFile
* @param genChartByAiRequest
* @param request
* @return
*/
@PostMapping("/gen")
public BaseResponse<String> genChartByAi(@RequestPart("file") MultipartFile multipartFile,
GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {
String name = genChartByAiRequest.getName();
String goal = genChartByAiRequest.getGoal();
String chartType = genChartByAiRequest.getChartType();
// 校验
// 如果分析目标为空,就抛出请求参数错误异常,并给出提示
ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
// 如果名称不为空,并且名称长度大于100,就抛出异常,并给出提示
ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
// 用户输入
StringBuilder userInput = new StringBuilder();
userInput.append("你是一个数据分析师,接下来我会给你我的分析目标和原始数据,请告诉我分析结论。").append("\n");
userInput.append("分析目标:").append(goal).append("\n");
// 压缩后的数据(把multipartFile传进来,其他的东西先注释)
String result = ExcelUtils.excelToCsv(multipartFile);
userInput.append("数据:").append(result).append("\n");
return ResultUtils.success(userInput.toString());
// // 读取到用户上传的 excel 文件,进行一个处理
// User loginUser = userService.getLoginUser(request);
// // 文件目录:根据业务、用户来划分
// String uuid = RandomStringUtils.randomAlphanumeric(8);
// String filename = uuid + "-" + multipartFile.getOriginalFilename();
// File file = null;
// try {
//
// // 返回可访问地址
// return ResultUtils.success("");
// } catch (Exception e) {
log.error("file upload error, filepath = " + filepath, e);
// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
// } finally {
// if (file != null) {
// // 删除临时文件
// boolean delete = file.delete();
// if (!delete) {
log.error("file delete error, filepath = {}", filepath);
// }
// }
// }
}
系统预设最好是放在模型里,效果更好
这一期就到这里了,具体如何调用AI下期再来,拜拜溜~