mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
Merge branch 'main' into manager-api-lastConnectedAtIsNull-BUG
This commit is contained in:
@@ -177,5 +177,5 @@ public interface Constant {
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
public static final String VERSION = "0.3.13";
|
||||
public static final String VERSION = "0.3.14";
|
||||
}
|
||||
@@ -103,4 +103,18 @@ public class RedisKeys {
|
||||
public static String getOtaDownloadCountKey(String uuid) {
|
||||
return "ota:download:count:" + uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据的缓存key
|
||||
*/
|
||||
public static String getDictDataByTypeKey(String dictType) {
|
||||
return "sys:dict:data:" + dictType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体音频ID的缓存key
|
||||
*/
|
||||
public static String getAgentAudioIdKey(String uuid) {
|
||||
return "agent:audio:id:" + uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,12 +128,10 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
return SqlHelper.retBool(result);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<M> currentMapperClass() {
|
||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<T> currentModelClass() {
|
||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
||||
|
||||
@@ -24,7 +24,6 @@ import xiaozhi.common.utils.ConvertUtils;
|
||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
||||
implements CrudService<T, D> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<D> currentDtoClass() {
|
||||
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
||||
}
|
||||
|
||||
+36
@@ -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;
|
||||
|
||||
/**
|
||||
* 小智服务聊天上报请求
|
||||
* <p>
|
||||
* 小智服务聊天上报请求,包含Base64编码的音频数据和相关信息。
|
||||
*
|
||||
* @param request 包含上传文件及相关信息的请求对象
|
||||
*/
|
||||
@Operation(summary = "小智服务聊天上报请求")
|
||||
@PostMapping("/report")
|
||||
public Result<Boolean> uploadFile(@Valid @RequestBody AgentChatHistoryReportDTO request) {
|
||||
Boolean result = agentChatHistoryBizService.report(request);
|
||||
return new Result<Boolean>().ok(result);
|
||||
}
|
||||
}
|
||||
+85
-2
@@ -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<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||
public Result<String> 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<String>().ok(entity.getId());
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@@ -178,6 +192,8 @@ public class AgentController {
|
||||
public Result<Void> 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<List<AgentTemplateEntity>>().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<PageData<AgentChatSessionDTO>> getAgentSessions(
|
||||
@PathVariable("id") String id,
|
||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||
params.put("agentId", id);
|
||||
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
||||
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/chat-history/{sessionId}")
|
||||
@Operation(summary = "获取智能体聊天记录")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<AgentChatHistoryDTO>> getAgentChatHistory(
|
||||
@PathVariable("id") String id,
|
||||
@PathVariable("sessionId") String sessionId) {
|
||||
// 获取当前用户
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(id, user.getId())) {
|
||||
return new Result<List<AgentChatHistoryDTO>>().error("没有权限查看该智能体的聊天记录");
|
||||
}
|
||||
|
||||
// 查询聊天记录
|
||||
List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
|
||||
return new Result<List<AgentChatHistoryDTO>>().ok(result);
|
||||
}
|
||||
|
||||
@PostMapping("/audio/{audioId}")
|
||||
@Operation(summary = "获取音频下载ID")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||
if (audioData == null) {
|
||||
return new Result<String>().error("音频不存在");
|
||||
}
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
|
||||
return new Result<String>().ok(uuid);
|
||||
}
|
||||
|
||||
@GetMapping("/play/{uuid}")
|
||||
@Operation(summary = "播放音频")
|
||||
public ResponseEntity<byte[]> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<AgentEntity> {
|
||||
* @return 设备数量
|
||||
*/
|
||||
Integer getDeviceCountByAgentId(@Param("agentId") String agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备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);
|
||||
}
|
||||
|
||||
@@ -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<AgentChatAudioEntity> {
|
||||
}
|
||||
@@ -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<AgentChatHistoryEntity> {
|
||||
/**
|
||||
* 根据智能体ID删除音频
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
void deleteAudioByAgentId(String agentId);
|
||||
|
||||
/**
|
||||
* 根据智能体ID删除聊天历史记录
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
void deleteHistoryByAgentId(String agentId);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+81
@@ -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;
|
||||
}
|
||||
+30
@@ -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<AgentChatAudioEntity> {
|
||||
/**
|
||||
* 保存音频数据
|
||||
*
|
||||
* @param audioData 音频数据
|
||||
* @return 音频ID
|
||||
*/
|
||||
String saveAudio(byte[] audioData);
|
||||
|
||||
/**
|
||||
* 获取音频数据
|
||||
*
|
||||
* @param audioId 音频ID
|
||||
* @return 音频数据
|
||||
*/
|
||||
byte[] getAudio(String audioId);
|
||||
}
|
||||
+45
@@ -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<AgentChatHistoryEntity> {
|
||||
|
||||
/**
|
||||
* 根据智能体ID获取会话列表
|
||||
*
|
||||
* @param params 查询参数,包含agentId、page、limit
|
||||
* @return 分页的会话列表
|
||||
*/
|
||||
PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 根据会话ID获取聊天记录列表
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
* @param sessionId 会话ID
|
||||
* @return 聊天记录列表
|
||||
*/
|
||||
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
|
||||
|
||||
/**
|
||||
* 根据智能体ID删除聊天记录
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
void deleteByAgentId(String agentId);
|
||||
}
|
||||
@@ -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<AgentEntity> {
|
||||
|
||||
/**
|
||||
* 管理员获取所有智能体列表(分页)
|
||||
* 获取管理员智能体列表
|
||||
*
|
||||
* @param params 查询参数
|
||||
* @return 分页数据
|
||||
*/
|
||||
PageData<AgentEntity> adminAgentList(Map<String, Object> 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<AgentDTO> getUserAgents(Long userId);
|
||||
|
||||
/**
|
||||
* 获取智能体的设备数量
|
||||
*
|
||||
* 根据智能体ID获取设备数量
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
* @return 设备数量
|
||||
*/
|
||||
Integer getDeviceCountByAgentId(String agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备MAC地址查询对应设备的默认智能体信息
|
||||
*
|
||||
* @param macAddress 设备MAC地址
|
||||
* @return 默认智能体信息,不存在时返回null
|
||||
*/
|
||||
AgentEntity getDefaultAgentByMacAddress(String macAddress);
|
||||
|
||||
/**
|
||||
* 检查用户是否有权限访问智能体
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
* @param userId 用户ID
|
||||
* @return 是否有权限
|
||||
*/
|
||||
boolean checkAgentPermission(String agentId, Long userId);
|
||||
}
|
||||
|
||||
+22
@@ -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);
|
||||
}
|
||||
+82
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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<AiAgentChatAudioDao, AgentChatAudioEntity>
|
||||
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;
|
||||
}
|
||||
}
|
||||
+85
@@ -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<AiAgentChatHistoryDao, AgentChatHistoryEntity>
|
||||
implements AgentChatHistoryService {
|
||||
|
||||
@Override
|
||||
public PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> 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<AgentChatHistoryEntity> 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<Map<String, Object>> pageParam = new Page<>(page, limit);
|
||||
IPage<Map<String, Object>> result = this.baseMapper.selectMapsPage(pageParam, wrapper);
|
||||
|
||||
List<AgentChatSessionDTO> records = result.getRecords().stream().map(map -> {
|
||||
AgentChatSessionDTO dto = new AgentChatSessionDTO();
|
||||
dto.setSessionId((String) map.get("session_id"));
|
||||
dto.setCreatedAt((LocalDateTime) map.get("created_at"));
|
||||
dto.setChatCount(((Number) map.get("chat_count")).intValue());
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new PageData<>(records, result.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId) {
|
||||
// 构建查询条件
|
||||
QueryWrapper<AgentChatHistoryEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("agent_id", agentId)
|
||||
.eq("session_id", sessionId)
|
||||
.orderByAsc("created_at");
|
||||
|
||||
// 查询聊天记录
|
||||
List<AgentChatHistoryEntity> historyList = list(wrapper);
|
||||
|
||||
// 转换为DTO
|
||||
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteByAgentId(String agentId) {
|
||||
baseMapper.deleteAudioByAgentId(agentId);
|
||||
baseMapper.deleteHistoryByAgentId(agentId);
|
||||
}
|
||||
}
|
||||
+33
-18
@@ -6,13 +6,13 @@ import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
@@ -23,27 +23,17 @@ import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||
import xiaozhi.modules.timbre.service.TimbreService;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
||||
private final AgentDao agentDao;
|
||||
|
||||
@Autowired
|
||||
private TimbreService timbreModelService;
|
||||
|
||||
@Autowired
|
||||
private DeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private ModelConfigService modelConfigService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
public AgentServiceImpl(AgentDao agentDao) {
|
||||
this.agentDao = agentDao;
|
||||
}
|
||||
private final TimbreService timbreModelService;
|
||||
private final ModelConfigService modelConfigService;
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
@Override
|
||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||
@@ -136,4 +126,29 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
|
||||
return deviceCount != null ? deviceCount : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentEntity getDefaultAgentByMacAddress(String macAddress) {
|
||||
if (StringUtils.isEmpty(macAddress)) {
|
||||
return null;
|
||||
}
|
||||
return agentDao.getDefaultAgentByMacAddress(macAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAgentPermission(String agentId, Long userId) {
|
||||
// 获取智能体信息
|
||||
AgentEntity agent = getAgentById(agentId);
|
||||
if (agent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是超级管理员,直接返回true
|
||||
if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否是智能体的所有者
|
||||
return userId.equals(agent.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-20
@@ -1,6 +1,5 @@
|
||||
package xiaozhi.modules.config.controller;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -8,15 +7,12 @@ 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.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
||||
import xiaozhi.modules.config.service.ConfigService;
|
||||
import xiaozhi.modules.sys.dto.ConfigSecretDTO;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
/**
|
||||
* xiaozhi-server 配置获取
|
||||
@@ -29,33 +25,20 @@ import xiaozhi.modules.sys.service.SysParamsService;
|
||||
@AllArgsConstructor
|
||||
public class ConfigController {
|
||||
private final ConfigService configService;
|
||||
private final SysParamsService sysParamsService;
|
||||
|
||||
@PostMapping("server-base")
|
||||
@Operation(summary = "获取配置")
|
||||
public Result<Object> getConfig(@RequestBody ConfigSecretDTO dto) {
|
||||
// 效验数据
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
checkSecret(dto.getSecret());
|
||||
public Result<Object> getConfig() {
|
||||
Object config = configService.getConfig(true);
|
||||
return new Result<Object>().ok(config);
|
||||
}
|
||||
|
||||
@PostMapping("agent-models")
|
||||
@Operation(summary = "获取智能体模型")
|
||||
public Result<Object> getAgentModels(@RequestBody AgentModelsDTO dto) {
|
||||
public Result<Object> getAgentModels(@Valid @RequestBody AgentModelsDTO dto) {
|
||||
// 效验数据
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
checkSecret(dto.getSecret());
|
||||
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
||||
return new Result<Object>().ok(models);
|
||||
}
|
||||
|
||||
private void checkSecret(String secret) {
|
||||
String secretParam = sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
||||
// 验证密钥
|
||||
if (StringUtils.isBlank(secret) || !secret.equals(secretParam)) {
|
||||
throw new RenException("密钥错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ import lombok.Data;
|
||||
@Data
|
||||
@Schema(description = "获取智能体模型配置DTO")
|
||||
public class AgentModelsDTO {
|
||||
@NotBlank(message = "密钥不能为空")
|
||||
@Schema(description = "密钥")
|
||||
private String secret;
|
||||
|
||||
@NotBlank(message = "设备MAC地址不能为空")
|
||||
@Schema(description = "设备MAC地址")
|
||||
|
||||
+5
-18
@@ -61,15 +61,15 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
// 构建模块配置
|
||||
buildModuleConfig(
|
||||
agent.getAgentName(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
agent.getVadModelId(),
|
||||
agent.getAsrModelId(),
|
||||
agent.getLlmModelId(),
|
||||
agent.getTtsModelId(),
|
||||
agent.getMemModelId(),
|
||||
agent.getIntentModelId(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
result,
|
||||
isCache);
|
||||
|
||||
@@ -117,18 +117,6 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
|
||||
agent.setAsrModelId(null);
|
||||
}
|
||||
String alreadySelectedLlmModelId = (String) selectedModule.get("LLM");
|
||||
if (alreadySelectedLlmModelId != null && alreadySelectedLlmModelId.equals(agent.getLlmModelId())) {
|
||||
agent.setLlmModelId(null);
|
||||
}
|
||||
String alreadySelectedMemModelId = (String) selectedModule.get("Memory");
|
||||
if (alreadySelectedMemModelId != null && alreadySelectedMemModelId.equals(agent.getMemModelId())) {
|
||||
agent.setMemModelId(null);
|
||||
}
|
||||
String alreadySelectedIntentModelId = (String) selectedModule.get("Intent");
|
||||
if (alreadySelectedIntentModelId != null && alreadySelectedIntentModelId.equals(agent.getIntentModelId())) {
|
||||
agent.setIntentModelId(null);
|
||||
}
|
||||
|
||||
// 构建模块配置
|
||||
buildModuleConfig(
|
||||
@@ -153,7 +141,6 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
* @param paramsList 系统参数列表
|
||||
* @return 配置信息
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object buildConfig(Map<String, Object> config) {
|
||||
|
||||
// 查询所有系统参数
|
||||
|
||||
-4
@@ -21,9 +21,7 @@ import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.agent.dao.AgentDao;
|
||||
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
import xiaozhi.modules.model.dao.ModelConfigDao;
|
||||
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
@@ -41,8 +39,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
private final ModelConfigDao modelConfigDao;
|
||||
private final ModelProviderService modelProviderService;
|
||||
private final RedisUtils redisUtils;
|
||||
private final AgentTemplateDao agentTemplateDao;
|
||||
private final AgentTemplateService agentTemplateService;
|
||||
private final AgentDao agentDao;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import jakarta.servlet.Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||
import xiaozhi.modules.security.secret.ServerSecretFilter;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
/**
|
||||
* Shiro的配置文件
|
||||
@@ -46,7 +48,7 @@ public class ShiroConfig {
|
||||
}
|
||||
|
||||
@Bean("shiroFilter")
|
||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
|
||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
|
||||
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
||||
config.setFilterOncePerRequest(true);
|
||||
|
||||
@@ -54,9 +56,11 @@ public class ShiroConfig {
|
||||
shiroFilter.setSecurityManager(securityManager);
|
||||
shiroFilter.setShiroFilterConfiguration(config);
|
||||
|
||||
// oauth过滤
|
||||
Map<String, Filter> filters = new HashMap<>();
|
||||
// oauth过滤
|
||||
filters.put("oauth2", new Oauth2Filter());
|
||||
// 服务密钥过滤
|
||||
filters.put("server", new ServerSecretFilter(sysParamsService));
|
||||
shiroFilter.setFilters(filters);
|
||||
|
||||
// 添加Shiro的内置过滤器
|
||||
@@ -79,8 +83,10 @@ public class ShiroConfig {
|
||||
filterMap.put("/user/login", "anon");
|
||||
filterMap.put("/user/pub-config", "anon");
|
||||
filterMap.put("/user/register", "anon");
|
||||
filterMap.put("/config/server-base", "anon");
|
||||
filterMap.put("/config/agent-models", "anon");
|
||||
// 将config路径使用server服务过滤器
|
||||
filterMap.put("/config/**", "server");
|
||||
filterMap.put("/agent/chat-history/report", "server");
|
||||
filterMap.put("/agent/play/**", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||
|
||||
@@ -98,4 +104,4 @@ public class ShiroConfig {
|
||||
advisor.setSecurityManager(securityManager);
|
||||
return advisor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package xiaozhi.modules.security.config;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@@ -18,6 +19,15 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
|
||||
import xiaozhi.common.utils.DateUtils;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@@ -53,10 +63,29 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
// 忽略未知属性
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
// 日期格式转换
|
||||
// mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
|
||||
// 设置时区
|
||||
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
||||
|
||||
// 配置Java8日期时间序列化
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(java.time.LocalDateTime.class, new LocalDateTimeSerializer(
|
||||
java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_TIME_PATTERN)));
|
||||
javaTimeModule.addSerializer(java.time.LocalDate.class, new LocalDateSerializer(
|
||||
java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_PATTERN)));
|
||||
javaTimeModule.addSerializer(java.time.LocalTime.class,
|
||||
new LocalTimeSerializer(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(java.time.LocalDateTime.class, new LocalDateTimeDeserializer(
|
||||
java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_TIME_PATTERN)));
|
||||
javaTimeModule.addDeserializer(java.time.LocalDate.class, new LocalDateDeserializer(
|
||||
java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_PATTERN)));
|
||||
javaTimeModule.addDeserializer(java.time.LocalTime.class,
|
||||
new LocalTimeDeserializer(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
mapper.registerModule(javaTimeModule);
|
||||
|
||||
// 配置java.util.Date的序列化和反序列化
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN);
|
||||
mapper.setDateFormat(dateFormat);
|
||||
|
||||
// Long类型转String类型
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package xiaozhi.modules.security.secret;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.utils.HttpContextUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
/**
|
||||
* Config API 过滤器
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ServerSecretFilter extends AuthenticatingFilter {
|
||||
private final SysParamsService sysParamsService;
|
||||
|
||||
@Override
|
||||
protected ServerSecretToken createToken(ServletRequest request, ServletResponse response) {
|
||||
// 获取请求token
|
||||
String token = getRequestToken((HttpServletRequest) request);
|
||||
|
||||
if (StringUtils.isBlank(token)) {
|
||||
log.warn("createToken:token is empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ServerSecretToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
// 对OPTIONS请求放行
|
||||
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||
// 获取token并校验
|
||||
String token = getRequestToken((HttpServletRequest) servletRequest);
|
||||
if (StringUtils.isBlank(token)) {
|
||||
// token为空,返回401
|
||||
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "服务器密钥不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证token是否匹配
|
||||
String serverSecret = getServerSecret();
|
||||
if (StringUtils.isBlank(serverSecret) || !serverSecret.equals(token)) {
|
||||
// token无效,返回401
|
||||
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "无效的服务器密钥");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送未授权响应
|
||||
*/
|
||||
private void sendUnauthorizedResponse(HttpServletResponse response, String message) {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
response.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
|
||||
|
||||
try {
|
||||
String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED, message));
|
||||
response.getWriter().print(json);
|
||||
} catch (IOException e) {
|
||||
log.error("响应输出失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的token
|
||||
*/
|
||||
private String getRequestToken(HttpServletRequest httpRequest) {
|
||||
String token = null;
|
||||
// 从header中获取token
|
||||
String authorization = httpRequest.getHeader("Authorization");
|
||||
if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
|
||||
token = authorization.replace("Bearer ", "");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getServerSecret() {
|
||||
return sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package xiaozhi.modules.security.secret;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* Config API Token
|
||||
*/
|
||||
public class ServerSecretToken implements AuthenticationToken {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String token;
|
||||
|
||||
public ServerSecretToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ public class AdminController {
|
||||
dto.setLimit((String) params.get(Constant.LIMIT));
|
||||
dto.setPage((String) params.get(Constant.PAGE));
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
PageData<AdminPageUserVO> page = sysUserService.page(dto);
|
||||
return new Result<PageData<AdminPageUserVO>>().ok(page);
|
||||
}
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package xiaozhi.modules.sys.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||
import xiaozhi.modules.sys.service.SysDictDataService;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataVO;
|
||||
|
||||
/**
|
||||
* 字典数据管理
|
||||
*
|
||||
* @author czc
|
||||
* @since 2025-04-30
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/admin/dict/data")
|
||||
@Tag(name = "字典数据管理")
|
||||
public class SysDictDataController {
|
||||
private final SysDictDataService sysDictDataService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询字典数据")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameters({ @Parameter(name = "dictTypeId", description = "字典类型ID", required = true),
|
||||
@Parameter(name = "dictLabel", description = "数据标签"), @Parameter(name = "dictValue", description = "数据值"),
|
||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true) })
|
||||
public Result<PageData<SysDictDataVO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||
ValidatorUtils.validateEntity(params);
|
||||
// 强制校验dictTypeId是否存在
|
||||
if (!params.containsKey("dictTypeId") || StringUtils.isEmpty(String.valueOf(params.get("dictTypeId")))) {
|
||||
return new Result<PageData<SysDictDataVO>>().error("dictTypeId不能为空");
|
||||
}
|
||||
|
||||
PageData<SysDictDataVO> page = sysDictDataService.page(params);
|
||||
return new Result<PageData<SysDictDataVO>>().ok(page);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取字典数据详情")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<SysDictDataVO> get(@PathVariable("id") Long id) {
|
||||
SysDictDataVO vo = sysDictDataService.get(id);
|
||||
return new Result<SysDictDataVO>().ok(vo);
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@Operation(summary = "新增字典数据")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> save(@RequestBody SysDictDataDTO dto) {
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
sysDictDataService.save(dto);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改字典数据")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> update(@RequestBody SysDictDataDTO dto) {
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
sysDictDataService.update(dto);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除字典数据")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameter(name = "ids", description = "ID数组", required = true)
|
||||
public Result<Void> delete(@RequestBody Long[] ids) {
|
||||
sysDictDataService.delete(ids);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/type/{dictType}")
|
||||
@Operation(summary = "获取字典数据列表")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<SysDictDataItem>> getDictDataByType(@PathVariable("dictType") String dictType) {
|
||||
List<SysDictDataItem> list = sysDictDataService.getDictDataByType(dictType);
|
||||
return new Result<List<SysDictDataItem>>().ok(list);
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package xiaozhi.modules.sys.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
||||
import xiaozhi.modules.sys.service.SysDictTypeService;
|
||||
import xiaozhi.modules.sys.vo.SysDictTypeVO;
|
||||
|
||||
/**
|
||||
* 字典类型管理
|
||||
*
|
||||
* @author czc
|
||||
* @since 2025-04-30
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/admin/dict/type")
|
||||
@Tag(name = "字典类型管理")
|
||||
public class SysDictTypeController {
|
||||
private final SysDictTypeService sysDictTypeService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询字典类型")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameters({ @Parameter(name = "dictType", description = "字典类型编码"),
|
||||
@Parameter(name = "dictName", description = "字典类型名称"),
|
||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true) })
|
||||
public Result<PageData<SysDictTypeVO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||
ValidatorUtils.validateEntity(params);
|
||||
PageData<SysDictTypeVO> page = sysDictTypeService.page(params);
|
||||
return new Result<PageData<SysDictTypeVO>>().ok(page);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取字典类型详情")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<SysDictTypeVO> get(@PathVariable("id") Long id) {
|
||||
SysDictTypeVO vo = sysDictTypeService.get(id);
|
||||
return new Result<SysDictTypeVO>().ok(vo);
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@Operation(summary = "保存字典类型")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> save(@RequestBody SysDictTypeDTO dto) {
|
||||
// 参数校验
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
|
||||
sysDictTypeService.save(dto);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改字典类型")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> update(@RequestBody SysDictTypeDTO dto) {
|
||||
// 参数校验
|
||||
ValidatorUtils.validateEntity(dto);
|
||||
|
||||
sysDictTypeService.update(dto);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除字典类型")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameter(name = "ids", description = "ID数组", required = true)
|
||||
public Result<Void> delete(@RequestBody Long[] ids) {
|
||||
sysDictTypeService.delete(ids);
|
||||
return new Result<>();
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,8 @@ import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||
import xiaozhi.modules.sys.entity.DictData;
|
||||
import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
|
||||
/**
|
||||
* 字典数据
|
||||
@@ -15,10 +14,13 @@ import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
||||
@Mapper
|
||||
public interface SysDictDataDao extends BaseDao<SysDictDataEntity> {
|
||||
|
||||
/**
|
||||
* 字典数据列表
|
||||
*/
|
||||
List<DictData> getDictDataList();
|
||||
List<SysDictDataItem> getDictDataByType(String dictType);
|
||||
|
||||
List<SysDictDataDTO> getDataByTypeCode(String dictType);
|
||||
/**
|
||||
* 根据字典类型ID获取字典类型编码
|
||||
*
|
||||
* @param dictTypeId 字典类型ID
|
||||
* @return 字典类型编码
|
||||
*/
|
||||
String getTypeByTypeId(Long dictTypeId);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package xiaozhi.modules.sys.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.sys.entity.DictType;
|
||||
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||
|
||||
/**
|
||||
@@ -14,9 +11,4 @@ import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||
@Mapper
|
||||
public interface SysDictTypeDao extends BaseDao<SysDictTypeEntity> {
|
||||
|
||||
/**
|
||||
* 字典类型列表
|
||||
*/
|
||||
List<DictType> getDictTypeList();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package xiaozhi.modules.sys.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "配置密钥DTO")
|
||||
public class ConfigSecretDTO {
|
||||
@Schema(description = "密钥")
|
||||
@NotBlank(message = "密钥不能为空")
|
||||
private String secret;
|
||||
}
|
||||
@@ -51,5 +51,6 @@ public class SysDictTypeDTO implements Serializable {
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||
private Date updateDate;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package xiaozhi.modules.sys.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典数据
|
||||
*/
|
||||
@Data
|
||||
public class DictData {
|
||||
@JsonIgnore
|
||||
private Long dictTypeId;
|
||||
private String dictLabel;
|
||||
private String dictValue;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package xiaozhi.modules.sys.entity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@Data
|
||||
public class DictType {
|
||||
@JsonIgnore
|
||||
private Long id;
|
||||
private String dictType;
|
||||
private List<DictData> dataList = new ArrayList<>();
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import xiaozhi.common.entity.BaseEntity;
|
||||
@TableName("sys_dict_type")
|
||||
public class SysDictTypeEntity extends BaseEntity {
|
||||
/**
|
||||
* 字典类型
|
||||
* 字典类型编码
|
||||
*/
|
||||
private String dictType;
|
||||
/**
|
||||
|
||||
@@ -1,25 +1,70 @@
|
||||
package xiaozhi.modules.sys.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||
import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataVO;
|
||||
|
||||
/**
|
||||
* 数据字典
|
||||
*/
|
||||
public interface SysDictDataService extends BaseService<SysDictDataEntity> {
|
||||
|
||||
PageData<SysDictDataDTO> page(Map<String, Object> params);
|
||||
/**
|
||||
* 分页查询数据字典信息
|
||||
*
|
||||
* @param params 查询参数,包含分页信息和查询条件
|
||||
* @return 返回数据字典的分页查询结果
|
||||
*/
|
||||
PageData<SysDictDataVO> page(Map<String, Object> params);
|
||||
|
||||
SysDictDataDTO get(Long id);
|
||||
/**
|
||||
* 根据ID获取数据字典实体
|
||||
*
|
||||
* @param id 数据字典实体的唯一标识
|
||||
* @return 返回数据字典实体的详细信息
|
||||
*/
|
||||
SysDictDataVO get(Long id);
|
||||
|
||||
/**
|
||||
* 保存新的数据字典项
|
||||
*
|
||||
* @param dto 数据字典项的保存数据传输对象
|
||||
*/
|
||||
void save(SysDictDataDTO dto);
|
||||
|
||||
/**
|
||||
* 更新数据字典项
|
||||
*
|
||||
* @param dto 数据字典项的更新数据传输对象
|
||||
*/
|
||||
void update(SysDictDataDTO dto);
|
||||
|
||||
/**
|
||||
* 删除数据字典项
|
||||
*
|
||||
* @param ids 要删除的数据字典项的ID数组
|
||||
*/
|
||||
void delete(Long[] ids);
|
||||
|
||||
/**
|
||||
* 根据字典类型ID删除对应的字典数据
|
||||
*
|
||||
* @param dictTypeId 字典类型ID
|
||||
*/
|
||||
void deleteByTypeId(Long dictTypeId);
|
||||
|
||||
/**
|
||||
* 根据字典类型获取字典数据列表
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @return 返回字典数据列表
|
||||
*/
|
||||
List<SysDictDataItem> getDictDataByType(String dictType);
|
||||
|
||||
}
|
||||
@@ -6,25 +6,55 @@ import java.util.Map;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
||||
import xiaozhi.modules.sys.entity.DictType;
|
||||
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||
import xiaozhi.modules.sys.vo.SysDictTypeVO;
|
||||
|
||||
/**
|
||||
* 数据字典
|
||||
*/
|
||||
public interface SysDictTypeService extends BaseService<SysDictTypeEntity> {
|
||||
|
||||
PageData<SysDictTypeDTO> page(Map<String, Object> params);
|
||||
/**
|
||||
* 分页查询字典类型信息
|
||||
*
|
||||
* @param params 查询参数,包含分页信息和查询条件
|
||||
* @return 返回分页的字典类型数据
|
||||
*/
|
||||
PageData<SysDictTypeVO> page(Map<String, Object> params);
|
||||
|
||||
SysDictTypeDTO get(Long id);
|
||||
/**
|
||||
* 根据ID获取字典类型信息
|
||||
*
|
||||
* @param id 字典类型ID
|
||||
* @return 返回字典类型对象
|
||||
*/
|
||||
SysDictTypeVO get(Long id);
|
||||
|
||||
/**
|
||||
* 保存字典类型信息
|
||||
*
|
||||
* @param dto 字典类型数据传输对象
|
||||
*/
|
||||
void save(SysDictTypeDTO dto);
|
||||
|
||||
/**
|
||||
* 更新字典类型信息
|
||||
*
|
||||
* @param dto 字典类型数据传输对象
|
||||
*/
|
||||
void update(SysDictTypeDTO dto);
|
||||
|
||||
/**
|
||||
* 删除字典类型信息
|
||||
*
|
||||
* @param ids 要删除的字典类型ID数组
|
||||
*/
|
||||
void delete(Long[] ids);
|
||||
|
||||
List<DictType> getAllList();
|
||||
|
||||
List<DictType> getDictTypeList();
|
||||
/**
|
||||
* 列出所有字典类型信息
|
||||
*
|
||||
* @return 返回字典类型列表
|
||||
*/
|
||||
List<SysDictTypeVO> list(Map<String, Object> params);
|
||||
}
|
||||
+114
-10
@@ -1,37 +1,55 @@
|
||||
package xiaozhi.modules.sys.service.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||
import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||
import xiaozhi.modules.sys.service.SysDictDataService;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataVO;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysDictDataEntity>
|
||||
implements SysDictDataService {
|
||||
private final SysUserDao sysUserDao;
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
@Override
|
||||
public PageData<SysDictDataDTO> page(Map<String, Object> params) {
|
||||
IPage<SysDictDataEntity> page = baseDao.selectPage(
|
||||
getPage(params, "sort", true),
|
||||
getWrapper(params));
|
||||
public PageData<SysDictDataVO> page(Map<String, Object> params) {
|
||||
IPage<SysDictDataEntity> page = baseDao.selectPage(getPage(params, "sort", true), getWrapper(params));
|
||||
|
||||
return getPageData(page, SysDictDataDTO.class);
|
||||
PageData<SysDictDataVO> pageData = getPageData(page, SysDictDataVO.class);
|
||||
|
||||
setUserName(pageData.getList());
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
private QueryWrapper<SysDictDataEntity> getWrapper(Map<String, Object> params) {
|
||||
@@ -48,32 +66,118 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysDictDataDTO get(Long id) {
|
||||
public SysDictDataVO get(Long id) {
|
||||
SysDictDataEntity entity = baseDao.selectById(id);
|
||||
|
||||
return ConvertUtils.sourceToTarget(entity, SysDictDataDTO.class);
|
||||
return ConvertUtils.sourceToTarget(entity, SysDictDataVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void save(SysDictDataDTO dto) {
|
||||
// 相同字典类型的标签不能相同
|
||||
checkDictValueUnique(dto.getDictTypeId(), dto.getDictValue(), null);
|
||||
|
||||
SysDictDataEntity entity = ConvertUtils.sourceToTarget(dto, SysDictDataEntity.class);
|
||||
|
||||
insert(entity);
|
||||
// 删除Redis缓存
|
||||
String dictType = baseDao.getTypeByTypeId(dto.getDictTypeId());
|
||||
redisUtils.delete(RedisKeys.getDictDataByTypeKey(dictType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(SysDictDataDTO dto) {
|
||||
// 相同字典类型的标签不能相同
|
||||
checkDictValueUnique(dto.getDictTypeId(), dto.getDictValue(), String.valueOf(dto.getId()));
|
||||
|
||||
SysDictDataEntity entity = ConvertUtils.sourceToTarget(dto, SysDictDataEntity.class);
|
||||
|
||||
updateById(entity);
|
||||
// 删除Redis缓存
|
||||
String dictType = baseDao.getTypeByTypeId(dto.getDictTypeId());
|
||||
redisUtils.delete(RedisKeys.getDictDataByTypeKey(dictType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long[] ids) {
|
||||
// 删除
|
||||
deleteBatchIds(Arrays.asList(ids));
|
||||
for (Long id : ids) {
|
||||
SysDictDataEntity entity = baseDao.selectById(id);
|
||||
// 删除Redis缓存
|
||||
String dictType = baseDao.getTypeByTypeId(entity.getDictTypeId());
|
||||
redisUtils.delete(RedisKeys.getDictDataByTypeKey(dictType));
|
||||
// 删除
|
||||
deleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteByTypeId(Long dictTypeId) {
|
||||
LambdaQueryWrapper<SysDictDataEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SysDictDataEntity::getDictTypeId, dictTypeId);
|
||||
baseDao.delete(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户名
|
||||
*
|
||||
* @param sysDictDataList 字典类型集合
|
||||
*/
|
||||
private void setUserName(List<SysDictDataVO> sysDictDataList) {
|
||||
// 收集所有用户 ID
|
||||
Set<Long> userIds = sysDictDataList.stream().flatMap(vo -> Stream.of(vo.getCreator(), vo.getUpdater()))
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
|
||||
// 设置更新者和创建者名称
|
||||
if (!userIds.isEmpty()) {
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
||||
// 把List转成Map,Map<Long, String>
|
||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||
|
||||
sysDictDataList.forEach(vo -> {
|
||||
vo.setCreatorName(userNameMap.get(vo.getCreator()));
|
||||
vo.setUpdaterName(userNameMap.get(vo.getUpdater()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDictValueUnique(Long dictTypeId, String dictValue, String excludeId) {
|
||||
LambdaQueryWrapper<SysDictDataEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysDictDataEntity::getDictTypeId, dictTypeId).eq(SysDictDataEntity::getDictLabel, dictValue);
|
||||
if (StringUtils.isNotBlank(excludeId)) {
|
||||
queryWrapper.ne(SysDictDataEntity::getId, excludeId);
|
||||
}
|
||||
boolean exists = baseDao.exists(queryWrapper);
|
||||
if (exists) {
|
||||
throw new RenException("字典标签重复");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysDictDataItem> getDictDataByType(String dictType) {
|
||||
if (StringUtils.isBlank(dictType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 先从Redis获取缓存
|
||||
String key = RedisKeys.getDictDataByTypeKey(dictType);
|
||||
List<SysDictDataItem> cachedData = (List<SysDictDataItem>) redisUtils.get(key);
|
||||
if (cachedData != null) {
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
// 如果缓存中没有,则从数据库获取
|
||||
List<SysDictDataItem> data = baseDao.getDictDataByType(dictType);
|
||||
|
||||
// 存入Redis缓存
|
||||
if (data != null) {
|
||||
redisUtils.set(key, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
+76
-27
@@ -3,42 +3,52 @@ package xiaozhi.modules.sys.service.impl;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||
import xiaozhi.modules.sys.dao.SysDictTypeDao;
|
||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
||||
import xiaozhi.modules.sys.entity.DictData;
|
||||
import xiaozhi.modules.sys.entity.DictType;
|
||||
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||
import xiaozhi.modules.sys.service.SysDictDataService;
|
||||
import xiaozhi.modules.sys.service.SysDictTypeService;
|
||||
import xiaozhi.modules.sys.vo.SysDictTypeVO;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysDictTypeEntity>
|
||||
implements SysDictTypeService {
|
||||
private final SysDictDataDao sysDictDataDao;
|
||||
private final SysUserDao sysUserDao;
|
||||
private final SysDictDataService sysDictDataService;
|
||||
|
||||
@Override
|
||||
public PageData<SysDictTypeDTO> page(Map<String, Object> params) {
|
||||
IPage<SysDictTypeEntity> page = baseDao.selectPage(
|
||||
getPage(params, "sort", true),
|
||||
getWrapper(params));
|
||||
public PageData<SysDictTypeVO> page(Map<String, Object> params) {
|
||||
IPage<SysDictTypeEntity> page = baseDao.selectPage(getPage(params, "sort", true), getWrapper(params));
|
||||
|
||||
return getPageData(page, SysDictTypeDTO.class);
|
||||
PageData<SysDictTypeVO> pageData = getPageData(page, SysDictTypeVO.class);
|
||||
|
||||
setUserName(pageData.getList());
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
private QueryWrapper<SysDictTypeEntity> getWrapper(Map<String, Object> params) {
|
||||
@@ -53,15 +63,21 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysDictTypeDTO get(Long id) {
|
||||
public SysDictTypeVO get(Long id) {
|
||||
SysDictTypeEntity entity = baseDao.selectById(id);
|
||||
if (entity == null) {
|
||||
throw new RenException("字典类型不存在");
|
||||
}
|
||||
|
||||
return ConvertUtils.sourceToTarget(entity, SysDictTypeDTO.class);
|
||||
return ConvertUtils.sourceToTarget(entity, SysDictTypeVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void save(SysDictTypeDTO dto) {
|
||||
// 字典类型编码不能重复
|
||||
checkDictTypeUnique(dto.getDictType(), null);
|
||||
|
||||
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
||||
|
||||
insert(entity);
|
||||
@@ -70,6 +86,9 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(SysDictTypeDTO dto) {
|
||||
// 字典类型编码不能重复
|
||||
checkDictTypeUnique(dto.getDictType(), String.valueOf(dto.getId()));
|
||||
|
||||
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
||||
|
||||
updateById(entity);
|
||||
@@ -78,27 +97,57 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long[] ids) {
|
||||
// 删除
|
||||
// 先删除对应的字典数据
|
||||
for (Long id : ids) {
|
||||
sysDictDataService.deleteByTypeId(id);
|
||||
}
|
||||
// 再删除字典类型
|
||||
deleteBatchIds(Arrays.asList(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DictType> getAllList() {
|
||||
List<DictType> typeList = baseDao.getDictTypeList();
|
||||
List<DictData> dataList = sysDictDataDao.getDictDataList();
|
||||
for (DictType type : typeList) {
|
||||
for (DictData data : dataList) {
|
||||
if (type.getId().equals(data.getDictTypeId())) {
|
||||
type.getDataList().add(data);
|
||||
}
|
||||
}
|
||||
public List<SysDictTypeVO> list(Map<String, Object> params) {
|
||||
List<SysDictTypeEntity> entityList = baseDao.selectList(getWrapper(params));
|
||||
List<SysDictTypeVO> sysDictTypeVOList = ConvertUtils.sourceToTarget(entityList, SysDictTypeVO.class);
|
||||
// 设置用户名
|
||||
setUserName(sysDictTypeVOList);
|
||||
|
||||
return sysDictTypeVOList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户名
|
||||
*
|
||||
* @param sysDictTypeList 字典类型集合
|
||||
*/
|
||||
private void setUserName(List<SysDictTypeVO> sysDictTypeList) {
|
||||
// 收集所有用户 ID
|
||||
Set<Long> userIds = sysDictTypeList.stream().flatMap(vo -> Stream.of(vo.getCreator(), vo.getUpdater()))
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
|
||||
// 设置更新者和创建者名称
|
||||
if (!userIds.isEmpty()) {
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
||||
// 把List转成Map,Map<Long, String>
|
||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||
|
||||
sysDictTypeList.forEach(vo -> {
|
||||
vo.setCreatorName(userNameMap.get(vo.getCreator()));
|
||||
vo.setUpdaterName(userNameMap.get(vo.getUpdater()));
|
||||
});
|
||||
}
|
||||
return typeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DictType> getDictTypeList() {
|
||||
return baseDao.getDictTypeList();
|
||||
private void checkDictTypeUnique(String dictType, String excludeId) {
|
||||
LambdaQueryWrapper<SysDictTypeEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysDictTypeEntity::getDictType, dictType);
|
||||
if (StringUtils.isNotBlank(excludeId)) {
|
||||
queryWrapper.ne(SysDictTypeEntity::getId, excludeId);
|
||||
}
|
||||
boolean exists = baseDao.exists(queryWrapper);
|
||||
if (exists) {
|
||||
throw new RenException("字典类型编码重复");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -169,6 +169,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
||||
String deviceCount = deviceService.selectCountByUserId(user.getId()).toString();
|
||||
adminPageUserVO.setDeviceCount(deviceCount);
|
||||
adminPageUserVO.setStatus(user.getStatus());
|
||||
adminPageUserVO.setCreateDate(user.getCreateDate());
|
||||
return adminPageUserVO;
|
||||
}).toList();
|
||||
return new PageData<>(list, page.getTotal());
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@ public class SysUserUtilServiceImpl extends BaseServiceImpl<SysUserDao, SysUserE
|
||||
@Override
|
||||
public void assignUsername(Long userId, Consumer<String> setter) {
|
||||
String userIdKey = RedisKeys.getUserIdKey(userId);
|
||||
String username = redisUtils.get(userIdKey).toString();
|
||||
|
||||
Object value = redisUtils.get(userIdKey);
|
||||
String username = (value != null) ? value.toString() : null;
|
||||
if(username != null){
|
||||
setter.accept(username);
|
||||
}else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||
|
||||
@@ -45,8 +46,9 @@ public class WebSocketValidator {
|
||||
try {
|
||||
WebSocketClient client = new StandardWebSocketClient();
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
|
||||
|
||||
client.doHandshake(new WebSocketTestHandler(future), null, URI.create(url));
|
||||
client.execute(new WebSocketTestHandler(future), headers, URI.create(url));
|
||||
|
||||
// 等待最多5秒获取连接结果
|
||||
return future.get(5, TimeUnit.SECONDS);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package xiaozhi.modules.sys.vo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -23,4 +25,7 @@ public class AdminPageUserVO {
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private String userid;
|
||||
|
||||
@Schema(description = "注册时间")
|
||||
private Date createDate;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package xiaozhi.modules.sys.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典数据VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "字典数据项")
|
||||
public class SysDictDataItem implements Serializable {
|
||||
|
||||
@Schema(description = "字典标签")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "字典值")
|
||||
private String key;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package xiaozhi.modules.sys.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 字典数据VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "字典数据VO")
|
||||
public class SysDictDataVO implements Serializable {
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "字典类型ID")
|
||||
private Long dictTypeId;
|
||||
|
||||
@Schema(description = "字典标签")
|
||||
private String dictLabel;
|
||||
|
||||
@Schema(description = "字典值")
|
||||
private String dictValue;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建者名称")
|
||||
private String creatorName;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createDate;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新者名称")
|
||||
private String updaterName;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateDate;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package xiaozhi.modules.sys.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 字典类型VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "字典类型VO")
|
||||
public class SysDictTypeVO implements Serializable {
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "字典类型")
|
||||
private String dictType;
|
||||
|
||||
@Schema(description = "字典名称")
|
||||
private String dictName;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建者名称")
|
||||
private String creatorName;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createDate;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新者名称")
|
||||
private String updaterName;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateDate;
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
-- 增加FunASR服务语音识别模型供应器和配置
|
||||
DELETE FROM `ai_model_provider` WHERE `id` = 'SYSTEM_ASR_FunASRServer';
|
||||
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||
('SYSTEM_ASR_FunASRServer', 'ASR', 'fun_server', 'FunASR服务语音识别', '[{"key":"host","label":"服务地址","type":"string"},{"key":"port","label":"端口号","type":"number"}]', 4, 1, NOW(), 1, NOW());
|
||||
|
||||
DELETE FROM `ai_model_config` WHERE `id` = 'ASR_FunASRServer';
|
||||
INSERT INTO `ai_model_config` VALUES ('ASR_FunASRServer', 'ASR', 'FunASRServer', 'FunASR服务语音识别', 0, 1, '{\"type\": \"fun_server\", \"host\": \"127.0.0.1\", \"port\": 10096}', NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 修改ai_model_config表的remark字段类型为TEXT
|
||||
ALTER TABLE `ai_model_config` MODIFY COLUMN `remark` TEXT COMMENT '备注';
|
||||
|
||||
-- 更新ASR模型配置的说明文档
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md',
|
||||
`remark` = '独立部署FunASR,使用FunASR的API服务,只需要五句话
|
||||
第一句:mkdir -p ./funasr-runtime-resources/models
|
||||
第二句:sudo docker run -d -p 10096:10095 --privileged=true -v $PWD/funasr-runtime-resources/models:/workspace/models registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12
|
||||
上一句话执行后会进入到容器,继续第三句:cd FunASR/runtime
|
||||
不要退出容器,继续在容器中执行第四句:nohup bash run_server_2pass.sh --download-model-dir /workspace/models --vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx --model-dir damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-onnx --online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx --punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx --lm-dir damo/speech_ngram_lm_zh-cn-ai-wesp-fst --itn-dir thuduj12/fst_itn_zh --hotword /workspace/models/hotwords.txt > log.txt 2>&1 &
|
||||
上一句话执行后会进入到容器,继续第五句:tail -f log.txt
|
||||
第五句话执行完后,会看到模型下载日志,下载完后就可以连接使用了
|
||||
以上是使用CPU推理,如果有GPU,详细参考:https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md' WHERE `id` = 'ASR_FunASRServer';
|
||||
|
||||
-- 更新FunASR本地模型配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/modelscope/FunASR',
|
||||
`remark` = 'FunASR本地模型配置说明:
|
||||
1. 需要下载模型文件到xiaozhi-server/models/SenseVoiceSmall目录
|
||||
2. 支持中日韩粤语音识别
|
||||
3. 本地推理,无需网络连接
|
||||
4. 待识别文件保存在tmp/目录' WHERE `id` = 'ASR_FunASR';
|
||||
|
||||
-- 更新SherpaASR配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/k2-fsa/sherpa-onnx',
|
||||
`remark` = 'SherpaASR配置说明:
|
||||
1. 运行时自动下载模型文件到models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17目录
|
||||
2. 支持中文、英文、日语、韩语、粤语等多种语言
|
||||
3. 本地推理,无需网络连接
|
||||
4. 输出文件保存在tmp/目录' WHERE `id` = 'ASR_SherpaASR';
|
||||
|
||||
-- 更新豆包ASR配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.volcengine.com/speech/app',
|
||||
`remark` = '豆包ASR配置说明:
|
||||
1. 需要在火山引擎控制台创建应用并获取appid和access_token
|
||||
2. 支持中文语音识别
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://console.volcengine.com/speech/app
|
||||
2. 创建新应用
|
||||
3. 获取appid和access_token
|
||||
4. 填入配置文件中' WHERE `id` = 'ASR_DoubaoASR';
|
||||
|
||||
-- 更新腾讯ASR配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.cloud.tencent.com/cam/capi',
|
||||
`remark` = '腾讯ASR配置说明:
|
||||
1. 需要在腾讯云控制台创建应用并获取appid、secret_id和secret_key
|
||||
2. 支持中文语音识别
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://console.cloud.tencent.com/cam/capi 获取密钥
|
||||
2. 访问 https://console.cloud.tencent.com/asr/resourcebundle 领取免费资源
|
||||
3. 获取appid、secret_id和secret_key
|
||||
4. 填入配置文件中' WHERE `id` = 'ASR_TencentASR';
|
||||
|
||||
-- 更新TTS模型配置说明
|
||||
-- EdgeTTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/rany2/edge-tts',
|
||||
`remark` = 'EdgeTTS配置说明:
|
||||
1. 使用微软Edge TTS服务
|
||||
2. 支持多种语言和音色
|
||||
3. 免费使用,无需注册
|
||||
4. 需要网络连接
|
||||
5. 输出文件保存在tmp/目录' WHERE `id` = 'TTS_EdgeTTS';
|
||||
|
||||
-- 豆包TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.volcengine.com/speech/service/8',
|
||||
`remark` = '豆包TTS配置说明:
|
||||
1. 访问 https://console.volcengine.com/speech/service/8
|
||||
2. 需要在火山引擎控制台创建应用并获取appid和access_token
|
||||
3. 山引擎语音一定要购买花钱,起步价30元,就有100并发了。如果用免费的只有2个并发,会经常报tts错误
|
||||
4. 购买服务后,购买免费的音色后,可能要等半小时左右,才能使用。
|
||||
5. 填入配置文件中' WHERE `id` = 'TTS_DoubaoTTS';
|
||||
|
||||
-- 硅基流动TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://cloud.siliconflow.cn/account/ak',
|
||||
`remark` = '硅基流动TTS配置说明:
|
||||
1. 访问 https://cloud.siliconflow.cn/account/ak
|
||||
2. 注册并获取API密钥
|
||||
3. 填入配置文件中' WHERE `id` = 'TTS_CosyVoiceSiliconflow';
|
||||
|
||||
-- Coze中文TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://www.coze.cn/open/oauth/pats',
|
||||
`remark` = 'Coze中文TTS配置说明:
|
||||
1. 访问 https://www.coze.cn/open/oauth/pats
|
||||
2. 获取个人令牌
|
||||
3. 填入配置文件中' WHERE `id` = 'TTS_CozeCnTTS';
|
||||
|
||||
-- FishSpeech配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/fishaudio/fish-speech',
|
||||
`remark` = 'FishSpeech配置说明:
|
||||
1. 需要本地部署FishSpeech服务
|
||||
2. 支持自定义音色
|
||||
3. 本地推理,无需网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
5. 运行服务示例命令:python -m tools.api_server --listen 0.0.0.0:8080 --llama-checkpoint-path "checkpoints/fish-speech-1.5" --decoder-checkpoint-path "checkpoints/fish-speech-1.5/firefly-gan-vq-fsq-8x1024-21hz-generator.pth" --decoder-config-name firefly_gan_vq --compile' WHERE `id` = 'TTS_FishSpeech';
|
||||
|
||||
-- GPT-SoVITS V2配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/RVC-Boss/GPT-SoVITS',
|
||||
`remark` = 'GPT-SoVITS V2配置说明:
|
||||
1. 需要本地部署GPT-SoVITS服务
|
||||
2. 支持自定义音色克隆
|
||||
3. 本地推理,无需网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
部署步骤:
|
||||
1. 运行服务示例命令:python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/demo.yaml' WHERE `id` = 'TTS_GPT_SOVITS_V2';
|
||||
|
||||
-- GPT-SoVITS V3配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/RVC-Boss/GPT-SoVITS',
|
||||
`remark` = 'GPT-SoVITS V3配置说明:
|
||||
1. 需要本地部署GPT-SoVITS V3服务
|
||||
2. 支持自定义音色克隆
|
||||
3. 本地推理,无需网络连接
|
||||
4. 输出文件保存在tmp/目录' WHERE `id` = 'TTS_GPT_SOVITS_V3';
|
||||
|
||||
-- MiniMax TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://platform.minimaxi.com/',
|
||||
`remark` = 'MiniMax TTS配置说明:
|
||||
1. 需要在MiniMax平台创建账户并充值
|
||||
2. 支持多种音色,当前配置使用female-shaonv
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://platform.minimaxi.com/ 注册账号
|
||||
2. 访问 https://platform.minimaxi.com/user-center/payment/balance 充值
|
||||
3. 访问 https://platform.minimaxi.com/user-center/basic-information 获取group_id
|
||||
4. 访问 https://platform.minimaxi.com/user-center/basic-information/interface-key 获取api_key
|
||||
5. 填入配置文件中' WHERE `id` = 'TTS_MinimaxTTS';
|
||||
|
||||
-- 阿里云TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://nls-portal.console.aliyun.com/',
|
||||
`remark` = '阿里云TTS配置说明:
|
||||
1. 需要在阿里云平台开通智能语音交互服务
|
||||
2. 支持多种音色,当前配置使用xiaoyun
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://nls-portal.console.aliyun.com/ 开通服务
|
||||
2. 访问 https://nls-portal.console.aliyun.com/applist 获取appkey
|
||||
3. 访问 https://nls-portal.console.aliyun.com/overview 获取token
|
||||
4. 填入配置文件中
|
||||
注意:token是临时的24小时有效,长期使用需要配置access_key_id和access_key_secret' WHERE `id` = 'TTS_AliyunTTS';
|
||||
|
||||
-- 腾讯TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.cloud.tencent.com/cam/capi',
|
||||
`remark` = '腾讯TTS配置说明:
|
||||
1. 需要在腾讯云平台开通智能语音交互服务
|
||||
2. 支持多种音色,当前配置使用101001
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://console.cloud.tencent.com/cam/capi 获取密钥
|
||||
2. 访问 https://console.cloud.tencent.com/tts/resourcebundle 领取免费资源
|
||||
3. 创建新应用
|
||||
4. 获取appid、secret_id和secret_key
|
||||
5. 填入配置文件中' WHERE `id` = 'TTS_TencentTTS';
|
||||
|
||||
-- 302AI TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://dash.302.ai/',
|
||||
`remark` = '302AI TTS配置说明:
|
||||
1. 需要在302平台创建账户并获取API密钥
|
||||
2. 支持多种音色,当前配置使用湾湾小何音色
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://dash.302.ai/ 注册账号
|
||||
2. 访问 https://dash.302.ai/apis/list 获取API密钥
|
||||
3. 填入配置文件中
|
||||
价格:$35/百万字符' WHERE `id` = 'TTS_TTS302AI';
|
||||
|
||||
-- 机智云TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://agentrouter.gizwitsapi.com/panel/token',
|
||||
`remark` = '机智云TTS配置说明:
|
||||
1. 需要在机智云平台获取API密钥
|
||||
2. 支持多种音色,当前配置使用湾湾小何音色
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://agentrouter.gizwitsapi.com/panel/token 获取API密钥
|
||||
2. 填入配置文件中
|
||||
注意:前一万名注册的用户,将送5元体验金额' WHERE `id` = 'TTS_GizwitsTTS';
|
||||
|
||||
-- ACGN TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://acgn.ttson.cn/',
|
||||
`remark` = 'ACGN TTS配置说明:
|
||||
1. 需要在ttson平台购买token
|
||||
2. 支持多种角色音色,当前配置使用角色ID:1695
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://acgn.ttson.cn/ 查看角色列表
|
||||
2. 访问 www.ttson.cn 购买token
|
||||
3. 填入配置文件中
|
||||
开发相关疑问请提交至网站上的qq' WHERE `id` = 'TTS_ACGNTTS';
|
||||
|
||||
-- OpenAI TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://platform.openai.com/api-keys',
|
||||
`remark` = 'OpenAI TTS配置说明:
|
||||
1. 需要在OpenAI平台获取API密钥
|
||||
2. 支持多种音色,当前配置使用onyx
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
申请步骤:
|
||||
1. 访问 https://platform.openai.com/api-keys 获取API密钥
|
||||
2. 填入配置文件中
|
||||
注意:国内需要使用代理访问' WHERE `id` = 'TTS_OpenAITTS';
|
||||
|
||||
-- 自定义TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = '自定义TTS配置说明:
|
||||
1. 支持自定义TTS接口服务
|
||||
2. 使用GET方式请求
|
||||
3. 需要网络连接
|
||||
4. 输出文件保存在tmp/目录
|
||||
配置说明:
|
||||
1. 在params中配置请求参数
|
||||
2. 在headers中配置请求头
|
||||
3. 设置返回音频格式' WHERE `id` = 'TTS_CustomTTS';
|
||||
|
||||
-- 火山引擎边缘大模型网关TTS配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.volcengine.com/vei/aigateway/',
|
||||
`remark` = '火山引擎边缘大模型网关TTS配置说明:
|
||||
1. 访问 https://console.volcengine.com/vei/aigateway/
|
||||
2. 创建网关访问密钥,搜索并勾选 Doubao-语音合成
|
||||
3. 如果需要使用LLM,一并勾选 Doubao-pro-32k-functioncall
|
||||
4. 访问 https://console.volcengine.com/vei/aigateway/tokens-list 获取密钥
|
||||
5. 填入配置文件中
|
||||
音色列表参考:https://www.volcengine.com/docs/6561/1257544' WHERE `id` = 'TTS_VolcesAiGatewayTTS';
|
||||
|
||||
-- 更新LLM模型配置说明
|
||||
-- ChatGLM配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://bigmodel.cn/usercenter/proj-mgmt/apikeys',
|
||||
`remark` = 'ChatGLM配置说明:
|
||||
1. 访问 https://bigmodel.cn/usercenter/proj-mgmt/apikeys
|
||||
2. 注册并获取API密钥
|
||||
3. 填入配置文件中' WHERE `id` = 'LLM_ChatGLMLLM';
|
||||
|
||||
-- Ollama配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://ollama.com/',
|
||||
`remark` = 'Ollama配置说明:
|
||||
1. 安装Ollama服务
|
||||
2. 运行命令:ollama pull qwen2.5
|
||||
3. 确保服务运行在http://localhost:11434' WHERE `id` = 'LLM_OllamaLLM';
|
||||
|
||||
-- 通义千问配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://bailian.console.aliyun.com/?apiKey=1#/api-key',
|
||||
`remark` = '通义千问配置说明:
|
||||
1. 访问 https://bailian.console.aliyun.com/?apiKey=1#/api-key
|
||||
2. 获取API密钥
|
||||
3. 填入配置文件中,当前配置使用qwen-turbo模型
|
||||
4. 支持自定义参数:temperature=0.7, max_tokens=500, top_p=1, top_k=50' WHERE `id` = 'LLM_AliLLM';
|
||||
|
||||
-- 通义百炼配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://bailian.console.aliyun.com/?apiKey=1#/api-key',
|
||||
`remark` = '通义百炼配置说明:
|
||||
1. 访问 https://bailian.console.aliyun.com/?apiKey=1#/api-key
|
||||
2. 获取app_id和api_key
|
||||
3. 填入配置文件中' WHERE `id` = 'LLM_AliAppLLM';
|
||||
|
||||
-- 豆包大模型配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement',
|
||||
`remark` = '豆包大模型配置说明:
|
||||
1. 访问 https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement
|
||||
2. 开通Doubao-1.5-pro服务
|
||||
3. 访问 https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey 获取API密钥
|
||||
4. 填入配置文件中
|
||||
5. 当前建议使用doubao-1-5-pro-32k-250115
|
||||
注意:有免费额度500000token' WHERE `id` = 'LLM_DoubaoLLM';
|
||||
|
||||
-- DeepSeek配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://platform.deepseek.com/',
|
||||
`remark` = 'DeepSeek配置说明:
|
||||
1. 访问 https://platform.deepseek.com/
|
||||
2. 注册并获取API密钥
|
||||
3. 填入配置文件中' WHERE `id` = 'LLM_DeepSeekLLM';
|
||||
|
||||
-- Dify配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://cloud.dify.ai/',
|
||||
`remark` = 'Dify配置说明:
|
||||
1. 访问 https://cloud.dify.ai/
|
||||
2. 注册并获取API密钥
|
||||
3. 填入配置文件中
|
||||
4. 支持多种对话模式:workflows/run, chat-messages, completion-messages
|
||||
5. 平台设置的角色定义会失效,需要在Dify控制台设置
|
||||
注意:建议使用本地部署的Dify接口,国内部分区域访问公有云接口可能受限' WHERE `id` = 'LLM_DifyLLM';
|
||||
|
||||
-- Gemini配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://aistudio.google.com/apikey',
|
||||
`remark` = 'Gemini配置说明:
|
||||
1. 使用谷歌Gemini API服务
|
||||
2. 当前配置使用gemini-2.0-flash模型
|
||||
3. 需要网络连接
|
||||
4. 支持配置代理
|
||||
申请步骤:
|
||||
1. 访问 https://aistudio.google.com/apikey
|
||||
2. 创建API密钥
|
||||
3. 填入配置文件中
|
||||
注意:若在中国境内使用,请遵守《生成式人工智能服务管理暂行办法》' WHERE `id` = 'LLM_GeminiLLM';
|
||||
|
||||
-- Coze配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://www.coze.cn/open/oauth/pats',
|
||||
`remark` = 'Coze配置说明:
|
||||
1. 使用Coze平台服务
|
||||
2. 需要bot_id、user_id和个人令牌
|
||||
3. 需要网络连接
|
||||
申请步骤:
|
||||
1. 访问 https://www.coze.cn/open/oauth/pats
|
||||
2. 获取个人令牌
|
||||
3. 手动计算bot_id和user_id
|
||||
4. 填入配置文件中' WHERE `id` = 'LLM_CozeLLM';
|
||||
|
||||
-- LM Studio配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://lmstudio.ai/',
|
||||
`remark` = 'LM Studio配置说明:
|
||||
1. 使用本地部署的LM Studio服务
|
||||
2. 当前配置使用deepseek-r1-distill-llama-8b@q4_k_m模型
|
||||
3. 本地推理,无需网络连接
|
||||
4. 需要预先下载模型
|
||||
部署步骤:
|
||||
1. 安装LM Studio
|
||||
2. 从社区下载模型
|
||||
3. 确保服务运行在http://localhost:1234/v1' WHERE `id` = 'LLM_LMStudioLLM';
|
||||
|
||||
-- FastGPT配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://cloud.tryfastgpt.ai/account/apikey',
|
||||
`remark` = 'FastGPT配置说明:
|
||||
1. 使用FastGPT平台服务
|
||||
2. 需要网络连接
|
||||
3. 配置文件中的prompt无效,需要在FastGPT控制台设置
|
||||
4. 支持自定义变量
|
||||
申请步骤:
|
||||
1. 访问 https://cloud.tryfastgpt.ai/account/apikey
|
||||
2. 获取API密钥
|
||||
3. 填入配置文件中' WHERE `id` = 'LLM_FastgptLLM';
|
||||
|
||||
-- Xinference配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/xorbitsai/inference',
|
||||
`remark` = 'Xinference配置说明:
|
||||
1. 使用本地部署的Xinference服务
|
||||
2. 当前配置使用qwen2.5:72b-AWQ模型
|
||||
3. 本地推理,无需网络连接
|
||||
4. 需要预先启动对应模型
|
||||
部署步骤:
|
||||
1. 安装Xinference
|
||||
2. 启动服务并加载模型
|
||||
3. 确保服务运行在http://localhost:9997' WHERE `id` = 'LLM_XinferenceLLM';
|
||||
|
||||
-- Xinference小模型配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/xorbitsai/inference',
|
||||
`remark` = 'Xinference小模型配置说明:
|
||||
1. 使用本地部署的Xinference服务
|
||||
2. 当前配置使用qwen2.5:3b-AWQ模型
|
||||
3. 本地推理,无需网络连接
|
||||
4. 用于意图识别
|
||||
部署步骤:
|
||||
1. 安装Xinference
|
||||
2. 启动服务并加载模型
|
||||
3. 确保服务运行在http://localhost:9997' WHERE `id` = 'LLM_XinferenceSmallLLM';
|
||||
|
||||
-- 火山引擎边缘大模型网关LLM配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://console.volcengine.com/vei/aigateway/',
|
||||
`remark` = '火山引擎边缘大模型网关LLM配置说明:
|
||||
1. 使用火山引擎边缘大模型网关服务
|
||||
2. 需要网关访问密钥
|
||||
3. 需要网络连接
|
||||
4. 支持function_call功能
|
||||
申请步骤:
|
||||
1. 访问 https://console.volcengine.com/vei/aigateway/
|
||||
2. 创建网关访问密钥,搜索并勾选 Doubao-pro-32k-functioncall
|
||||
3. 如果需要使用语音合成,一并勾选 Doubao-语音合成
|
||||
4. 访问 https://console.volcengine.com/vei/aigateway/tokens-list 获取密钥
|
||||
5. 填入配置文件中' WHERE `id` = 'LLM_VolcesAiGatewayLLM';
|
||||
|
||||
-- 更新Memory模型配置说明
|
||||
-- 无记忆配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = '无记忆配置说明:
|
||||
1. 不保存对话历史
|
||||
2. 每次对话都是独立的
|
||||
3. 无需额外配置
|
||||
4. 适合对隐私要求高的场景' WHERE `id` = 'Memory_nomem';
|
||||
|
||||
-- 本地短期记忆配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = '本地短期记忆配置说明:
|
||||
1. 使用本地存储保存对话历史
|
||||
2. 通过selected_module的llm总结对话内容
|
||||
3. 数据保存在本地,不会上传到服务器
|
||||
4. 适合注重隐私的场景
|
||||
5. 无需额外配置' WHERE `id` = 'Memory_mem_local_short';
|
||||
|
||||
-- Mem0AI记忆配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://app.mem0.ai/dashboard/api-keys',
|
||||
`remark` = 'Mem0AI记忆配置说明:
|
||||
1. 使用Mem0AI服务保存对话历史
|
||||
2. 需要API密钥
|
||||
3. 需要网络连接
|
||||
4. 每月有1000次免费调用
|
||||
申请步骤:
|
||||
1. 访问 https://app.mem0.ai/dashboard/api-keys
|
||||
2. 获取API密钥
|
||||
3. 填入配置文件中' WHERE `id` = 'Memory_mem0ai';
|
||||
|
||||
-- 更新Intent模型配置说明
|
||||
-- 无意图识别配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = '无意图识别配置说明:
|
||||
1. 不进行意图识别
|
||||
2. 所有对话直接传递给LLM处理
|
||||
3. 无需额外配置
|
||||
4. 适合简单对话场景' WHERE `id` = 'Intent_nointent';
|
||||
|
||||
-- LLM意图识别配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = 'LLM意图识别配置说明:
|
||||
1. 使用独立的LLM进行意图识别
|
||||
2. 默认使用selected_module.LLM的模型
|
||||
3. 可以配置使用独立的LLM(如免费的ChatGLMLLM)
|
||||
4. 通用性强,但会增加处理时间
|
||||
5. 不支持控制音量大小等iot操作
|
||||
配置说明:
|
||||
1. 在llm字段中指定使用的LLM模型
|
||||
2. 如果不指定,则使用selected_module.LLM的模型' WHERE `id` = 'Intent_intent_llm';
|
||||
|
||||
-- 函数调用意图识别配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = NULL,
|
||||
`remark` = '函数调用意图识别配置说明:
|
||||
1. 使用LLM的function_call功能进行意图识别
|
||||
2. 需要所选择的LLM支持function_call
|
||||
3. 按需调用工具,处理速度快
|
||||
4. 支持所有iot指令
|
||||
5. 默认已加载以下功能:
|
||||
- handle_exit_intent(退出识别)
|
||||
- play_music(音乐播放)
|
||||
- change_role(角色切换)
|
||||
- get_weather(天气查询)
|
||||
- get_news(新闻查询)
|
||||
配置说明:
|
||||
1. 在functions字段中配置需要加载的功能模块
|
||||
2. 系统默认已加载基础功能,无需重复配置
|
||||
3. 可以添加自定义功能模块' WHERE `id` = 'Intent_function_call';
|
||||
|
||||
-- 更新VAD模型配置说明
|
||||
-- SileroVAD配置说明
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://github.com/snakers4/silero-vad',
|
||||
`remark` = 'SileroVAD配置说明:
|
||||
1. 使用SileroVAD模型进行语音活动检测
|
||||
2. 本地推理,无需网络连接
|
||||
3. 需要下载模型文件到models/snakers4_silero-vad目录
|
||||
4. 可配置参数:
|
||||
- threshold: 0.5(语音检测阈值)
|
||||
- min_silence_duration_ms: 700(最小静音持续时间,单位毫秒)
|
||||
5. 如果说话停顿比较长,可以适当增加min_silence_duration_ms的值' WHERE `id` = 'VAD_SileroVAD';
|
||||
@@ -0,0 +1,85 @@
|
||||
update `ai_model_provider` set `fields` =
|
||||
'[{"key": "api_url","label": "API地址","type": "string"},{"key": "voice","label": "音色","type": "string"},{"key": "output_dir","label": "输出目录","type": "string"},{"key": "authorization","label": "授权","type": "string"},{"key": "appid","label": "应用ID","type": "string"},{"key": "access_token","label": "访问令牌","type": "string"},{"key": "cluster","label": "集群","type": "string"},{"key": "speed_ratio","label": "语速","type": "number"},{"key": "volume_ratio","label": "音量","type": "number"},{"key": "pitch_ratio","label": "音高","type": "number"}]'
|
||||
where `id` = 'SYSTEM_TTS_doubao';
|
||||
|
||||
-- 添加阿里云ASR供应器
|
||||
delete from `ai_model_provider` where `id` = 'SYSTEM_ASR_AliyunASR';
|
||||
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||
('SYSTEM_ASR_AliyunASR', 'ASR', 'aliyun', '阿里云语音识别', '[{"key":"appkey","label":"应用AppKey","type":"string"},{"key":"token","label":"临时Token","type":"string"},{"key":"access_key_id","label":"AccessKey ID","type":"string"},{"key":"access_key_secret","label":"AccessKey Secret","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 5, 1, NOW(), 1, NOW());
|
||||
|
||||
-- 添加阿里云ASR模型配置
|
||||
delete from `ai_model_config` where `id` = 'ASR_AliyunASR';
|
||||
INSERT INTO `ai_model_config` VALUES ('ASR_AliyunASR', 'ASR', 'AliyunASR', '阿里云语音识别', 0, 1, '{\"type\": \"aliyun\", \"appkey\": \"\", \"token\": \"\", \"access_key_id\": \"\", \"access_key_secret\": \"\", \"output_dir\": \"tmp/\"}', NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 更新阿里云ASR模型配置的说明文档
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://nls-portal.console.aliyun.com/',
|
||||
`remark` = '阿里云ASR配置说明:
|
||||
1. 访问 https://nls-portal.console.aliyun.com/ 开通服务
|
||||
2. 访问 https://nls-portal.console.aliyun.com/applist 获取appkey
|
||||
3. 访问 https://nls-portal.console.aliyun.com/overview 获取token
|
||||
4. 获取access_key_id和access_key_secret
|
||||
5. 填入配置文件中' WHERE `id` = 'ASR_AliyunASR';
|
||||
|
||||
-- 插入固件类型字典类型
|
||||
delete from `sys_dict_type` where `id` = 101;
|
||||
INSERT INTO `sys_dict_type` (`id`, `dict_type`, `dict_name`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||
(101, 'FIRMWARE_TYPE', '固件类型', '固件类型字典', 0, 1, NOW(), 1, NOW());
|
||||
|
||||
-- 插入固件类型字典数据
|
||||
delete from `sys_dict_data` where `dict_type_id` = 101;
|
||||
INSERT INTO `sys_dict_data` (`id`, `dict_type_id`, `dict_label`, `dict_value`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||
(101001, 101, '面包板新版接线(WiFi)', 'bread-compact-wifi', '面包板新版接线(WiFi)', 1, 1, NOW(), 1, NOW()),
|
||||
(101002, 101, '面包板新版接线(WiFi)+ LCD', 'bread-compact-wifi-lcd', '面包板新版接线(WiFi)+ LCD', 2, 1, NOW(), 1, NOW()),
|
||||
(101003, 101, '面包板新版接线(ML307 AT)', 'bread-compact-ml307', '面包板新版接线(ML307 AT)', 3, 1, NOW(), 1, NOW()),
|
||||
(101004, 101, '面包板(WiFi) ESP32 DevKit', 'bread-compact-esp32', '面包板(WiFi) ESP32 DevKit', 4, 1, NOW(), 1, NOW()),
|
||||
(101005, 101, '面包板(WiFi+ LCD) ESP32 DevKit', 'bread-compact-esp32-lcd', '面包板(WiFi+ LCD) ESP32 DevKit', 5, 1, NOW(), 1, NOW()),
|
||||
(101006, 101, 'DFRobot 行空板 k10', 'df-k10', 'DFRobot 行空板 k10', 6, 1, NOW(), 1, NOW()),
|
||||
(101007, 101, 'ESP32 CGC', 'esp32-cgc', 'ESP32 CGC', 7, 1, NOW(), 1, NOW()),
|
||||
(101008, 101, 'ESP BOX 3', 'esp-box-3', 'ESP BOX 3', 8, 1, NOW(), 1, NOW()),
|
||||
(101009, 101, 'ESP BOX', 'esp-box', 'ESP BOX', 9, 1, NOW(), 1, NOW()),
|
||||
(101010, 101, 'ESP BOX Lite', 'esp-box-lite', 'ESP BOX Lite', 10, 1, NOW(), 1, NOW()),
|
||||
(101011, 101, 'Kevin Box 1', 'kevin-box-1', 'Kevin Box 1', 11, 1, NOW(), 1, NOW()),
|
||||
(101012, 101, 'Kevin Box 2', 'kevin-box-2', 'Kevin Box 2', 12, 1, NOW(), 1, NOW()),
|
||||
(101013, 101, 'Kevin C3', 'kevin-c3', 'Kevin C3', 13, 1, NOW(), 1, NOW()),
|
||||
(101014, 101, 'Kevin SP V3开发板', 'kevin-sp-v3-dev', 'Kevin SP V3开发板', 14, 1, NOW(), 1, NOW()),
|
||||
(101015, 101, 'Kevin SP V4开发板', 'kevin-sp-v4-dev', 'Kevin SP V4开发板', 15, 1, NOW(), 1, NOW()),
|
||||
(101016, 101, '鱼鹰科技3.13LCD开发板', 'kevin-yuying-313lcd', '鱼鹰科技3.13LCD开发板', 16, 1, NOW(), 1, NOW()),
|
||||
(101017, 101, '立创·实战派ESP32-S3开发板', 'lichuang-dev', '立创·实战派ESP32-S3开发板', 17, 1, NOW(), 1, NOW()),
|
||||
(101018, 101, '立创·实战派ESP32-C3开发板', 'lichuang-c3-dev', '立创·实战派ESP32-C3开发板', 18, 1, NOW(), 1, NOW()),
|
||||
(101019, 101, '神奇按钮 Magiclick_2.4', 'magiclick-2p4', '神奇按钮 Magiclick_2.4', 19, 1, NOW(), 1, NOW()),
|
||||
(101020, 101, '神奇按钮 Magiclick_2.5', 'magiclick-2p5', '神奇按钮 Magiclick_2.5', 20, 1, NOW(), 1, NOW()),
|
||||
(101021, 101, '神奇按钮 Magiclick_C3', 'magiclick-c3', '神奇按钮 Magiclick_C3', 21, 1, NOW(), 1, NOW()),
|
||||
(101022, 101, '神奇按钮 Magiclick_C3_v2', 'magiclick-c3-v2', '神奇按钮 Magiclick_C3_v2', 22, 1, NOW(), 1, NOW()),
|
||||
(101023, 101, 'M5Stack CoreS3', 'm5stack-core-s3', 'M5Stack CoreS3', 23, 1, NOW(), 1, NOW()),
|
||||
(101024, 101, 'AtomS3 + Echo Base', 'atoms3-echo-base', 'AtomS3 + Echo Base', 24, 1, NOW(), 1, NOW()),
|
||||
(101025, 101, 'AtomS3R + Echo Base', 'atoms3r-echo-base', 'AtomS3R + Echo Base', 25, 1, NOW(), 1, NOW()),
|
||||
(101026, 101, 'AtomS3R CAM/M12 + Echo Base', 'atoms3r-cam-m12-echo-base', 'AtomS3R CAM/M12 + Echo Base', 26, 1, NOW(), 1, NOW()),
|
||||
(101027, 101, 'AtomMatrix + Echo Base', 'atommatrix-echo-base', 'AtomMatrix + Echo Base', 27, 1, NOW(), 1, NOW()),
|
||||
(101028, 101, '虾哥 Mini C3', 'xmini-c3', '虾哥 Mini C3', 28, 1, NOW(), 1, NOW()),
|
||||
(101029, 101, 'ESP32S3_KORVO2_V3开发板', 'esp32s3-korvo2-v3', 'ESP32S3_KORVO2_V3开发板', 29, 1, NOW(), 1, NOW()),
|
||||
(101030, 101, 'ESP-SparkBot开发板', 'esp-sparkbot', 'ESP-SparkBot开发板', 30, 1, NOW(), 1, NOW()),
|
||||
(101031, 101, 'ESP-Spot-S3', 'esp-spot-s3', 'ESP-Spot-S3', 31, 1, NOW(), 1, NOW()),
|
||||
(101032, 101, 'Waveshare ESP32-S3-Touch-AMOLED-1.8', 'esp32-s3-touch-amoled-1.8', 'Waveshare ESP32-S3-Touch-AMOLED-1.8', 32, 1, NOW(), 1, NOW()),
|
||||
(101033, 101, 'Waveshare ESP32-S3-Touch-LCD-1.85C', 'esp32-s3-touch-lcd-1.85c', 'Waveshare ESP32-S3-Touch-LCD-1.85C', 33, 1, NOW(), 1, NOW()),
|
||||
(101034, 101, 'Waveshare ESP32-S3-Touch-LCD-1.85', 'esp32-s3-touch-lcd-1.85', 'Waveshare ESP32-S3-Touch-LCD-1.85', 34, 1, NOW(), 1, NOW()),
|
||||
(101035, 101, 'Waveshare ESP32-S3-Touch-LCD-1.46', 'esp32-s3-touch-lcd-1.46', 'Waveshare ESP32-S3-Touch-LCD-1.46', 35, 1, NOW(), 1, NOW()),
|
||||
(101036, 101, 'Waveshare ESP32-S3-Touch-LCD-3.5', 'esp32-s3-touch-lcd-3.5', 'Waveshare ESP32-S3-Touch-LCD-3.5', 36, 1, NOW(), 1, NOW()),
|
||||
(101037, 101, '土豆子', 'tudouzi', '土豆子', 37, 1, NOW(), 1, NOW()),
|
||||
(101038, 101, 'LILYGO T-Circle-S3', 'lilygo-t-circle-s3', 'LILYGO T-Circle-S3', 38, 1, NOW(), 1, NOW()),
|
||||
(101039, 101, 'LILYGO T-CameraPlus-S3', 'lilygo-t-cameraplus-s3', 'LILYGO T-CameraPlus-S3', 39, 1, NOW(), 1, NOW()),
|
||||
(101040, 101, 'Movecall Moji 小智AI衍生版', 'movecall-moji-esp32s3', 'Movecall Moji 小智AI衍生版', 40, 1, NOW(), 1, NOW()),
|
||||
(101041, 101, 'Movecall CuiCan 璀璨·AI吊坠', 'movecall-cuican-esp32s3', 'Movecall CuiCan 璀璨·AI吊坠', 41, 1, NOW(), 1, NOW()),
|
||||
(101042, 101, '正点原子DNESP32S3开发板', 'atk-dnesp32s3', '正点原子DNESP32S3开发板', 42, 1, NOW(), 1, NOW()),
|
||||
(101043, 101, '正点原子DNESP32S3-BOX', 'atk-dnesp32s3-box', '正点原子DNESP32S3-BOX', 43, 1, NOW(), 1, NOW()),
|
||||
(101044, 101, '嘟嘟开发板CHATX(wifi)', 'du-chatx', '嘟嘟开发板CHATX(wifi)', 44, 1, NOW(), 1, NOW()),
|
||||
(101045, 101, '太极小派esp32s3', 'taiji-pi-s3', '太极小派esp32s3', 45, 1, NOW(), 1, NOW()),
|
||||
(101046, 101, '无名科技星智0.85(WIFI)', 'xingzhi-cube-0.85tft-wifi', '无名科技星智0.85(WIFI)', 46, 1, NOW(), 1, NOW()),
|
||||
(101047, 101, '无名科技星智0.85(ML307)', 'xingzhi-cube-0.85tft-ml307', '无名科技星智0.85(ML307)', 47, 1, NOW(), 1, NOW()),
|
||||
(101048, 101, '无名科技星智0.96(WIFI)', 'xingzhi-cube-0.96oled-wifi', '无名科技星智0.96(WIFI)', 48, 1, NOW(), 1, NOW()),
|
||||
(101049, 101, '无名科技星智0.96(ML307)', 'xingzhi-cube-0.96oled-ml307', '无名科技星智0.96(ML307)', 49, 1, NOW(), 1, NOW()),
|
||||
(101050, 101, '无名科技星智1.54(WIFI)', 'xingzhi-cube-1.54tft-wifi', '无名科技星智1.54(WIFI)', 50, 1, NOW(), 1, NOW()),
|
||||
(101051, 101, '无名科技星智1.54(ML307)', 'xingzhi-cube-1.54tft-ml307', '无名科技星智1.54(ML307)', 51, 1, NOW(), 1, NOW()),
|
||||
(101052, 101, 'SenseCAP Watcher', 'sensecap-watcher', 'SenseCAP Watcher', 52, 1, NOW(), 1, NOW()),
|
||||
(101053, 101, '四博智联AI陪伴盒子', 'doit-s3-aibox', '四博智联AI陪伴盒子', 53, 1, NOW(), 1, NOW()),
|
||||
(101054, 101, '元控·青春', 'mixgo-nova', '元控·青春', 54, 1, NOW(), 1, NOW());
|
||||
@@ -0,0 +1,27 @@
|
||||
-- 初始化智能体聊天记录
|
||||
DROP TABLE IF EXISTS ai_chat_history;
|
||||
DROP TABLE IF EXISTS ai_chat_message;
|
||||
DROP TABLE IF EXISTS ai_agent_chat_history;
|
||||
CREATE TABLE ai_agent_chat_history
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT COMMENT '主键ID' PRIMARY KEY,
|
||||
mac_address VARCHAR(50) COMMENT 'MAC地址',
|
||||
agent_id VARCHAR(32) COMMENT '智能体id',
|
||||
session_id VARCHAR(50) COMMENT '会话ID',
|
||||
chat_type TINYINT(3) COMMENT '消息类型: 1-用户, 2-智能体',
|
||||
content VARCHAR(1024) COMMENT '聊天内容',
|
||||
audio_id VARCHAR(32) COMMENT '音频ID',
|
||||
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL COMMENT '创建时间',
|
||||
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
INDEX idx_ai_agent_chat_history_mac (mac_address),
|
||||
INDEX idx_ai_agent_chat_history_session_id (session_id),
|
||||
INDEX idx_ai_agent_chat_history_agent_id (agent_id),
|
||||
INDEX idx_ai_agent_chat_history_agent_session_created (agent_id, session_id, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天记录表';
|
||||
|
||||
DROP TABLE IF EXISTS ai_agent_chat_audio;
|
||||
CREATE TABLE ai_agent_chat_audio
|
||||
(
|
||||
id VARCHAR(32) COMMENT '主键ID' PRIMARY KEY,
|
||||
audio LONGBLOB COMMENT '音频opus数据'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天音频数据表';
|
||||
@@ -78,4 +78,25 @@ databaseChangeLog:
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202504251422.sql
|
||||
path: classpath:db/changelog/202504251422.sql
|
||||
- changeSet:
|
||||
id: 202504291043
|
||||
author: jiangkunyin
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202504291043.sql
|
||||
- changeSet:
|
||||
id: 202504301341
|
||||
author: Goody
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202504301341.sql
|
||||
- changeSet:
|
||||
id: 202505022134
|
||||
author: Goody
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202505022134.sql
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="xiaozhi.modules.agent.dao.AiAgentChatHistoryDao">
|
||||
<resultMap id="BaseResultMap" type="xiaozhi.modules.agent.entity.AgentChatHistoryEntity">
|
||||
<!--@mbg.generated-->
|
||||
<!--@Table ai_agent_chat_history-->
|
||||
<id column="id" jdbcType="BIGINT" property="id" />
|
||||
<result column="mac_address" jdbcType="VARCHAR" property="macAddress" />
|
||||
<result column="agent_id" jdbcType="VARCHAR" property="agentId" />
|
||||
<result column="session_id" jdbcType="VARCHAR" property="sessionId" />
|
||||
<result column="sort" jdbcType="BIGINT" property="sort" />
|
||||
<result column="chat_type" jdbcType="TINYINT" property="chatType" />
|
||||
<result column="content" jdbcType="VARCHAR" property="content" />
|
||||
<result column="audio" jdbcType="LONGVARCHAR" property="audio" />
|
||||
<result column="audio_url" jdbcType="VARCHAR" property="audioUrl" />
|
||||
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||
</resultMap>
|
||||
<sql id="Base_Column_List">
|
||||
<!--@mbg.generated-->
|
||||
id, mac_address, agent_id, session_id, sort, chat_type, content, audio, audio_url,
|
||||
created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<delete id="deleteAudioByAgentId">
|
||||
DELETE FROM ai_agent_chat_audio
|
||||
WHERE id IN (
|
||||
SELECT audio_id
|
||||
FROM ai_agent_chat_history
|
||||
WHERE agent_id = #{agentId}
|
||||
);
|
||||
</delete>
|
||||
|
||||
<delete id="deleteHistoryByAgentId">
|
||||
DELETE FROM ai_agent_chat_history
|
||||
WHERE agent_id = #{agentId};
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -2,12 +2,17 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="xiaozhi.modules.sys.dao.SysDictDataDao">
|
||||
|
||||
<select id="getDictDataList" resultType="xiaozhi.modules.sys.entity.DictData">
|
||||
select dict_type_id, dict_label, dict_value from sys_dict_data order by dict_type_id, sort
|
||||
<select id="getDictDataByType" resultType="xiaozhi.modules.sys.vo.SysDictDataItem">
|
||||
SELECT d.dict_label AS `name`, d.dict_value AS `key`
|
||||
FROM sys_dict_data d
|
||||
LEFT JOIN sys_dict_type t ON d.dict_type_id = t.id
|
||||
WHERE t.dict_type = #{dictType}
|
||||
ORDER BY d.sort ASC
|
||||
</select>
|
||||
|
||||
<select id="getDataByTypeCode" resultType="xiaozhi.modules.sys.dto.SysDictDataDTO">
|
||||
select * from sys_dict_data WHERE DICT_TYPE_ID =(SELECT id FROM SYS_DICT_TYPE WHERE DICT_TYPE = #{dictType}) order by sort;
|
||||
<select id="getTypeByTypeId" resultType="java.lang.String">
|
||||
SELECT dict_type
|
||||
FROM sys_dict_type
|
||||
WHERE id = #{dictTypeId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -3,8 +3,4 @@
|
||||
|
||||
<mapper namespace="xiaozhi.modules.sys.dao.SysDictTypeDao">
|
||||
|
||||
<select id="getDictTypeList" resultType="xiaozhi.modules.sys.entity.DictType">
|
||||
select id, dict_type from sys_dict_type order by dict_type, sort
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user