feat: 统计接口 & 智能分析接口实现

This commit is contained in:
Shu Guang 2025-05-17 03:31:33 +08:00
parent 362d28994b
commit 1b98b1bd66
26 changed files with 7712 additions and 26 deletions

14
.idea/dataSources.xml generated
View File

@ -14,5 +14,19 @@
</jdbc-additional-properties> </jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
<data-source source="LOCAL" name="jingsaisystem@110.40.62.21" uuid="16d85b6f-0e79-4d40-bd9c-cfe6f3fbea5f">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/System/src/main/resources/application.yml</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://110.40.62.21:3306/jingsaisystem?useSSL=false&amp;serverTimezone=UTC</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component> </component>
</project> </project>

124
.idea/uiDesigner.xml generated Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View File

@ -30,6 +30,11 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@ -0,0 +1,80 @@
package com.example.system.common;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.stereotype.Component;
@Component
public class AiPost {
private static final String baseUrl = "https://openai.933999.xyz";
private static final String apiKey = "sk-1PBIyxIdJ42yyC11XRNqbEXYDt2eZRNVNbd8XxmKjnPXGh5S";
private static final String model = "gpt-4o-mini";
/**
* 发送消息并获取AI响应
* @param message 用户消息
* @param maxTokens 最大token数
* @return AI响应内容
* @throws Exception 发送请求异常
*/
public String sendMessageAndGetResponse(String message, int maxTokens) throws Exception {
// 构造 messages 数组
JSONArray messagesArray = new JSONArray();
JSONObject userMessage = new JSONObject();
userMessage.put("role", "user");
userMessage.put("content", message);
messagesArray.add(userMessage);
// 构造请求体
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("messages", messagesArray);
// requestBody.put("max_tokens", maxTokens);
// requestBody.put("temperature", 0.2); // 更稳定输出
// 打印请求体
System.out.println("请求地址:" + baseUrl + "/v1/chat/completions");
System.out.println("请求体:" + requestBody.toStringPretty());
try {
HttpResponse response = HttpRequest.post(baseUrl + "/v1/chat/completions")
.timeout(60_000) // 60秒超时
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.body(requestBody.toString())
.execute();
String responseText = response.body();
System.out.println("响应状态码:" + response.getStatus());
System.out.println("响应内容:" + responseText);
if (response.getStatus() != 200) {
throw new RuntimeException("AI服务请求失败" + responseText);
}
// 安全解析响应
if (!JSONUtil.isJsonObj(responseText)) {
throw new RuntimeException("返回不是合法JSON" + responseText);
}
JSONObject responseJson = JSONUtil.parseObj(responseText);
JSONArray choices = responseJson.getJSONArray("choices");
if (choices == null || choices.isEmpty()) {
throw new RuntimeException("AI响应为空" + responseText);
}
JSONObject messageObj = choices.getJSONObject(0).getJSONObject("message");
return messageObj.getStr("content");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("调用AI接口发生错误", e);
}
}
}

View File

@ -0,0 +1,59 @@
package com.example.system.common;
import lombok.Data;
/**
* 图表实体类
*/
@Data
public class Chart {
/**
* 主键
*/
private String id;
/**
* 图表名称
*/
private String name;
/**
* 分析目标
*/
private String goal;
/**
* 图表数据CSV格式
*/
private String chartData;
/**
* 图表类型
*/
private String chartType;
/**
* 生成的图表配置Echarts JSON配置
*/
private String genChart;
/**
* 生成的分析结论
*/
private String genResult;
/**
* 创建人ID
*/
private Long adminId;
/**
* 创建时间
*/
private Long createTime;
/**
* 更新时间
*/
private Long updateTime;
}

View File

@ -0,0 +1,145 @@
package com.example.system.common;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 统一返回结果类
*/
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
private Integer status;
/**
* 返回消息
*/
private String msg;
/**
* 返回数据
*/
private T data;
/**
* 附加数据
*/
private Map<String, Object> additionalData = new HashMap<>();
/**
* 通用返回成功
*/
public static <T> R<T> success() {
R<T> result = new R<>();
result.setStatus(ResultCode.SUCCESS.getCode());
result.setMsg(ResultCode.SUCCESS.getMessage());
return result;
}
/**
* 返回成功消息
*/
public static <T> R<T> success(String message) {
R<T> result = new R<>();
result.setStatus(ResultCode.SUCCESS.getCode());
result.setMsg(message);
return result;
}
/**
* 返回成功数据
*/
public static <T> R<T> success(T data) {
R<T> result = new R<>();
result.setStatus(ResultCode.SUCCESS.getCode());
result.setMsg(ResultCode.SUCCESS.getMessage());
result.setData(data);
return result;
}
/**
* 返回成功消息和数据
*/
public static <T> R<T> success(String message, T data) {
R<T> result = new R<>();
result.setStatus(ResultCode.SUCCESS.getCode());
result.setMsg(message);
result.setData(data);
return result;
}
/**
* 通用返回失败
*/
public static <T> R<T> error() {
R<T> result = new R<>();
result.setStatus(ResultCode.ERROR.getCode());
result.setMsg(ResultCode.ERROR.getMessage());
return result;
}
/**
* 返回失败消息
*/
public static <T> R<T> error(String message) {
R<T> result = new R<>();
result.setStatus(ResultCode.ERROR.getCode());
result.setMsg(message);
return result;
}
/**
* 返回失败状态码和消息
*/
public static <T> R<T> error(int code, String message) {
R<T> result = new R<>();
result.setStatus(code);
result.setMsg(message);
return result;
}
/**
* 添加附加数据
*/
public R<T> add(String key, Object value) {
this.additionalData.put(key, value);
return this;
}
/**
* 返回结果状态码枚举
*/
public enum ResultCode {
SUCCESS(200, "操作成功"),
ERROR(500, "操作失败"),
VALIDATE_FAILED(400, "参数校验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private final int code;
private final String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}

View File

@ -0,0 +1,85 @@
package com.example.system.controller;
import com.example.system.common.R;
import com.example.system.pojo.GenChartByAiRequest;
import com.example.system.pojo.BiResponse;
import com.example.system.service.ChartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 智能分析控制器
*/
@RestController
@RequestMapping("/api/chart")
public class ChartController {
@Autowired
private ChartService chartService;
/**
* AI生成图表分析
*
* @param file 上传的Excel文件
// * @param genChartByAiRequest 分析请求参数
* @return 分析结果
*/
@CrossOrigin
@PostMapping("/gen")
public R<BiResponse> genChartByAi(@RequestParam("file") MultipartFile file,
@RequestParam(value = "name", required = false) String name,
@RequestParam("goal") String goal,
@RequestParam(value = "chartType", required = false) String chartType) {
// 构建请求对象
GenChartByAiRequest request = new GenChartByAiRequest();
request.setName(name);
request.setGoal(goal);
request.setChartType(chartType);
// request.setAdminId(adminId);
// 调用服务处理
return chartService.genChartByAi(file, request);
}
/**
* 获取示例提示
*
* @return 示例提示列表
*/
@CrossOrigin
@GetMapping("/examples")
public R<Object> getExamples() {
String[] examples = {
"分析学生成绩分布情况,使用柱状图",
"分析各学院参与竞赛人数占比,使用饼图",
"分析近五年竞赛获奖数量趋势,使用折线图",
"分析不同竞赛类型的参与人数对比,使用条形图",
"分析男女生参与各类竞赛的比例,使用堆叠柱状图"
};
return R.success("获取成功", examples);
}
/**
* 获取支持的图表类型
*
* @return 图表类型列表
*/
@CrossOrigin
@GetMapping("/chart-types")
public R<Object> getChartTypes() {
String[] chartTypes = {
"柱状图",
"折线图",
"饼图",
"散点图",
"雷达图",
"热力图",
"漏斗图"
};
return R.success("获取成功", chartTypes);
}
}

View File

@ -0,0 +1,106 @@
package com.example.system.controller;
import com.example.system.model.Result;
import com.example.system.model.ResultCompetitionTable;
import com.example.system.service.CompetitionService;
import com.example.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@CrossOrigin
@Slf4j
@RestController
@RequestMapping("/counts")
public class CountController {
@Autowired
private UserService userService;
@Autowired
private CompetitionService competitionService;
/**
* 获取系统统计数据
* 包括用户总数教师人数学生人数比赛总数
*
* @return 统计数据
*/
@GetMapping("/statistics")
public Object getStatistics() {
log.info("获取系统统计数据");
Map<String, Object> statistics = new HashMap<>();
// 获取用户总数
Object totalUsers = userService.getCount();
statistics.put("totalUsers", totalUsers);
// 获取教师人数
Object teacherCount = userService.getTeacherCount();
statistics.put("teacherCount", teacherCount);
// 获取学生人数
Object studentCount = userService.getStudentCount();
statistics.put("studentCount", studentCount);
// 获取比赛总数
List<ResultCompetitionTable> competitionList = competitionService.getAllCompetitions();
int competitionCount = competitionList != null ? competitionList.size() : 0;
statistics.put("competitionCount", competitionCount);
return Result.success(statistics);
}
/**
* 获取用户总数
*
* @return 用户总数
*/
@GetMapping("/users/total")
public Object getTotalUsers() {
log.info("获取用户总数");
Object count = userService.getCount();
return Result.success(count);
}
/**
* 获取教师人数
*
* @return 教师人数
*/
@GetMapping("/users/teachers")
public Object getTeacherCount() {
log.info("获取教师人数");
Object count = userService.getTeacherCount();
return Result.success(count);
}
/**
* 获取学生人数
*
* @return 学生人数
*/
@GetMapping("/users/students")
public Object getStudentCount() {
log.info("获取学生人数");
Object count = userService.getStudentCount();
return Result.success(count);
}
/**
* 获取比赛总数
*
* @return 比赛总数
*/
@GetMapping("/competitions/total")
public Object getCompetitionCount() {
log.info("获取比赛总数");
List<ResultCompetitionTable> competitionList = competitionService.getAllCompetitions();
int count = competitionList != null ? competitionList.size() : 0;
return Result.success(count);
}
}

View File

@ -0,0 +1,35 @@
package com.example.system.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 文件上传请求
*
* @author <a href="https://github.com/luoye6">程序员小白条</a>
*
*/
@Data
public class GenChartByAiRequest implements Serializable {
/**
* 名称
*/
private String name;
/**
* 分析目标
*/
private String goal;
/**
* 图表类型
*/
private String chartType;
/**
* 用户id
*/
private Long adminId;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,100 @@
package com.example.system.mapper;
import com.example.system.common.Chart;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class ChartMapper {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 保存图表信息
* @param chart 图表对象
* @return 受影响的行数
*/
public int saveChart(Chart chart) {
String sql = "INSERT INTO t_chart (id, name, goal, chart_data, chart_type, gen_chart, gen_result, admin_id, create_time, update_time) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
return jdbcTemplate.update(sql,
chart.getId(),
chart.getName(),
chart.getGoal(),
chart.getChartData(),
chart.getChartType(),
chart.getGenChart(),
chart.getGenResult(),
chart.getAdminId(),
chart.getCreateTime(),
chart.getUpdateTime()
);
}
/**
* 更新图表信息
* @param chart 图表对象
* @return 受影响的行数
*/
public int updateChart(Chart chart) {
String sql = "UPDATE t_chart SET " +
"name = ?, " +
"goal = ?, " +
"chart_type = ?, " +
"gen_chart = ?, " +
"gen_result = ?, " +
"update_time = ? " +
"WHERE id = ?";
return jdbcTemplate.update(sql,
chart.getName(),
chart.getGoal(),
chart.getChartType(),
chart.getGenChart(),
chart.getGenResult(),
chart.getUpdateTime(),
chart.getId()
);
}
/**
* 根据ID查询图表
* @param id 图表ID
* @return 图表对象
*/
public Chart findById(String id) {
String sql = "SELECT * FROM t_chart WHERE id = ?";
try {
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
Chart chart = new Chart();
chart.setId(rs.getString("id"));
chart.setName(rs.getString("name"));
chart.setGoal(rs.getString("goal"));
chart.setChartData(rs.getString("chart_data"));
chart.setChartType(rs.getString("chart_type"));
chart.setGenChart(rs.getString("gen_chart"));
chart.setGenResult(rs.getString("gen_result"));
chart.setAdminId(rs.getLong("admin_id"));
chart.setCreateTime(rs.getLong("create_time"));
chart.setUpdateTime(rs.getLong("update_time"));
return chart;
}, id);
} catch (Exception e) {
return null;
}
}
/**
* 删除图表
* @param id 图表ID
* @return 受影响的行数
*/
public int deleteById(String id) {
String sql = "DELETE FROM t_chart WHERE id = ?";
return jdbcTemplate.update(sql, id);
}
}

View File

@ -9,7 +9,8 @@ import java.util.List;
@Mapper @Mapper
public interface CompetitionMapper { public interface CompetitionMapper {
//获取所有竞赛信息 //获取所有竞赛信息
@Select("select * from competition_table;") // @Select("select * from competition_table;")
@Select("SELECT c.*, (SELECT COUNT(*) FROM registration_table r WHERE r.competition_id = c.competition_id) AS registration_count FROM competition_table c;")
List<ResultCompetitionTable> getCompetition(); List<ResultCompetitionTable> getCompetition();
@Select("select competition_name from competition_table where competition_id=#{competitionId}") @Select("select competition_name from competition_table where competition_id=#{competitionId}")

View File

@ -14,4 +14,5 @@ public class ResultCompetitionTable {
private Date registrationEndTime; // 注册结束时间对应数据库中的registration_end_time字段 private Date registrationEndTime; // 注册结束时间对应数据库中的registration_end_time字段
private String announcementLink; // 公告链接对应数据库中的announcement_link字段 private String announcementLink; // 公告链接对应数据库中的announcement_link字段
private Integer competitionStatus = 0; // 竞赛状态对应数据库中的competition_status字段默认为0 private Integer competitionStatus = 0; // 竞赛状态对应数据库中的competition_status字段默认为0
private String registrationCount;
} }

View File

@ -0,0 +1,24 @@
package com.example.system.pojo;
import lombok.Data;
/**
* 智能分析结果响应VO
*/
@Data
public class BiResponse {
/**
* 生成的图表数据
*/
private String genChart;
/**
* 生成的分析结论
*/
private String genResult;
/**
* 图表ID
*/
private String chartId;
}

View File

@ -0,0 +1,29 @@
package com.example.system.pojo;
import lombok.Data;
/**
* AI图表生成请求DTO
*/
@Data
public class GenChartByAiRequest {
/**
* 名称
*/
private String name;
/**
* 分析目标
*/
private String goal;
/**
* 图表类型
*/
private String chartType;
/**
* 管理员ID
*/
private Long adminId;
}

View File

@ -0,0 +1,53 @@
package com.example.system.service;
import com.example.system.common.R;
import com.example.system.common.Chart;
import com.example.system.pojo.GenChartByAiRequest;
import com.example.system.pojo.BiResponse;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* @author xiaobaitiao
* @description 针对表t_chart(图表信息表)的数据库操作Service
* @createDate 2023-08-30 11:05:22
*/
public interface ChartService {
/**
* AI生成图表
* @param multipartFile 上传的Excel文件
* @param genChartByAiRequest 生成图表请求参数
* @return 分析结果
*/
R<BiResponse> genChartByAi(MultipartFile multipartFile,
GenChartByAiRequest genChartByAiRequest);
/**
* 根据ID查询图表
* @param id 图表ID
* @return 图表信息
*/
Chart getChartById(String id);
/**
* 保存图表信息
* @param chart 图表对象
* @return 是否保存成功
*/
boolean saveChart(Chart chart);
/**
* 更新图表信息
* @param chart 图表对象
* @return 是否更新成功
*/
boolean updateChart(Chart chart);
/**
* 删除图表
* @param id 图表ID
* @return 是否删除成功
*/
boolean deleteChart(String id);
}

View File

@ -0,0 +1,225 @@
package com.example.system.service.impl;
import cn.hutool.core.io.FileUtil;
import com.example.system.common.AiPost;
import com.example.system.common.Chart;
import com.example.system.common.R;
import com.example.system.mapper.ChartMapper;
import com.example.system.pojo.GenChartByAiRequest;
import com.example.system.pojo.BiResponse;
import com.example.system.service.ChartService;
import com.example.system.utils.ExcelUtils;
import com.example.system.utils.StringUtils;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class ChartServiceImpl implements ChartService {
@Autowired
private AiPost aiPost;
@Autowired
private ChartMapper chartMapper;
@Override
public R<BiResponse> genChartByAi(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest) {
String name = genChartByAiRequest.getName();
String goal = genChartByAiRequest.getGoal();
String chartType = genChartByAiRequest.getChartType();
System.out.println(name);
System.out.println(goal);
System.out.println(chartType);
// 文件校验
long size = multipartFile.getSize();
String originalFilename = multipartFile.getOriginalFilename();
final long ONE_MB = 1024 * 1024L;
if (size > ONE_MB) {
return R.error("文件超过1MB请上传小于1MB的文件");
}
String suffix = FileUtil.getSuffix(originalFilename);
final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
if (!validFileSuffixList.contains(suffix)) {
return R.error("仅支持xlsx、xls格式的Excel文件");
}
// 构建AI输入提示
final String prompt = "你是一个数据分析师和前端开发专家,按照以下固定格式分析数据:\n" +
"分析需求:\n{数据分析的需求或者目标}\n" +
"原始数据:\n{csv格式的原始数据用,作为分隔符}\n" +
"请按以下格式生成内容(不要输出任何多余的开头、结尾、注释)\n" +
"【【【【【\n" +
"{前端 Echarts V5 的 option 配置对象js代码合理地将数据进行可视化}\n" +
"【【【【【\n" +
"{明确的数据分析结论,越详细越好}\n";
StringBuilder userInput = new StringBuilder();
userInput.append(prompt);
userInput.append("你是一个数据分析师,接下来我会给你分析目标和原始数据,请告诉我分析结论。").append("\n");
String userGoal = goal;
if (StringUtils.isNotBlank(chartType)) {
userGoal += ",请使用" + chartType;
}
userInput.append("分析目标:").append(userGoal).append("\n");
userInput.append("分析需求:").append(userGoal).append("\n");
userInput.append("原始数据: ").append("\n");
// 将Excel转为CSV
String csvData = ExcelUtils.excelToCsv(multipartFile);
userInput.append(csvData).append("\n");
String result;
try {
// 调用AI服务获取分析结果
result = aiPost.sendMessageAndGetResponse(userInput.toString(), 1024);
System.out.println("AI返回内容\n" + result);
} catch (Exception e) {
return R.error("AI分析失败" + e.getMessage());
}
// 解析AI返回内容
String[] splits = result.split("【【【【【");
if (splits.length < 3) {
return R.error("AI生成格式错误请确认返回内容完整");
}
// 提取 option JSON
String optionBlock = splits[1].trim();
Pattern jsonPattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL);
Matcher jsonMatcher = jsonPattern.matcher(optionBlock);
String genChart;
if (jsonMatcher.find()) {
genChart = jsonMatcher.group(0).trim();
} else {
return R.error("AI返回的图表配置无法识别");
}
// 提取分析结论
String genResult = splits[2].trim();
// 解析 Echarts 配置
Gson gson = new Gson();
Object objectChart;
try {
objectChart = gson.fromJson(genChart, Object.class);
} catch (Exception e) {
return R.error("图表配置JSON格式错误" + e.getMessage());
}
// 保存图表数据
Chart chart = new Chart();
chart.setId(UUID.randomUUID().toString().replace("-", ""));
chart.setName(StringUtils.isBlank(name) ? "未命名图表" : name);
chart.setGoal(goal);
chart.setChartData(csvData);
chart.setChartType(chartType);
chart.setGenChart(genChart);
chart.setGenResult(genResult);
chart.setAdminId(genChartByAiRequest.getAdminId());
chart.setCreateTime(System.currentTimeMillis());
chart.setUpdateTime(System.currentTimeMillis());
// 保存到数据库
try {
saveChart(chart);
} catch (Exception e) {
return R.error("图表保存失败:" + e.getMessage());
}
// 构造响应
BiResponse biResponse = new BiResponse();
biResponse.setGenChart(genChart);
biResponse.setGenResult(genResult);
biResponse.setChartId(chart.getId());
R<BiResponse> resultData = new R<>();
resultData.add("genChart", objectChart);
resultData.setData(biResponse);
resultData.setMsg("图表生成成功");
resultData.setStatus(200);
return resultData;
}
@Override
public Chart getChartById(String id) {
if (StringUtils.isBlank(id)) {
return null;
}
return chartMapper.findById(id);
}
@Override
public boolean saveChart(Chart chart) {
if (chart == null) {
return false;
}
// 如果ID为空生成UUID
if (StringUtils.isBlank(chart.getId())) {
chart.setId(UUID.randomUUID().toString().replace("-", ""));
}
// 设置创建和更新时间
long currentTime = System.currentTimeMillis();
if (chart.getCreateTime() == null) {
chart.setCreateTime(currentTime);
}
chart.setUpdateTime(currentTime);
try {
int result = chartMapper.saveChart(chart);
return result > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean updateChart(Chart chart) {
if (chart == null || StringUtils.isBlank(chart.getId())) {
return false;
}
// 设置更新时间
chart.setUpdateTime(System.currentTimeMillis());
try {
int result = chartMapper.updateChart(chart);
return result > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean deleteChart(String id) {
if (StringUtils.isBlank(id)) {
return false;
}
try {
int result = chartMapper.deleteById(id);
return result > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

View File

@ -64,6 +64,7 @@ public class CompetitionServiceImpl implements CompetitionService {
for (ResultCompetitionTable competitionTable : competitionTables) { for (ResultCompetitionTable competitionTable : competitionTables) {
String userName = userMapper.getNameById(competitionTable.getUserId()); String userName = userMapper.getNameById(competitionTable.getUserId());
competitionTable.setUserName(userName); competitionTable.setUserName(userName);
} }
return competitionTables; return competitionTables;
} }

View File

@ -0,0 +1,4 @@
package com.example.system.service.impl;
public class CountControllerImpl {
}

View File

@ -0,0 +1,123 @@
package com.example.system.utils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.DateFormatConverter;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Excel处理工具类
*/
public class ExcelUtils {
/**
* 将Excel文件转换为CSV格式字符串
* @param multipartFile Excel文件
* @return CSV格式字符串
*/
public static String excelToCsv(MultipartFile multipartFile) {
try (InputStream inputStream = multipartFile.getInputStream()) {
// 创建工作簿
Workbook workbook = WorkbookFactory.create(inputStream);
// 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 存储CSV行
List<String> csvLines = new ArrayList<>();
// 遍历行
for (Row row : sheet) {
List<String> cellValues = new ArrayList<>();
// 遍历单元格
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
cellValues.add(getCellValueAsString(cell));
}
// 生成CSV行用逗号分隔
csvLines.add(cellValues.stream().collect(Collectors.joining(",")));
}
// 将所有行连接起来每行以换行符分隔
return String.join("\n", csvLines);
} catch (IOException e) {
throw new RuntimeException("Excel文件处理失败: " + e.getMessage(), e);
}
}
/**
* 处理CSV文件
* @param multipartFile CSV文件
* @return CSV内容字符串
*/
public static String processCsv(MultipartFile multipartFile) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(multipartFile.getInputStream()))) {
return reader.lines().collect(Collectors.joining("\n"));
} catch (IOException e) {
throw new RuntimeException("CSV文件处理失败: " + e.getMessage(), e);
}
}
/**
* 获取单元格的字符串值
* @param cell 单元格
* @return 单元格的字符串表示
*/
private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
// 避免数值显示为科学计数法
double value = cell.getNumericCellValue();
if (value == Math.floor(value)) {
return String.valueOf((long) value);
} else {
return String.valueOf(value);
}
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return String.valueOf(cell.getNumericCellValue());
} catch (Exception e) {
try {
return cell.getStringCellValue();
} catch (Exception ex) {
return cell.getCellFormula();
}
}
default:
return "";
}
}
/**
* 检查是否为Excel文件
* @param filename 文件名
* @return 是否为Excel文件
*/
public static boolean isExcelFile(String filename) {
if (filename == null) {
return false;
}
String lowerCase = filename.toLowerCase();
return lowerCase.endsWith(".xlsx") || lowerCase.endsWith(".xls");
}
}

View File

@ -0,0 +1,207 @@
package com.example.system.utils;
/**
* 自定义字符串工具类代替 org.apache.commons.lang3.StringUtils
*/
public class StringUtils {
/**
* 检查字符串是否为空白null空字符串或只包含空白字符
*
* @param str 待检查的字符串
* @return 如果字符串为空白则返回true
*/
public static boolean isBlank(String str) {
if (str == null) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* 检查字符串是否不为空白
*
* @param str 待检查的字符串
* @return 如果字符串不为空白则返回true
*/
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
/**
* 检查字符串是否为空null或空字符串
*
* @param str 待检查的字符串
* @return 如果字符串为空则返回true
*/
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
/**
* 检查字符串是否不为空
*
* @param str 待检查的字符串
* @return 如果字符串不为空则返回true
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* 安全地获取字符串长度如果为null则返回0
*
* @param str 待检查的字符串
* @return 字符串长度如果为null则返回0
*/
public static int length(String str) {
return str == null ? 0 : str.length();
}
/**
* 将null字符串转为空字符串
*
* @param str 待处理的字符串
* @return 处理后的字符串如果为null则返回空字符串
*/
public static String nullToEmpty(String str) {
return str == null ? "" : str;
}
/**
* 截取字符串处理各种边界情况
*
* @param str 待处理的字符串
* @param start 起始位置
* @return 截取后的字符串
*/
public static String substring(String str, int start) {
if (str == null) {
return null;
}
// 处理负数索引
if (start < 0) {
start = str.length() + start;
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return "";
}
return str.substring(start);
}
/**
* 截取字符串处理各种边界情况
*
* @param str 待处理的字符串
* @param start 起始位置
* @param end 结束位置
* @return 截取后的字符串
*/
public static String substring(String str, int start, int end) {
if (str == null) {
return null;
}
// 处理负数索引
if (start < 0) {
start = str.length() + start;
}
if (end < 0) {
end = str.length() + end;
}
// 处理边界情况
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return "";
}
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
/**
* 去除字符串两端的空白字符
*
* @param str 待处理的字符串
* @return 处理后的字符串
*/
public static String trim(String str) {
return str == null ? null : str.trim();
}
/**
* 判断字符串是否以指定前缀开始忽略大小写
*
* @param str 待检查的字符串
* @param prefix 前缀
* @return 如果以指定前缀开始则返回true
*/
public static boolean startsWithIgnoreCase(String str, String prefix) {
if (str == null || prefix == null) {
return str == null && prefix == null;
}
if (prefix.length() > str.length()) {
return false;
}
return str.regionMatches(true, 0, prefix, 0, prefix.length());
}
/**
* 判断字符串是否以指定后缀结束忽略大小写
*
* @param str 待检查的字符串
* @param suffix 后缀
* @return 如果以指定后缀结束则返回true
*/
public static boolean endsWithIgnoreCase(String str, String suffix) {
if (str == null || suffix == null) {
return str == null && suffix == null;
}
if (suffix.length() > str.length()) {
return false;
}
return str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length());
}
/**
* 判断字符串是否包含另一个字符串忽略大小写
*
* @param str 待检查的字符串
* @param searchStr 待查找的字符串
* @return 如果包含则返回true
*/
public static boolean containsIgnoreCase(String str, String searchStr) {
if (str == null || searchStr == null) {
return false;
}
int len = searchStr.length();
int max = str.length() - len;
for (int i = 0; i <= max; i++) {
if (str.regionMatches(true, i, searchStr, 0, len)) {
return true;
}
}
return false;
}
}

View File

@ -15,10 +15,10 @@ spring:
password: Aa123456... password: Aa123456...
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
hikari: hikari:
connection-timeout: 10000 # 设置连接超时时间为10秒 connection-timeout: 10000
maximum-pool-size: 200 # 根据实际情况调整 maximum-pool-size: 200
minimum-idle: 5 # 设置最小空闲连接数 minimum-idle: 5
idle-timeout: 30000 # 设置连接空闲超时时间为30秒 idle-timeout: 30000
servlet: servlet:
multipart: multipart:
max-file-size: 500MB max-file-size: 500MB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.