对话接口bug修复及后台管理功能完善
This commit is contained in:
@@ -6,7 +6,9 @@ import com.emotion.dto.request.AdminCreateRequest;
|
|||||||
import com.emotion.dto.request.AdminPageRequest;
|
import com.emotion.dto.request.AdminPageRequest;
|
||||||
import com.emotion.dto.request.AdminUpdateRequest;
|
import com.emotion.dto.request.AdminUpdateRequest;
|
||||||
import com.emotion.dto.response.AdminResponse;
|
import com.emotion.dto.response.AdminResponse;
|
||||||
|
import com.emotion.dto.response.DashboardStatsResponse;
|
||||||
import com.emotion.service.AdminService;
|
import com.emotion.service.AdminService;
|
||||||
|
import com.emotion.service.DashboardService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -27,6 +29,9 @@ public class AdminController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AdminService adminService;
|
private AdminService adminService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DashboardService dashboardService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询管理员
|
* 分页查询管理员
|
||||||
*/
|
*/
|
||||||
@@ -88,4 +93,54 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
return Result.success("删除成功", null);
|
return Result.success("删除成功", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仪表盘统计数据
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取仪表盘统计数据", description = "获取管理后台仪表盘的统计数据,包括用户、内容、AI服务和系统统计")
|
||||||
|
@GetMapping("/dashboard/stats")
|
||||||
|
public Result<DashboardStatsResponse> getDashboardStats() {
|
||||||
|
DashboardStatsResponse stats = dashboardService.getDashboardStats();
|
||||||
|
return Result.success("获取成功", stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户统计数据
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取用户统计数据", description = "获取用户相关的统计数据")
|
||||||
|
@GetMapping("/dashboard/user-stats")
|
||||||
|
public Result<DashboardStatsResponse.UserStats> getUserStats() {
|
||||||
|
DashboardStatsResponse.UserStats userStats = dashboardService.getUserStats();
|
||||||
|
return Result.success("获取成功", userStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容统计数据
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取内容统计数据", description = "获取内容相关的统计数据")
|
||||||
|
@GetMapping("/dashboard/content-stats")
|
||||||
|
public Result<DashboardStatsResponse.ContentStats> getContentStats() {
|
||||||
|
DashboardStatsResponse.ContentStats contentStats = dashboardService.getContentStats();
|
||||||
|
return Result.success("获取成功", contentStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AI服务统计数据
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取AI服务统计数据", description = "获取AI服务相关的统计数据")
|
||||||
|
@GetMapping("/dashboard/ai-stats")
|
||||||
|
public Result<DashboardStatsResponse.AiServiceStats> getAiServiceStats() {
|
||||||
|
DashboardStatsResponse.AiServiceStats aiStats = dashboardService.getAiServiceStats();
|
||||||
|
return Result.success("获取成功", aiStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统统计数据
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取系统统计数据", description = "获取系统相关的统计数据")
|
||||||
|
@GetMapping("/dashboard/system-stats")
|
||||||
|
public Result<DashboardStatsResponse.SystemStats> getSystemStats() {
|
||||||
|
DashboardStatsResponse.SystemStats systemStats = dashboardService.getSystemStats();
|
||||||
|
return Result.success("获取成功", systemStats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.emotion.dto.response;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仪表盘统计数据响应
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2025-10-31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "仪表盘统计数据响应")
|
||||||
|
public class DashboardStatsResponse {
|
||||||
|
|
||||||
|
@Schema(description = "用户统计")
|
||||||
|
private UserStats userStats;
|
||||||
|
|
||||||
|
@Schema(description = "内容统计")
|
||||||
|
private ContentStats contentStats;
|
||||||
|
|
||||||
|
@Schema(description = "AI服务统计")
|
||||||
|
private AiServiceStats aiServiceStats;
|
||||||
|
|
||||||
|
@Schema(description = "系统统计")
|
||||||
|
private SystemStats systemStats;
|
||||||
|
|
||||||
|
@Schema(description = "最近活动")
|
||||||
|
private List<RecentActivity> recentActivities;
|
||||||
|
|
||||||
|
@Schema(description = "数据更新时间")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户统计
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "用户统计")
|
||||||
|
public static class UserStats {
|
||||||
|
@Schema(description = "总用户数")
|
||||||
|
private Long totalUsers;
|
||||||
|
|
||||||
|
@Schema(description = "今日新增用户")
|
||||||
|
private Long todayNewUsers;
|
||||||
|
|
||||||
|
@Schema(description = "活跃用户数")
|
||||||
|
private Long activeUsers;
|
||||||
|
|
||||||
|
@Schema(description = "访客用户数")
|
||||||
|
private Long guestUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容统计
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "内容统计")
|
||||||
|
public static class ContentStats {
|
||||||
|
@Schema(description = "总对话数")
|
||||||
|
private Long totalConversations;
|
||||||
|
|
||||||
|
@Schema(description = "总消息数")
|
||||||
|
private Long totalMessages;
|
||||||
|
|
||||||
|
@Schema(description = "日记帖子数")
|
||||||
|
private Long diaryPosts;
|
||||||
|
|
||||||
|
@Schema(description = "社区帖子数")
|
||||||
|
private Long communityPosts;
|
||||||
|
|
||||||
|
@Schema(description = "情绪记录数")
|
||||||
|
private Long emotionRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI服务统计
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "AI服务统计")
|
||||||
|
public static class AiServiceStats {
|
||||||
|
@Schema(description = "总API调用次数")
|
||||||
|
private Long totalApiCalls;
|
||||||
|
|
||||||
|
@Schema(description = "今日API调用次数")
|
||||||
|
private Long todayApiCalls;
|
||||||
|
|
||||||
|
@Schema(description = "成功调用次数")
|
||||||
|
private Long successfulCalls;
|
||||||
|
|
||||||
|
@Schema(description = "失败调用次数")
|
||||||
|
private Long failedCalls;
|
||||||
|
|
||||||
|
@Schema(description = "平均响应时间(ms)")
|
||||||
|
private Double avgResponseTime;
|
||||||
|
|
||||||
|
@Schema(description = "AI配置数量")
|
||||||
|
private Long aiConfigCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统统计
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "系统统计")
|
||||||
|
public static class SystemStats {
|
||||||
|
@Schema(description = "管理员数量")
|
||||||
|
private Long adminCount;
|
||||||
|
|
||||||
|
@Schema(description = "成就数量")
|
||||||
|
private Long achievementCount;
|
||||||
|
|
||||||
|
@Schema(description = "奖励数量")
|
||||||
|
private Long rewardCount;
|
||||||
|
|
||||||
|
@Schema(description = "系统运行时间")
|
||||||
|
private String uptime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最近活动
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "最近活动")
|
||||||
|
public static class RecentActivity {
|
||||||
|
@Schema(description = "活动类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "活动描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "活动时间")
|
||||||
|
private LocalDateTime activityTime;
|
||||||
|
|
||||||
|
@Schema(description = "额外数据")
|
||||||
|
private Map<String, Object> extraData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.emotion.service;
|
||||||
|
|
||||||
|
import com.emotion.dto.response.DashboardStatsResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仪表盘服务接口
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2025-10-31
|
||||||
|
*/
|
||||||
|
public interface DashboardService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仪表盘统计数据
|
||||||
|
*
|
||||||
|
* @return 仪表盘统计数据
|
||||||
|
*/
|
||||||
|
DashboardStatsResponse getDashboardStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户统计数据
|
||||||
|
*
|
||||||
|
* @return 用户统计数据
|
||||||
|
*/
|
||||||
|
DashboardStatsResponse.UserStats getUserStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容统计数据
|
||||||
|
*
|
||||||
|
* @return 内容统计数据
|
||||||
|
*/
|
||||||
|
DashboardStatsResponse.ContentStats getContentStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AI服务统计数据
|
||||||
|
*
|
||||||
|
* @return AI服务统计数据
|
||||||
|
*/
|
||||||
|
DashboardStatsResponse.AiServiceStats getAiServiceStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统统计数据
|
||||||
|
*
|
||||||
|
* @return 系统统计数据
|
||||||
|
*/
|
||||||
|
DashboardStatsResponse.SystemStats getSystemStats();
|
||||||
|
}
|
||||||
@@ -842,6 +842,7 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
private String executeStreamCozeApiCall(CozeApiCall apiCall, AiConfig config, Map<String, Object> requestBody,
|
private String executeStreamCozeApiCall(CozeApiCall apiCall, AiConfig config, Map<String, Object> requestBody,
|
||||||
String conversationId, String userMessage, String userId) {
|
String conversationId, String userMessage, String userId) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 构建请求头
|
// 构建请求头
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.set("Authorization", "Bearer " + config.getApiToken());
|
headers.set("Authorization", "Bearer " + config.getApiToken());
|
||||||
@@ -854,7 +855,9 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
// 更新API调用记录的请求信息
|
// 更新API调用记录的请求信息
|
||||||
updateApiCallRequest(apiCall, cozeApiUrl, requestBody, headers);
|
updateApiCallRequest(apiCall, cozeApiUrl, requestBody, headers);
|
||||||
|
|
||||||
log.info("发送Coze流式请求到: {}, 请求体: {}", cozeApiUrl, requestBody);
|
log.info("发送Coze流式请求到: {}", cozeApiUrl);
|
||||||
|
log.info("请求头: {}", headers.toSingleValueMap());
|
||||||
|
log.info("请求体: {}", JSON.toJSONString(requestBody));
|
||||||
|
|
||||||
// 使用RestTemplate处理流式响应
|
// 使用RestTemplate处理流式响应
|
||||||
String streamResponse = handleStreamResponse(cozeApiUrl, headers, requestBody, apiCall);
|
String streamResponse = handleStreamResponse(cozeApiUrl, headers, requestBody, apiCall);
|
||||||
@@ -878,30 +881,37 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
*/
|
*/
|
||||||
private String handleStreamResponse(String url, HttpHeaders headers, Map<String, Object> requestBody, CozeApiCall apiCall) {
|
private String handleStreamResponse(String url, HttpHeaders headers, Map<String, Object> requestBody, CozeApiCall apiCall) {
|
||||||
try {
|
try {
|
||||||
// 创建HTTP客户端
|
log.info("开始处理流式响应,URL: {}", url);
|
||||||
|
|
||||||
|
// 创建HTTP客户端,增加更长的超时时间
|
||||||
java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder()
|
java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder()
|
||||||
.connectTimeout(java.time.Duration.ofSeconds(30))
|
.connectTimeout(java.time.Duration.ofSeconds(60))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 构建请求
|
// 构建请求
|
||||||
java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
|
java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
|
||||||
.uri(java.net.URI.create(url))
|
.uri(java.net.URI.create(url))
|
||||||
.timeout(java.time.Duration.ofMinutes(5))
|
.timeout(java.time.Duration.ofMinutes(10))
|
||||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(JSON.toJSONString(requestBody)));
|
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(JSON.toJSONString(requestBody)));
|
||||||
|
|
||||||
// 添加请求头
|
// 添加请求头
|
||||||
|
log.info("设置请求头:");
|
||||||
headers.forEach((key, values) -> {
|
headers.forEach((key, values) -> {
|
||||||
if (values != null && !values.isEmpty()) {
|
if (values != null && !values.isEmpty()) {
|
||||||
requestBuilder.header(key, values.get(0));
|
String value = values.get(0);
|
||||||
|
requestBuilder.header(key, value);
|
||||||
|
log.info(" {}: {}", key, key.equals("Authorization") ? "Bearer ***" : value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
java.net.http.HttpRequest request = requestBuilder.build();
|
java.net.http.HttpRequest request = requestBuilder.build();
|
||||||
|
log.info("发送流式请求,请求体: {}", JSON.toJSONString(requestBody));
|
||||||
|
|
||||||
// 发送请求并处理流式响应
|
// 发送请求并处理流式响应
|
||||||
StringBuilder responseBuilder = new StringBuilder();
|
StringBuilder responseBuilder = new StringBuilder();
|
||||||
StringBuilder fullStreamData = new StringBuilder();
|
StringBuilder fullStreamData = new StringBuilder();
|
||||||
final String[] currentEvent = {null}; // 使用数组来解决lambda中的final问题
|
String currentEvent = null;
|
||||||
|
boolean hasReceivedData = false;
|
||||||
|
|
||||||
java.net.http.HttpResponse<java.util.stream.Stream<String>> response = client.send(request,
|
java.net.http.HttpResponse<java.util.stream.Stream<String>> response = client.send(request,
|
||||||
java.net.http.HttpResponse.BodyHandlers.ofLines());
|
java.net.http.HttpResponse.BodyHandlers.ofLines());
|
||||||
@@ -911,93 +921,198 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
if (response.statusCode() != 200) {
|
if (response.statusCode() != 200) {
|
||||||
String errorBody = response.body().collect(java.util.stream.Collectors.joining("\n"));
|
String errorBody = response.body().collect(java.util.stream.Collectors.joining("\n"));
|
||||||
log.error("流式请求失败,状态码: {}, 响应: {}", response.statusCode(), errorBody);
|
log.error("流式请求失败,状态码: {}, 响应: {}", response.statusCode(), errorBody);
|
||||||
return "流式请求失败,状态码: " + response.statusCode();
|
updateApiCallError(apiCall, "HTTP_ERROR", "流式请求失败,状态码: " + response.statusCode());
|
||||||
|
return handleNonStreamFallback(url, headers, requestBody, apiCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理流式数据 - 正确处理SSE格式
|
// 处理流式数据 - 正确处理SSE格式
|
||||||
response.body().forEach(line -> {
|
java.util.Iterator<String> lineIterator = response.body().iterator();
|
||||||
|
while (lineIterator.hasNext()) {
|
||||||
|
String line = lineIterator.next();
|
||||||
fullStreamData.append(line).append("\n");
|
fullStreamData.append(line).append("\n");
|
||||||
log.debug("收到流式数据行: {}", line);
|
|
||||||
|
if (line.trim().isEmpty()) {
|
||||||
|
// 空行表示事件结束,重置当前事件
|
||||||
|
currentEvent = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理SSE事件格式
|
// 处理SSE事件格式
|
||||||
if (line.startsWith("event:")) {
|
if (line.startsWith("event:")) {
|
||||||
currentEvent[0] = line.substring(6).trim();
|
currentEvent = line.substring(6).trim();
|
||||||
log.debug("当前事件类型: {}", currentEvent[0]);
|
log.debug("当前事件类型: {}", currentEvent);
|
||||||
} else if (line.startsWith("data:")) {
|
} else if (line.startsWith("data:")) {
|
||||||
String data = line.substring(6).trim();
|
String data = line.substring(5).trim(); // 注意这里是5,因为"data:"后面可能没有空格
|
||||||
log.debug("解析流式数据: {}", data);
|
|
||||||
|
|
||||||
// 检查是否为结束标记
|
// 检查是否为结束标记
|
||||||
if ("[DONE]".equals(data)) {
|
if ("\"[DONE]\"".equals(data) || "[DONE]".equals(data)) {
|
||||||
log.info("流式响应完成");
|
log.info("收到流式响应结束标记");
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只处理消息增量事件
|
// 跳过空数据
|
||||||
if ("conversation.message.delta".equals(currentEvent[0])) {
|
if (data.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReceivedData = true;
|
||||||
|
log.debug("解析流式数据,事件: {}, 数据: {}", currentEvent, data);
|
||||||
|
|
||||||
|
// 处理不同类型的事件
|
||||||
|
if ("conversation.message.delta".equals(currentEvent)) {
|
||||||
try {
|
try {
|
||||||
JSONObject jsonData = JSON.parseObject(data);
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
log.debug("解析后的JSON数据: {}", jsonData);
|
|
||||||
|
|
||||||
// 提取content字段
|
// 提取content字段并检查类型
|
||||||
if (jsonData.containsKey("content")) {
|
if (jsonData.containsKey("content") && jsonData.containsKey("type")) {
|
||||||
|
String messageType = jsonData.getString("type");
|
||||||
String content = jsonData.getString("content");
|
String content = jsonData.getString("content");
|
||||||
if (content != null && !content.trim().isEmpty()) {
|
|
||||||
|
// 只处理answer类型的消息内容
|
||||||
|
if ("answer".equals(messageType) && content != null && !content.trim().isEmpty()) {
|
||||||
log.debug("提取增量内容: {}", content);
|
log.debug("提取增量内容: {}", content);
|
||||||
responseBuilder.append(content);
|
responseBuilder.append(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("解析流式数据失败: {}, 数据: {}", e.getMessage(), data);
|
log.warn("解析增量消息数据失败: {}, 数据: {}", e.getMessage(), data);
|
||||||
}
|
}
|
||||||
} else if ("conversation.message.completed".equals(currentEvent[0])) {
|
} else if ("conversation.message.completed".equals(currentEvent)) {
|
||||||
// 处理完整消息事件,可以用作备用方案
|
// 处理完整消息事件,作为备用方案
|
||||||
try {
|
try {
|
||||||
JSONObject jsonData = JSON.parseObject(data);
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
|
|
||||||
// 检查是否为answer类型的消息
|
// 检查是否为answer类型的消息
|
||||||
if (jsonData.containsKey("type") && "answer".equals(jsonData.getString("type"))) {
|
if (jsonData.containsKey("type") && "answer".equals(jsonData.getString("type"))) {
|
||||||
String content = jsonData.getString("content");
|
String content = jsonData.getString("content");
|
||||||
if (content != null && !content.trim().isEmpty() && responseBuilder.length() == 0) {
|
if (content != null && !content.trim().isEmpty()) {
|
||||||
log.debug("使用完整消息内容作为备用: {}", content);
|
// 如果增量方式没有获取到内容,使用完整消息作为备用
|
||||||
|
if (responseBuilder.length() == 0) {
|
||||||
|
log.info("使用完整消息内容作为备用: {}", content);
|
||||||
responseBuilder.append(content);
|
responseBuilder.append(content);
|
||||||
}
|
}
|
||||||
|
// 记录完整消息用于验证
|
||||||
|
log.debug("完整消息内容: {}", content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("解析完整消息数据失败: {}, 数据: {}", e.getMessage(), data);
|
log.warn("解析完整消息数据失败: {}, 数据: {}", e.getMessage(), data);
|
||||||
}
|
}
|
||||||
}
|
} else if ("conversation.chat.failed".equals(currentEvent)) {
|
||||||
|
// 处理聊天失败事件
|
||||||
|
try {
|
||||||
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
|
if (jsonData.containsKey("last_error")) {
|
||||||
|
JSONObject lastError = jsonData.getJSONObject("last_error");
|
||||||
|
String errorCode = lastError.getString("code");
|
||||||
|
String errorMsg = lastError.getString("msg");
|
||||||
|
log.error("Coze流式聊天失败: code={}, msg={}", errorCode, errorMsg);
|
||||||
|
|
||||||
// 重置当前事件
|
// 如果是参数错误,直接返回错误信息,不再尝试降级处理
|
||||||
currentEvent[0] = null;
|
if ("4000".equals(errorCode)) {
|
||||||
} else if (line.trim().isEmpty()) {
|
updateApiCallError(apiCall, "COZE_PARAM_ERROR", "参数错误: " + errorMsg);
|
||||||
// 空行表示事件结束
|
return "抱歉,请求参数有误,请检查配置后重试。";
|
||||||
currentEvent[0] = null;
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析聊天失败数据失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
} else if ("conversation.chat.created".equals(currentEvent)) {
|
||||||
|
// 记录聊天创建信息
|
||||||
|
try {
|
||||||
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
|
String chatId = jsonData.getString("id");
|
||||||
|
String conversationId = jsonData.getString("conversation_id");
|
||||||
|
if (chatId != null && conversationId != null) {
|
||||||
|
updateApiCallCozeIds(apiCall, chatId, conversationId);
|
||||||
|
log.info("流式聊天创建成功,chatId: {}, conversationId: {}", chatId, conversationId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析聊天创建数据失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
} else if ("conversation.chat.completed".equals(currentEvent)) {
|
||||||
|
// 记录聊天完成信息
|
||||||
|
try {
|
||||||
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
|
log.info("流式聊天完成,状态: {}", jsonData.getString("status"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析聊天完成数据失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// 记录完整的流式数据用于调试
|
// 记录完整的流式数据用于调试
|
||||||
updateApiCallStreamData(apiCall, fullStreamData.toString());
|
updateApiCallStreamData(apiCall, fullStreamData.toString());
|
||||||
log.info("流式响应处理完成,提取内容长度: {}", responseBuilder.length());
|
log.info("流式响应处理完成,提取内容长度: {}, 是否接收到数据: {}", responseBuilder.length(), hasReceivedData);
|
||||||
|
|
||||||
String finalResponse = responseBuilder.toString();
|
String finalResponse = responseBuilder.toString();
|
||||||
if (finalResponse.isEmpty()) {
|
if (finalResponse.isEmpty()) {
|
||||||
|
if (hasReceivedData) {
|
||||||
|
log.warn("流式响应处理完成但内容为空,可能是消息类型过滤导致");
|
||||||
|
// 尝试从完整流式数据中提取最后一个完整消息
|
||||||
|
String fallbackContent = extractContentFromStreamData(fullStreamData.toString());
|
||||||
|
if (fallbackContent != null && !fallbackContent.isEmpty()) {
|
||||||
|
log.info("从流式数据中提取到备用内容: {}", fallbackContent);
|
||||||
|
return fallbackContent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("流式响应未接收到任何数据");
|
||||||
|
}
|
||||||
|
|
||||||
log.warn("流式响应为空,尝试降级到非流式处理");
|
log.warn("流式响应为空,尝试降级到非流式处理");
|
||||||
// 降级到非流式处理
|
|
||||||
return handleNonStreamFallback(url, headers, requestBody, apiCall);
|
return handleNonStreamFallback(url, headers, requestBody, apiCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalResponse;
|
return finalResponse;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理流式响应失败", e);
|
log.error("处理流式响应失败: {}", e.getMessage(), e);
|
||||||
|
updateApiCallError(apiCall, "STREAM_PROCESSING_ERROR", e.getMessage());
|
||||||
// 降级到非流式处理
|
// 降级到非流式处理
|
||||||
return handleNonStreamFallback(url, headers, requestBody, apiCall);
|
return handleNonStreamFallback(url, headers, requestBody, apiCall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从流式数据中提取内容(备用方案)
|
||||||
|
*/
|
||||||
|
private String extractContentFromStreamData(String streamData) {
|
||||||
|
try {
|
||||||
|
// 查找最后一个conversation.message.completed事件
|
||||||
|
String[] lines = streamData.split("\n");
|
||||||
|
String lastCompletedContent = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
String line = lines[i].trim();
|
||||||
|
if ("event:conversation.message.completed".equals(line) && i + 1 < lines.length) {
|
||||||
|
String dataLine = lines[i + 1].trim();
|
||||||
|
if (dataLine.startsWith("data:")) {
|
||||||
|
String data = dataLine.substring(5).trim();
|
||||||
|
try {
|
||||||
|
JSONObject jsonData = JSON.parseObject(data);
|
||||||
|
if ("answer".equals(jsonData.getString("type"))) {
|
||||||
|
String content = jsonData.getString("content");
|
||||||
|
if (content != null && !content.trim().isEmpty()) {
|
||||||
|
lastCompletedContent = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("解析备用内容失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastCompletedContent;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("从流式数据提取内容失败: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 非流式处理降级方案
|
* 非流式处理降级方案
|
||||||
*/
|
*/
|
||||||
@@ -1019,7 +1134,7 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
nonStreamHeaders.set("Content-Type", "application/json");
|
nonStreamHeaders.set("Content-Type", "application/json");
|
||||||
|
|
||||||
HttpEntity<Map<String, Object>> request = new HttpEntity<>(nonStreamBody, nonStreamHeaders);
|
HttpEntity<Map<String, Object>> request = new HttpEntity<>(nonStreamBody, nonStreamHeaders);
|
||||||
log.info("发送非流式降级请求到: {}", url);
|
log.info("发送非流式降级请求到: {}, 请求体: {}", url, JSON.toJSONString(nonStreamBody));
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
@@ -1028,7 +1143,10 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
request,
|
request,
|
||||||
String.class);
|
String.class);
|
||||||
|
|
||||||
log.info("收到非流式降级响应: {}", response.getBody());
|
log.info("收到非流式降级响应状态: {}, 响应体: {}", response.getStatusCode(), response.getBody());
|
||||||
|
|
||||||
|
// 更新API调用记录的响应信息
|
||||||
|
updateApiCallResponse(apiCall, response);
|
||||||
|
|
||||||
// 解析响应获取chat_id和conversation_id
|
// 解析响应获取chat_id和conversation_id
|
||||||
JSONObject responseJson = JSON.parseObject(response.getBody());
|
JSONObject responseJson = JSON.parseObject(response.getBody());
|
||||||
@@ -1044,12 +1162,14 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
log.info("非流式降级处理成功: reply={}", aiReply);
|
log.info("非流式降级处理成功: reply={}", aiReply);
|
||||||
return aiReply;
|
return aiReply;
|
||||||
} else {
|
} else {
|
||||||
log.error("非流式降级处理失败:无法从响应中获取chat_id或conversation_id");
|
log.error("非流式降级处理失败:无法从响应中获取chat_id或conversation_id,响应: {}", response.getBody());
|
||||||
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
updateApiCallError(apiCall, "INVALID_RESPONSE", "无法从响应中获取chat_id或conversation_id");
|
||||||
|
return "抱歉,AI服务响应异常,请稍后再试。";
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("非流式降级处理失败", e);
|
log.error("非流式降级处理失败: {}", e.getMessage(), e);
|
||||||
|
updateApiCallError(apiCall, "FALLBACK_ERROR", e.getMessage());
|
||||||
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1162,43 +1282,43 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
*/
|
*/
|
||||||
private Map<String, Object> buildCozeRequestWithConfig(String conversationId, String userMessage, String userId, AiConfig config) {
|
private Map<String, Object> buildCozeRequestWithConfig(String conversationId, String userMessage, String userId, AiConfig config) {
|
||||||
Map<String, Object> cozeRequest = new HashMap<>();
|
Map<String, Object> cozeRequest = new HashMap<>();
|
||||||
|
|
||||||
|
// 严格按照成功示例的字段顺序和格式构建请求
|
||||||
cozeRequest.put("bot_id", config.getBotId());
|
cozeRequest.put("bot_id", config.getBotId());
|
||||||
|
|
||||||
// 如果有workflow_id,则添加
|
|
||||||
if (config.getWorkflowId() != null && !config.getWorkflowId().trim().isEmpty()) {
|
|
||||||
cozeRequest.put("workflow_id", config.getWorkflowId());
|
|
||||||
}
|
|
||||||
|
|
||||||
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
||||||
|
|
||||||
// 根据配置决定是否使用流式输出
|
// 根据配置决定是否使用流式输出
|
||||||
boolean useStream = config.getSupportStream() != null && config.getSupportStream() == 1;
|
boolean useStream = config.getSupportStream() != null && config.getSupportStream() == 1;
|
||||||
cozeRequest.put("stream", useStream);
|
cozeRequest.put("stream", useStream);
|
||||||
|
|
||||||
// 添加auto_save_history参数,确保对话历史被保存
|
// 构建消息列表 - 严格按照成功示例的格式
|
||||||
cozeRequest.put("auto_save_history", true);
|
|
||||||
|
|
||||||
// 如果有conversationId,添加到请求中(但不是必需的,Coze会自动创建)
|
|
||||||
// 注意:这里的conversationId是我们系统内部的ID,不是Coze的conversation_id
|
|
||||||
// Coze会自动创建新的conversation_id
|
|
||||||
|
|
||||||
// 构建消息列表 - 按照 Coze API 标准格式
|
|
||||||
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
||||||
|
|
||||||
// 添加当前用户消息
|
// 添加当前用户消息,格式完全按照成功示例
|
||||||
Map<String, Object> currentMsg = new HashMap<>();
|
Map<String, Object> currentMsg = new HashMap<>();
|
||||||
currentMsg.put(ROLE_KEY, USER_ROLE);
|
currentMsg.put("role", "user");
|
||||||
currentMsg.put(CONTENT_KEY, userMessage);
|
currentMsg.put("content", userMessage);
|
||||||
currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE);
|
currentMsg.put("content_type", "text");
|
||||||
// 移除type字段,可能不是必需的
|
currentMsg.put("type", "question"); // 根据成功示例添加type字段
|
||||||
messages.add(currentMsg);
|
messages.add(currentMsg);
|
||||||
|
|
||||||
cozeRequest.put("additional_messages", messages);
|
cozeRequest.put("additional_messages", messages);
|
||||||
|
|
||||||
// 确保parameters不为空,添加一些默认参数
|
// 添加空的parameters对象(根据成功示例)
|
||||||
Map<String, Object> parameters = new HashMap<>();
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
cozeRequest.put("parameters", parameters);
|
cozeRequest.put("parameters", parameters);
|
||||||
|
|
||||||
|
// 如果有workflow_id,则添加(根据成功示例,这是必需的)
|
||||||
|
if (config.getWorkflowId() != null && !config.getWorkflowId().trim().isEmpty()) {
|
||||||
|
cozeRequest.put("workflow_id", config.getWorkflowId());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("构建Coze请求完成: botId={}, workflowId={}, userId={}, stream={}, messageCount={}",
|
||||||
|
config.getBotId(), config.getWorkflowId(), userId, useStream, messages.size());
|
||||||
|
|
||||||
|
// 验证请求参数
|
||||||
|
validateCozeRequest(cozeRequest);
|
||||||
|
|
||||||
return cozeRequest;
|
return cozeRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1395,6 +1515,7 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
currentMsg.put(ROLE_KEY, USER_ROLE);
|
currentMsg.put(ROLE_KEY, USER_ROLE);
|
||||||
currentMsg.put(CONTENT_KEY, userMessage);
|
currentMsg.put(CONTENT_KEY, userMessage);
|
||||||
currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE);
|
currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE);
|
||||||
|
currentMsg.put("type", "question"); // 添加type字段
|
||||||
messages.add(currentMsg);
|
messages.add(currentMsg);
|
||||||
|
|
||||||
cozeRequest.put("additional_messages", messages);
|
cozeRequest.put("additional_messages", messages);
|
||||||
@@ -1866,4 +1987,47 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
return "/v3/chat";
|
return "/v3/chat";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Coze请求参数
|
||||||
|
*/
|
||||||
|
private void validateCozeRequest(Map<String, Object> cozeRequest) {
|
||||||
|
log.debug("验证Coze请求参数: {}", JSON.toJSONString(cozeRequest));
|
||||||
|
|
||||||
|
// 检查必需参数
|
||||||
|
if (!cozeRequest.containsKey("bot_id") || cozeRequest.get("bot_id") == null) {
|
||||||
|
throw new RuntimeException("缺少必需参数: bot_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cozeRequest.containsKey("user_id") || cozeRequest.get("user_id") == null) {
|
||||||
|
throw new RuntimeException("缺少必需参数: user_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cozeRequest.containsKey("additional_messages") || cozeRequest.get("additional_messages") == null) {
|
||||||
|
throw new RuntimeException("缺少必需参数: additional_messages");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查消息格式
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
java.util.List<Map<String, Object>> messages = (java.util.List<Map<String, Object>>) cozeRequest.get("additional_messages");
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
throw new RuntimeException("additional_messages不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map<String, Object> message : messages) {
|
||||||
|
if (!message.containsKey("role") || message.get("role") == null) {
|
||||||
|
throw new RuntimeException("消息缺少必需参数: role");
|
||||||
|
}
|
||||||
|
if (!message.containsKey("content") || message.get("content") == null) {
|
||||||
|
throw new RuntimeException("消息缺少必需参数: content");
|
||||||
|
}
|
||||||
|
if (!message.containsKey("content_type") || message.get("content_type") == null) {
|
||||||
|
throw new RuntimeException("消息缺少必需参数: content_type");
|
||||||
|
}
|
||||||
|
if (!message.containsKey("type") || message.get("type") == null) {
|
||||||
|
throw new RuntimeException("消息缺少必需参数: type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Coze请求参数验证通过");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
package com.emotion.service.impl;
|
||||||
|
|
||||||
|
import com.emotion.dto.response.DashboardStatsResponse;
|
||||||
|
import com.emotion.service.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仪表盘服务实现类
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2025-10-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class DashboardServiceImpl implements DashboardService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private GuestUserService guestUserService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConversationService conversationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MessageService messageService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DiaryPostService diaryPostService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommunityPostService communityPostService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmotionRecordService emotionRecordService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CozeApiCallService cozeApiCallService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AiConfigService aiConfigService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdminService adminService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AchievementService achievementService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RewardService rewardService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DashboardStatsResponse getDashboardStats() {
|
||||||
|
log.info("获取仪表盘统计数据");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return DashboardStatsResponse.builder()
|
||||||
|
.userStats(getUserStats())
|
||||||
|
.contentStats(getContentStats())
|
||||||
|
.aiServiceStats(getAiServiceStats())
|
||||||
|
.systemStats(getSystemStats())
|
||||||
|
.recentActivities(getRecentActivities())
|
||||||
|
.updateTime(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取仪表盘统计数据失败", e);
|
||||||
|
// 返回默认数据,避免前端报错
|
||||||
|
return getDefaultDashboardStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DashboardStatsResponse.UserStats getUserStats() {
|
||||||
|
try {
|
||||||
|
// 获取用户统计数据
|
||||||
|
Long totalUsers = userService.count();
|
||||||
|
Long guestUsers = guestUserService.count();
|
||||||
|
|
||||||
|
// 获取今日新增用户(这里简化处理,实际可能需要根据创建时间查询)
|
||||||
|
Long todayNewUsers = 0L; // 可以通过查询今日创建的用户数量来获取
|
||||||
|
|
||||||
|
// 活跃用户数(这里简化处理,可以根据最近登录时间或活动时间来计算)
|
||||||
|
Long activeUsers = totalUsers; // 简化处理
|
||||||
|
|
||||||
|
return DashboardStatsResponse.UserStats.builder()
|
||||||
|
.totalUsers(totalUsers)
|
||||||
|
.todayNewUsers(todayNewUsers)
|
||||||
|
.activeUsers(activeUsers)
|
||||||
|
.guestUsers(guestUsers)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户统计数据失败", e);
|
||||||
|
return DashboardStatsResponse.UserStats.builder()
|
||||||
|
.totalUsers(0L)
|
||||||
|
.todayNewUsers(0L)
|
||||||
|
.activeUsers(0L)
|
||||||
|
.guestUsers(0L)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DashboardStatsResponse.ContentStats getContentStats() {
|
||||||
|
try {
|
||||||
|
Long totalConversations = conversationService.count();
|
||||||
|
Long totalMessages = messageService.count();
|
||||||
|
Long diaryPosts = diaryPostService.count();
|
||||||
|
Long communityPosts = communityPostService.count();
|
||||||
|
Long emotionRecords = emotionRecordService.count();
|
||||||
|
|
||||||
|
return DashboardStatsResponse.ContentStats.builder()
|
||||||
|
.totalConversations(totalConversations)
|
||||||
|
.totalMessages(totalMessages)
|
||||||
|
.diaryPosts(diaryPosts)
|
||||||
|
.communityPosts(communityPosts)
|
||||||
|
.emotionRecords(emotionRecords)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取内容统计数据失败", e);
|
||||||
|
return DashboardStatsResponse.ContentStats.builder()
|
||||||
|
.totalConversations(0L)
|
||||||
|
.totalMessages(0L)
|
||||||
|
.diaryPosts(0L)
|
||||||
|
.communityPosts(0L)
|
||||||
|
.emotionRecords(0L)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DashboardStatsResponse.AiServiceStats getAiServiceStats() {
|
||||||
|
try {
|
||||||
|
Long totalApiCalls = cozeApiCallService.count();
|
||||||
|
Long aiConfigCount = aiConfigService.count();
|
||||||
|
|
||||||
|
// 获取今日API调用次数(简化处理)
|
||||||
|
Long todayApiCalls = 0L; // 可以通过查询今日的API调用记录来获取
|
||||||
|
|
||||||
|
// 获取成功和失败的调用次数(简化处理)
|
||||||
|
Long successfulCalls = totalApiCalls; // 可以通过状态字段查询
|
||||||
|
Long failedCalls = 0L;
|
||||||
|
|
||||||
|
// 平均响应时间(简化处理)
|
||||||
|
Double avgResponseTime = 500.0; // 可以通过计算duration_ms字段的平均值来获取
|
||||||
|
|
||||||
|
return DashboardStatsResponse.AiServiceStats.builder()
|
||||||
|
.totalApiCalls(totalApiCalls)
|
||||||
|
.todayApiCalls(todayApiCalls)
|
||||||
|
.successfulCalls(successfulCalls)
|
||||||
|
.failedCalls(failedCalls)
|
||||||
|
.avgResponseTime(avgResponseTime)
|
||||||
|
.aiConfigCount(aiConfigCount)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取AI服务统计数据失败", e);
|
||||||
|
return DashboardStatsResponse.AiServiceStats.builder()
|
||||||
|
.totalApiCalls(0L)
|
||||||
|
.todayApiCalls(0L)
|
||||||
|
.successfulCalls(0L)
|
||||||
|
.failedCalls(0L)
|
||||||
|
.avgResponseTime(0.0)
|
||||||
|
.aiConfigCount(0L)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DashboardStatsResponse.SystemStats getSystemStats() {
|
||||||
|
try {
|
||||||
|
Long adminCount = adminService.count();
|
||||||
|
Long achievementCount = achievementService.count();
|
||||||
|
Long rewardCount = rewardService.count();
|
||||||
|
|
||||||
|
// 获取系统运行时间
|
||||||
|
long uptimeMs = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||||
|
String uptime = formatUptime(uptimeMs);
|
||||||
|
|
||||||
|
return DashboardStatsResponse.SystemStats.builder()
|
||||||
|
.adminCount(adminCount)
|
||||||
|
.achievementCount(achievementCount)
|
||||||
|
.rewardCount(rewardCount)
|
||||||
|
.uptime(uptime)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取系统统计数据失败", e);
|
||||||
|
return DashboardStatsResponse.SystemStats.builder()
|
||||||
|
.adminCount(0L)
|
||||||
|
.achievementCount(0L)
|
||||||
|
.rewardCount(0L)
|
||||||
|
.uptime("未知")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最近活动
|
||||||
|
*/
|
||||||
|
private List<DashboardStatsResponse.RecentActivity> getRecentActivities() {
|
||||||
|
List<DashboardStatsResponse.RecentActivity> activities = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 这里可以添加获取最近活动的逻辑
|
||||||
|
// 例如:最近的用户注册、消息发送、帖子创建等
|
||||||
|
|
||||||
|
// 示例活动
|
||||||
|
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
||||||
|
.type("user_register")
|
||||||
|
.description("新用户注册")
|
||||||
|
.userId("system")
|
||||||
|
.username("系统")
|
||||||
|
.activityTime(LocalDateTime.now().minusMinutes(30))
|
||||||
|
.extraData(new HashMap<>())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
activities.add(DashboardStatsResponse.RecentActivity.builder()
|
||||||
|
.type("ai_chat")
|
||||||
|
.description("AI聊天对话")
|
||||||
|
.userId("system")
|
||||||
|
.username("系统")
|
||||||
|
.activityTime(LocalDateTime.now().minusMinutes(15))
|
||||||
|
.extraData(new HashMap<>())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取最近活动失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化运行时间
|
||||||
|
*/
|
||||||
|
private String formatUptime(long uptimeMs) {
|
||||||
|
long seconds = uptimeMs / 1000;
|
||||||
|
long minutes = seconds / 60;
|
||||||
|
long hours = minutes / 60;
|
||||||
|
long days = hours / 24;
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
return String.format("%d天%d小时%d分钟", days, hours % 24, minutes % 60);
|
||||||
|
} else if (hours > 0) {
|
||||||
|
return String.format("%d小时%d分钟", hours, minutes % 60);
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return String.format("%d分钟", minutes);
|
||||||
|
} else {
|
||||||
|
return String.format("%d秒", seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认仪表盘统计数据(用于异常情况)
|
||||||
|
*/
|
||||||
|
private DashboardStatsResponse getDefaultDashboardStats() {
|
||||||
|
return DashboardStatsResponse.builder()
|
||||||
|
.userStats(DashboardStatsResponse.UserStats.builder()
|
||||||
|
.totalUsers(0L)
|
||||||
|
.todayNewUsers(0L)
|
||||||
|
.activeUsers(0L)
|
||||||
|
.guestUsers(0L)
|
||||||
|
.build())
|
||||||
|
.contentStats(DashboardStatsResponse.ContentStats.builder()
|
||||||
|
.totalConversations(0L)
|
||||||
|
.totalMessages(0L)
|
||||||
|
.diaryPosts(0L)
|
||||||
|
.communityPosts(0L)
|
||||||
|
.emotionRecords(0L)
|
||||||
|
.build())
|
||||||
|
.aiServiceStats(DashboardStatsResponse.AiServiceStats.builder()
|
||||||
|
.totalApiCalls(0L)
|
||||||
|
.todayApiCalls(0L)
|
||||||
|
.successfulCalls(0L)
|
||||||
|
.failedCalls(0L)
|
||||||
|
.avgResponseTime(0.0)
|
||||||
|
.aiConfigCount(0L)
|
||||||
|
.build())
|
||||||
|
.systemStats(DashboardStatsResponse.SystemStats.builder()
|
||||||
|
.adminCount(0L)
|
||||||
|
.achievementCount(0L)
|
||||||
|
.rewardCount(0L)
|
||||||
|
.uptime("未知")
|
||||||
|
.build())
|
||||||
|
.recentActivities(new ArrayList<>())
|
||||||
|
.updateTime(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 仪表盘统计数据类型定义
|
||||||
|
export interface UserStats {
|
||||||
|
totalUsers: number
|
||||||
|
todayNewUsers: number
|
||||||
|
activeUsers: number
|
||||||
|
guestUsers: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContentStats {
|
||||||
|
totalConversations: number
|
||||||
|
totalMessages: number
|
||||||
|
diaryPosts: number
|
||||||
|
communityPosts: number
|
||||||
|
emotionRecords: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiServiceStats {
|
||||||
|
totalApiCalls: number
|
||||||
|
todayApiCalls: number
|
||||||
|
successfulCalls: number
|
||||||
|
failedCalls: number
|
||||||
|
avgResponseTime: number
|
||||||
|
aiConfigCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SystemStats {
|
||||||
|
adminCount: number
|
||||||
|
achievementCount: number
|
||||||
|
rewardCount: number
|
||||||
|
uptime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecentActivity {
|
||||||
|
type: string
|
||||||
|
description: string
|
||||||
|
userId: string
|
||||||
|
username: string
|
||||||
|
activityTime: string
|
||||||
|
extraData: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStats {
|
||||||
|
userStats: UserStats
|
||||||
|
contentStats: ContentStats
|
||||||
|
aiServiceStats: AiServiceStats
|
||||||
|
systemStats: SystemStats
|
||||||
|
recentActivities: RecentActivity[]
|
||||||
|
updateTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仪表盘统计数据
|
||||||
|
*/
|
||||||
|
export function getDashboardStats() {
|
||||||
|
return request<DashboardStats>({
|
||||||
|
url: '/admin/dashboard/stats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户统计数据
|
||||||
|
*/
|
||||||
|
export function getUserStats() {
|
||||||
|
return request<UserStats>({
|
||||||
|
url: '/admin/dashboard/user-stats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容统计数据
|
||||||
|
*/
|
||||||
|
export function getContentStats() {
|
||||||
|
return request<ContentStats>({
|
||||||
|
url: '/admin/dashboard/content-stats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AI服务统计数据
|
||||||
|
*/
|
||||||
|
export function getAiServiceStats() {
|
||||||
|
return request<AiServiceStats>({
|
||||||
|
url: '/admin/dashboard/ai-stats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统统计数据
|
||||||
|
*/
|
||||||
|
export function getSystemStats() {
|
||||||
|
return request<SystemStats>({
|
||||||
|
url: '/admin/dashboard/system-stats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,8 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
|
<div class="dashboard-header">
|
||||||
<h2 class="page-title">仪表盘</h2>
|
<h2 class="page-title">仪表盘</h2>
|
||||||
|
<div class="dashboard-actions">
|
||||||
|
<el-text type="info" size="small" v-if="dashboardStats.updateTime">
|
||||||
|
最后更新: {{ new Date(dashboardStats.updateTime).toLocaleString() }}
|
||||||
|
</el-text>
|
||||||
|
<el-text type="success" size="small" v-if="dashboardStats.systemStats.uptime">
|
||||||
|
系统运行: {{ dashboardStats.systemStats.uptime }}
|
||||||
|
</el-text>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:loading="loading"
|
||||||
|
@click="fetchDashboardData"
|
||||||
|
>
|
||||||
|
刷新数据
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-row :gutter="20" class="stats-row">
|
<el-row :gutter="20" class="stats-row" v-loading="loading">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-card class="stat-card">
|
<el-card class="stat-card">
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
@@ -10,7 +28,7 @@
|
|||||||
<el-icon><User /></el-icon>
|
<el-icon><User /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">1,234</div>
|
<div class="stat-value">{{ dashboardStats.userStats.totalUsers.toLocaleString() }}</div>
|
||||||
<div class="stat-label">用户总数</div>
|
<div class="stat-label">用户总数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,7 +42,7 @@
|
|||||||
<el-icon><UserFilled /></el-icon>
|
<el-icon><UserFilled /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">5</div>
|
<div class="stat-value">{{ dashboardStats.systemStats.adminCount }}</div>
|
||||||
<div class="stat-label">管理员总数</div>
|
<div class="stat-label">管理员总数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,8 +56,8 @@
|
|||||||
<el-icon><TrendCharts /></el-icon>
|
<el-icon><TrendCharts /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">89</div>
|
<div class="stat-value">{{ dashboardStats.userStats.activeUsers.toLocaleString() }}</div>
|
||||||
<div class="stat-label">今日活跃</div>
|
<div class="stat-label">活跃用户</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -52,7 +70,7 @@
|
|||||||
<el-icon><ChatDotRound /></el-icon>
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">456</div>
|
<div class="stat-value">{{ dashboardStats.contentStats.totalConversations.toLocaleString() }}</div>
|
||||||
<div class="stat-label">对话总数</div>
|
<div class="stat-label">对话总数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,6 +78,65 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 内容统计行 -->
|
||||||
|
<el-row :gutter="20" class="stats-row" v-loading="loading">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon" style="background-color: #909399;">
|
||||||
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-value">{{ dashboardStats.contentStats.totalMessages.toLocaleString() }}</div>
|
||||||
|
<div class="stat-label">消息总数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon" style="background-color: #67c23a;">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-value">{{ dashboardStats.userStats.guestUsers.toLocaleString() }}</div>
|
||||||
|
<div class="stat-label">访客用户</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon" style="background-color: #e6a23c;">
|
||||||
|
<el-icon><TrendCharts /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-value">{{ dashboardStats.contentStats.emotionRecords.toLocaleString() }}</div>
|
||||||
|
<div class="stat-label">情绪记录</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card class="stat-card">
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon" style="background-color: #f56c6c;">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-value">{{ dashboardStats.aiServiceStats.totalApiCalls.toLocaleString() }}</div>
|
||||||
|
<div class="stat-label">API调用总数</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- AI配置统计 -->
|
<!-- AI配置统计 -->
|
||||||
<el-row :gutter="20" class="stats-row">
|
<el-row :gutter="20" class="stats-row">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
@@ -153,10 +230,45 @@ import { ref, reactive, onMounted } from 'vue'
|
|||||||
import { User, UserFilled, TrendCharts, ChatDotRound, Setting, CircleCheck, CircleClose, Star } from '@element-plus/icons-vue'
|
import { User, UserFilled, TrendCharts, ChatDotRound, Setting, CircleCheck, CircleClose, Star } from '@element-plus/icons-vue'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import { countEnabledConfigs, countDisabledConfigs, countDefaultConfigs } from '@/api/aiconfig'
|
import { countEnabledConfigs, countDisabledConfigs, countDefaultConfigs } from '@/api/aiconfig'
|
||||||
|
import { getDashboardStats, type DashboardStats } from '@/api/dashboard'
|
||||||
import AiConfigQuickActions from '@/components/AiConfigQuickActions.vue'
|
import AiConfigQuickActions from '@/components/AiConfigQuickActions.vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const userChartRef = ref<HTMLElement>()
|
const userChartRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 仪表盘统计数据
|
||||||
|
const dashboardStats = reactive<DashboardStats>({
|
||||||
|
userStats: {
|
||||||
|
totalUsers: 0,
|
||||||
|
todayNewUsers: 0,
|
||||||
|
activeUsers: 0,
|
||||||
|
guestUsers: 0
|
||||||
|
},
|
||||||
|
contentStats: {
|
||||||
|
totalConversations: 0,
|
||||||
|
totalMessages: 0,
|
||||||
|
diaryPosts: 0,
|
||||||
|
communityPosts: 0,
|
||||||
|
emotionRecords: 0
|
||||||
|
},
|
||||||
|
aiServiceStats: {
|
||||||
|
totalApiCalls: 0,
|
||||||
|
todayApiCalls: 0,
|
||||||
|
successfulCalls: 0,
|
||||||
|
failedCalls: 0,
|
||||||
|
avgResponseTime: 0,
|
||||||
|
aiConfigCount: 0
|
||||||
|
},
|
||||||
|
systemStats: {
|
||||||
|
adminCount: 0,
|
||||||
|
achievementCount: 0,
|
||||||
|
rewardCount: 0,
|
||||||
|
uptime: ''
|
||||||
|
},
|
||||||
|
recentActivities: [],
|
||||||
|
updateTime: ''
|
||||||
|
})
|
||||||
|
|
||||||
const aiStats = reactive({
|
const aiStats = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
enabled: 0,
|
enabled: 0,
|
||||||
@@ -171,11 +283,35 @@ const recentLogins = ref([
|
|||||||
{ username: '赵六', time: '15分钟前' }
|
{ username: '赵六', time: '15分钟前' }
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initUserChart()
|
initUserChart()
|
||||||
fetchAiStats()
|
fetchDashboardData()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取仪表盘数据
|
||||||
|
const fetchDashboardData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 获取仪表盘统计数据
|
||||||
|
const statsRes = await getDashboardStats()
|
||||||
|
if (statsRes.data) {
|
||||||
|
Object.assign(dashboardStats, statsRes.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取AI配置统计(保持原有逻辑)
|
||||||
|
await fetchAiStats()
|
||||||
|
|
||||||
|
ElMessage.success('数据加载成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取仪表盘数据失败:', error)
|
||||||
|
ElMessage.error('数据加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取AI配置统计
|
// 获取AI配置统计
|
||||||
const fetchAiStats = async () => {
|
const fetchAiStats = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -232,12 +368,25 @@ const initUserChart = () => {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.dashboard {
|
.dashboard {
|
||||||
.page-title {
|
.dashboard-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.stats-row {
|
.stats-row {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user