feat: 统计接口 & 智能分析接口实现
This commit is contained in:
parent
362d28994b
commit
1b98b1bd66
14
.idea/dataSources.xml
generated
14
.idea/dataSources.xml
generated
@ -14,5 +14,19 @@
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</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&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>
|
||||
</project>
|
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal 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>
|
@ -30,6 +30,11 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
80
System/src/main/java/com/example/system/common/AiPost.java
Normal file
80
System/src/main/java/com/example/system/common/AiPost.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
59
System/src/main/java/com/example/system/common/Chart.java
Normal file
59
System/src/main/java/com/example/system/common/Chart.java
Normal 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;
|
||||
}
|
145
System/src/main/java/com/example/system/common/R.java
Normal file
145
System/src/main/java/com/example/system/common/R.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
100
System/src/main/java/com/example/system/mapper/ChartMapper.java
Normal file
100
System/src/main/java/com/example/system/mapper/ChartMapper.java
Normal 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);
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ import java.util.List;
|
||||
@Mapper
|
||||
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();
|
||||
|
||||
@Select("select competition_name from competition_table where competition_id=#{competitionId}")
|
||||
|
@ -14,4 +14,5 @@ public class ResultCompetitionTable {
|
||||
private Date registrationEndTime; // 注册结束时间,对应数据库中的registration_end_time字段。
|
||||
private String announcementLink; // 公告链接,对应数据库中的announcement_link字段。
|
||||
private Integer competitionStatus = 0; // 竞赛状态,对应数据库中的competition_status字段,默认为0。
|
||||
private String registrationCount;
|
||||
}
|
||||
|
24
System/src/main/java/com/example/system/pojo/BiResponse.java
Normal file
24
System/src/main/java/com/example/system/pojo/BiResponse.java
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@ public class CompetitionServiceImpl implements CompetitionService {
|
||||
for (ResultCompetitionTable competitionTable : competitionTables) {
|
||||
String userName = userMapper.getNameById(competitionTable.getUserId());
|
||||
competitionTable.setUserName(userName);
|
||||
|
||||
}
|
||||
return competitionTables;
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
package com.example.system.service.impl;
|
||||
|
||||
public class CountControllerImpl {
|
||||
}
|
123
System/src/main/java/com/example/system/utils/ExcelUtils.java
Normal file
123
System/src/main/java/com/example/system/utils/ExcelUtils.java
Normal 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");
|
||||
}
|
||||
}
|
207
System/src/main/java/com/example/system/utils/StringUtils.java
Normal file
207
System/src/main/java/com/example/system/utils/StringUtils.java
Normal 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;
|
||||
}
|
||||
}
|
@ -15,10 +15,10 @@ spring:
|
||||
password: Aa123456...
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
hikari:
|
||||
connection-timeout: 10000 # 设置连接超时时间为10秒
|
||||
maximum-pool-size: 200 # 根据实际情况调整
|
||||
minimum-idle: 5 # 设置最小空闲连接数
|
||||
idle-timeout: 30000 # 设置连接空闲超时时间为30秒
|
||||
connection-timeout: 10000
|
||||
maximum-pool-size: 200
|
||||
minimum-idle: 5
|
||||
idle-timeout: 30000
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 500MB
|
||||
|
6307
spring-blog.log
6307
spring-blog.log
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
spring-blog.log.2025-03-30.0.gz
Normal file
BIN
spring-blog.log.2025-03-30.0.gz
Normal file
Binary file not shown.
BIN
spring-blog.log.2025-04-06.0.gz
Normal file
BIN
spring-blog.log.2025-04-06.0.gz
Normal file
Binary file not shown.
BIN
spring-blog.log.2025-05-16.0.gz
Normal file
BIN
spring-blog.log.2025-05-16.0.gz
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user