) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java
new file mode 100644
index 00000000..9d6542cc
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java
@@ -0,0 +1,36 @@
+package xiaozhi.modules.agent.controller;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import xiaozhi.common.utils.Result;
+import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
+import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
+
+@Tag(name = "智能体聊天历史管理")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/agent/chat-history")
+public class AgentChatHistoryController {
+ private final AgentChatHistoryBizService agentChatHistoryBizService;
+
+ /**
+ * 小智服务聊天上报请求
+ *
+ * 小智服务聊天上报请求,包含Base64编码的音频数据和相关信息。
+ *
+ * @param request 包含上传文件及相关信息的请求对象
+ */
+ @Operation(summary = "小智服务聊天上报请求")
+ @PostMapping("/report")
+ public Result uploadFile(@Valid @RequestBody AgentChatHistoryReportDTO request) {
+ Boolean result = agentChatHistoryBizService.report(request);
+ return new Result().ok(result);
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
index 123e10ba..9368cf6b 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
@@ -3,8 +3,13 @@ package xiaozhi.modules.agent.controller;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
+import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -25,14 +30,20 @@ import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
+import xiaozhi.common.redis.RedisKeys;
+import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.Result;
+import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
+import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
+import xiaozhi.modules.agent.service.AgentChatAudioService;
+import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.device.service.DeviceService;
@@ -46,6 +57,9 @@ public class AgentController {
private final AgentService agentService;
private final AgentTemplateService agentTemplateService;
private final DeviceService deviceService;
+ private final AgentChatHistoryService agentChatHistoryService;
+ private final AgentChatAudioService agentChatAudioService;
+ private final RedisUtils redisUtils;
@GetMapping("/list")
@Operation(summary = "获取用户智能体列表")
@@ -80,7 +94,7 @@ public class AgentController {
@PostMapping
@Operation(summary = "创建智能体")
@RequiresPermissions("sys:role:normal")
- public Result save(@RequestBody @Valid AgentCreateDTO dto) {
+ public Result save(@RequestBody @Valid AgentCreateDTO dto) {
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
// 获取默认模板
@@ -108,7 +122,7 @@ public class AgentController {
// ID、智能体编码和排序会在Service层自动生成
agentService.insert(entity);
- return new Result<>();
+ return new Result().ok(entity.getId());
}
@PutMapping("/{id}")
@@ -178,6 +192,8 @@ public class AgentController {
public Result delete(@PathVariable String id) {
// 先删除关联的设备
deviceService.deleteByAgentId(id);
+ // 删除关联的聊天记录
+ agentChatHistoryService.deleteByAgentId(id);
// 再删除智能体
agentService.deleteById(id);
return new Result<>();
@@ -192,4 +208,71 @@ public class AgentController {
return new Result>().ok(list);
}
+ @GetMapping("/{id}/sessions")
+ @Operation(summary = "获取智能体会话列表")
+ @RequiresPermissions("sys:role:normal")
+ @Parameters({
+ @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
+ @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
+ })
+ public Result> getAgentSessions(
+ @PathVariable("id") String id,
+ @Parameter(hidden = true) @RequestParam Map params) {
+ params.put("agentId", id);
+ PageData page = agentChatHistoryService.getSessionListByAgentId(params);
+ return new Result>().ok(page);
+ }
+
+ @GetMapping("/{id}/chat-history/{sessionId}")
+ @Operation(summary = "获取智能体聊天记录")
+ @RequiresPermissions("sys:role:normal")
+ public Result> getAgentChatHistory(
+ @PathVariable("id") String id,
+ @PathVariable("sessionId") String sessionId) {
+ // 获取当前用户
+ UserDetail user = SecurityUser.getUser();
+
+ // 检查权限
+ if (!agentService.checkAgentPermission(id, user.getId())) {
+ return new Result>().error("没有权限查看该智能体的聊天记录");
+ }
+
+ // 查询聊天记录
+ List result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
+ return new Result>().ok(result);
+ }
+
+ @PostMapping("/audio/{audioId}")
+ @Operation(summary = "获取音频下载ID")
+ @RequiresPermissions("sys:role:normal")
+ public Result getAudioId(@PathVariable("audioId") String audioId) {
+ byte[] audioData = agentChatAudioService.getAudio(audioId);
+ if (audioData == null) {
+ return new Result().error("音频不存在");
+ }
+ String uuid = UUID.randomUUID().toString();
+ redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
+ return new Result().ok(uuid);
+ }
+
+ @GetMapping("/play/{uuid}")
+ @Operation(summary = "播放音频")
+ public ResponseEntity playAudio(@PathVariable("uuid") String uuid) {
+
+ String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid));
+ if (StringUtils.isBlank(audioId)) {
+ return ResponseEntity.notFound().build();
+ }
+
+ byte[] audioData = agentChatAudioService.getAudio(audioId);
+ if (audioData == null) {
+ return ResponseEntity.notFound().build();
+ }
+ redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid));
+ return ResponseEntity.ok()
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"")
+ .body(audioData);
+ }
+
}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
index 8c80d6cc..3b4be452 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
@@ -3,6 +3,7 @@ package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity;
@@ -15,4 +16,16 @@ public interface AgentDao extends BaseDao {
* @return 设备数量
*/
Integer getDeviceCountByAgentId(@Param("agentId") String agentId);
-}
\ No newline at end of file
+
+ /**
+ * 根据设备MAC地址查询对应设备的默认智能体信息
+ *
+ * @param macAddress 设备MAC地址
+ * @return 默认智能体信息
+ */
+ @Select(" SELECT a.* FROM ai_device d " +
+ " LEFT JOIN ai_agent a ON d.agent_id = a.id " +
+ " WHERE d.mac_address = #{macAddress} " +
+ " ORDER BY d.id DESC LIMIT 1")
+ AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java
new file mode 100644
index 00000000..355b25ac
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java
@@ -0,0 +1,18 @@
+package xiaozhi.modules.agent.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
+
+/**
+ * {@link AgentChatAudioEntity} 智能体聊天音频数据Dao对象
+ *
+ * @author Goody
+ * @version 1.0, 2025/5/8
+ * @since 1.0.0
+ */
+@Mapper
+public interface AiAgentChatAudioDao extends BaseMapper {
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java
new file mode 100644
index 00000000..43a5f1ce
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java
@@ -0,0 +1,31 @@
+package xiaozhi.modules.agent.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
+
+/**
+ * {@link AgentChatHistoryEntity} 智能体聊天历史记录Dao对象
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+@Mapper
+public interface AiAgentChatHistoryDao extends BaseMapper {
+ /**
+ * 根据智能体ID删除音频
+ *
+ * @param agentId 智能体ID
+ */
+ void deleteAudioByAgentId(String agentId);
+
+ /**
+ * 根据智能体ID删除聊天历史记录
+ *
+ * @param agentId 智能体ID
+ */
+ void deleteHistoryByAgentId(String agentId);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java
new file mode 100644
index 00000000..fd49e52b
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java
@@ -0,0 +1,28 @@
+package xiaozhi.modules.agent.dto;
+
+import java.util.Date;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 智能体聊天记录DTO
+ */
+@Data
+@Schema(description = "智能体聊天记录")
+public class AgentChatHistoryDTO {
+ @Schema(description = "创建时间")
+ private Date createdAt;
+
+ @Schema(description = "消息类型: 1-用户, 2-智能体")
+ private Byte chatType;
+
+ @Schema(description = "聊天内容")
+ private String content;
+
+ @Schema(description = "音频ID")
+ private String audioId;
+
+ @Schema(description = "MAC地址")
+ private String macAddress;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java
new file mode 100644
index 00000000..bdaed950
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java
@@ -0,0 +1,31 @@
+package xiaozhi.modules.agent.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 小智设备聊天上报请求
+ *
+ * @author Haotian
+ * @version 1.0, 2025/5/8
+ */
+@Data
+@Schema(description = "小智设备聊天上报请求")
+public class AgentChatHistoryReportDTO {
+ @Schema(description = "MAC地址", example = "00:11:22:33:44:55")
+ @NotBlank
+ private String macAddress;
+ @Schema(description = "会话ID", example = "79578c31-f1fb-426a-900e-1e934215f05a")
+ @NotBlank
+ private String sessionId;
+ @Schema(description = "消息类型: 1-用户, 2-智能体", example = "1")
+ @NotNull
+ private Byte chatType;
+ @Schema(description = "聊天内容", example = "你好呀")
+ @NotBlank
+ private String content;
+ @Schema(description = "base64编码的opus音频数据", example = "")
+ private String audioBase64;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java
new file mode 100644
index 00000000..c65f05c6
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java
@@ -0,0 +1,26 @@
+package xiaozhi.modules.agent.dto;
+
+import java.time.LocalDateTime;
+
+import lombok.Data;
+
+/**
+ * 智能体会话列表DTO
+ */
+@Data
+public class AgentChatSessionDTO {
+ /**
+ * 会话ID
+ */
+ private String sessionId;
+
+ /**
+ * 会话时间
+ */
+ private LocalDateTime createdAt;
+
+ /**
+ * 聊天条数
+ */
+ private Integer chatCount;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java
new file mode 100644
index 00000000..ac30d9d4
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java
@@ -0,0 +1,29 @@
+package xiaozhi.modules.agent.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import lombok.Data;
+
+/**
+ * 智能体聊天音频数据表
+ *
+ * @author Goody
+ * @version 1.0, 2025/5/8
+ * @since 1.0.0
+ */
+@Data
+@TableName("ai_agent_chat_audio")
+public class AgentChatAudioEntity {
+ /**
+ * 主键ID
+ */
+ @TableId(type = IdType.ASSIGN_UUID)
+ private String id;
+
+ /**
+ * 音频opus数据
+ */
+ private byte[] audio;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java
new file mode 100644
index 00000000..679c814a
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java
@@ -0,0 +1,81 @@
+package xiaozhi.modules.agent.entity;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 智能体聊天记录表
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "ai_agent_chat_history")
+public class AgentChatHistoryEntity {
+ /**
+ * 主键ID
+ */
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * MAC地址
+ */
+ @TableField(value = "mac_address")
+ private String macAddress;
+
+ /**
+ * 智能体id
+ */
+ @TableField(value = "agent_id")
+ private String agentId;
+
+ /**
+ * 会话ID
+ */
+ @TableField(value = "session_id")
+ private String sessionId;
+
+ /**
+ * 消息类型: 1-用户, 2-智能体
+ */
+ @TableField(value = "chat_type")
+ private Byte chatType;
+
+ /**
+ * 聊天内容
+ */
+ @TableField(value = "content")
+ private String content;
+
+ /**
+ * 音频base64数据
+ */
+ @TableField(value = "audio_id")
+ private String audioId;
+
+ /**
+ * 创建时间
+ */
+ @TableField(value = "created_at")
+ private Date createdAt;
+
+ /**
+ * 更新时间
+ */
+ @TableField(value = "updated_at")
+ private Date updatedAt;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java
new file mode 100644
index 00000000..e284b69f
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java
@@ -0,0 +1,30 @@
+package xiaozhi.modules.agent.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
+
+/**
+ * 智能体聊天音频数据表处理service
+ *
+ * @author Goody
+ * @version 1.0, 2025/5/8
+ * @since 1.0.0
+ */
+public interface AgentChatAudioService extends IService {
+ /**
+ * 保存音频数据
+ *
+ * @param audioData 音频数据
+ * @return 音频ID
+ */
+ String saveAudio(byte[] audioData);
+
+ /**
+ * 获取音频数据
+ *
+ * @param audioId 音频ID
+ * @return 音频数据
+ */
+ byte[] getAudio(String audioId);
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java
new file mode 100644
index 00000000..889f64cd
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java
@@ -0,0 +1,45 @@
+package xiaozhi.modules.agent.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import xiaozhi.common.page.PageData;
+import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
+import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
+import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
+
+/**
+ * 智能体聊天记录表处理service
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+public interface AgentChatHistoryService extends IService {
+
+ /**
+ * 根据智能体ID获取会话列表
+ *
+ * @param params 查询参数,包含agentId、page、limit
+ * @return 分页的会话列表
+ */
+ PageData getSessionListByAgentId(Map params);
+
+ /**
+ * 根据会话ID获取聊天记录列表
+ *
+ * @param agentId 智能体ID
+ * @param sessionId 会话ID
+ * @return 聊天记录列表
+ */
+ List getChatHistoryBySessionId(String agentId, String sessionId);
+
+ /**
+ * 根据智能体ID删除聊天记录
+ *
+ * @param agentId 智能体ID
+ */
+ void deleteByAgentId(String agentId);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
index 8d0b2959..2104d353 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
@@ -8,38 +8,75 @@ import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.dto.AgentDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
+/**
+ * 智能体表处理service
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
public interface AgentService extends BaseService {
-
/**
- * 管理员获取所有智能体列表(分页)
+ * 获取管理员智能体列表
+ *
+ * @param params 查询参数
+ * @return 分页数据
*/
PageData adminAgentList(Map params);
/**
- * 获取智能体详情
+ * 根据ID获取智能体
+ *
+ * @param id 智能体ID
+ * @return 智能体实体
*/
AgentEntity getAgentById(String id);
/**
- * 删除这个用户的所有
- *
- * @param userId
+ * 插入智能体
+ *
+ * @param entity 智能体实体
+ * @return 是否成功
+ */
+ boolean insert(AgentEntity entity);
+
+ /**
+ * 根据用户ID删除智能体
+ *
+ * @param userId 用户ID
*/
void deleteAgentByUserId(Long userId);
/**
* 获取用户智能体列表
- *
- * @param userId
- * @return
+ *
+ * @param userId 用户ID
+ * @return 智能体列表
*/
List getUserAgents(Long userId);
/**
- * 获取智能体的设备数量
- *
+ * 根据智能体ID获取设备数量
+ *
* @param agentId 智能体ID
* @return 设备数量
*/
Integer getDeviceCountByAgentId(String agentId);
-}
\ No newline at end of file
+
+ /**
+ * 根据设备MAC地址查询对应设备的默认智能体信息
+ *
+ * @param macAddress 设备MAC地址
+ * @return 默认智能体信息,不存在时返回null
+ */
+ AgentEntity getDefaultAgentByMacAddress(String macAddress);
+
+ /**
+ * 检查用户是否有权限访问智能体
+ *
+ * @param agentId 智能体ID
+ * @param userId 用户ID
+ * @return 是否有权限
+ */
+ boolean checkAgentPermission(String agentId, Long userId);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java
new file mode 100644
index 00000000..191b231b
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.agent.service.biz;
+
+import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
+
+/**
+ * 智能体聊天历史业务逻辑层
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+public interface AgentChatHistoryBizService {
+
+ /**
+ * 聊天上报方法
+ *
+ * @param agentChatHistoryReportDTO 包含聊天上报所需信息的输入对象
+ * 例如:设备MAC地址、文件类型、内容等
+ * @return 上传结果,true表示成功,false表示失败
+ */
+ Boolean report(AgentChatHistoryReportDTO agentChatHistoryReportDTO);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java
new file mode 100644
index 00000000..b839e9e4
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java
@@ -0,0 +1,82 @@
+package xiaozhi.modules.agent.service.biz.impl;
+
+import java.util.Base64;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
+import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
+import xiaozhi.modules.agent.entity.AgentEntity;
+import xiaozhi.modules.agent.service.AgentChatAudioService;
+import xiaozhi.modules.agent.service.AgentChatHistoryService;
+import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
+
+/**
+ * {@link AgentChatHistoryBizService} impl
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizService {
+ private final AgentService agentService;
+ private final AgentChatHistoryService agentChatHistoryService;
+ private final AgentChatAudioService agentChatAudioService;
+
+ /**
+ * 处理聊天记录上报,包括文件上传和相关信息记录
+ *
+ * @param report 包含聊天上报所需信息的输入对象
+ * @return 上传结果,true表示成功,false表示失败
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean report(AgentChatHistoryReportDTO report) {
+ String macAddress = report.getMacAddress();
+ Byte chatType = report.getChatType();
+ log.info("小智设备聊天上报请求: macAddress={}, type={}", macAddress, chatType);
+
+ // 1. base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表
+ String audioId = null;
+ if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) {
+ try {
+ byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64());
+ audioId = agentChatAudioService.saveAudio(audioData);
+ log.info("音频数据保存成功,audioId={}", audioId);
+ } catch (Exception e) {
+ log.error("音频数据保存失败", e);
+ return false;
+ }
+ }
+
+ // 2. 组装上报数据
+ // 2.1 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
+ AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress);
+ if (agentEntity == null) {
+ return false;
+ }
+ String agentId = agentEntity.getId();
+ log.info("设备 {} 对应智能体 {} 上报", macAddress, agentEntity.getId());
+
+ // 2.2 构建聊天记录实体
+ AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder()
+ .macAddress(macAddress)
+ .agentId(agentId)
+ .sessionId(report.getSessionId())
+ .chatType(report.getChatType())
+ .content(report.getContent())
+ .audioId(audioId)
+ .build();
+
+ // 3. 保存数据
+ agentChatHistoryService.save(entity);
+ return Boolean.TRUE;
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java
new file mode 100644
index 00000000..cf804678
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java
@@ -0,0 +1,34 @@
+package xiaozhi.modules.agent.service.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import xiaozhi.modules.agent.dao.AiAgentChatAudioDao;
+import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
+import xiaozhi.modules.agent.service.AgentChatAudioService;
+
+/**
+ * 智能体聊天音频数据表处理service {@link AgentChatAudioService} impl
+ *
+ * @author Goody
+ * @version 1.0, 2025/5/8
+ * @since 1.0.0
+ */
+@Service
+public class AgentChatAudioServiceImpl extends ServiceImpl
+ implements AgentChatAudioService {
+ @Override
+ public String saveAudio(byte[] audioData) {
+ AgentChatAudioEntity entity = new AgentChatAudioEntity();
+ entity.setAudio(audioData);
+ save(entity);
+ return entity.getId();
+ }
+
+ @Override
+ public byte[] getAudio(String audioId) {
+ AgentChatAudioEntity entity = getById(audioId);
+ return entity != null ? entity.getAudio() : null;
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java
new file mode 100644
index 00000000..cc7236ae
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java
@@ -0,0 +1,85 @@
+package xiaozhi.modules.agent.service.impl;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import xiaozhi.common.constant.Constant;
+import xiaozhi.common.page.PageData;
+import xiaozhi.common.utils.ConvertUtils;
+import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
+import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
+import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
+import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
+import xiaozhi.modules.agent.service.AgentChatHistoryService;
+
+/**
+ * 智能体聊天记录表处理service {@link AgentChatHistoryService} impl
+ *
+ * @author Goody
+ * @version 1.0, 2025/4/30
+ * @since 1.0.0
+ */
+@Service
+public class AgentChatHistoryServiceImpl extends ServiceImpl
+ implements AgentChatHistoryService {
+
+ @Override
+ public PageData getSessionListByAgentId(Map params) {
+ String agentId = (String) params.get("agentId");
+ int page = Integer.parseInt(params.get(Constant.PAGE).toString());
+ int limit = Integer.parseInt(params.get(Constant.LIMIT).toString());
+
+ // 构建查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.select("session_id", "MAX(created_at) as created_at", "COUNT(*) as chat_count")
+ .eq("agent_id", agentId)
+ .groupBy("session_id")
+ .orderByDesc("created_at");
+
+ // 执行分页查询
+ Page