mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-26 02:53:46 +08:00
feat: 移除数字人模块
This commit is contained in:
@@ -11,63 +11,12 @@
|
||||
Target Server Version : 80045 (8.0.45)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 24/02/2026 22:44:53
|
||||
Date: 27/02/2026 13:59:20
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for aihuman_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `aihuman_config`;
|
||||
CREATE TABLE `aihuman_config` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||
`agent_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int NULL DEFAULT NULL,
|
||||
`publish` int NULL DEFAULT NULL,
|
||||
`create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数字人配置信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of aihuman_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `aihuman_config` VALUES (9, '关爱老婆数字人(梅朵)', '梅朵吉祥物', '/Live2D/models/梅朵吉祥物/梅朵吉祥物.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"梅朵吉祥物.moc3\",\n \"Textures\": [\n \"梅朵吉祥物.4096/texture_00.png\",\n \"梅朵吉祥物.4096/texture_01.png\"\n ],\n \"Physics\": \"梅朵吉祥物.physics3.json\",\n \"DisplayInfo\": \"梅朵吉祥物.cdi3.json\",\n \"MotionSync\": \"梅朵吉祥物.motionsync3.json\",\n \"Expressions\": [\n {\n \"Name\": \"kaixin\",\n \"File\": \"kaixin.exp3.json\"\n },\n {\n \"Name\": \"maozi\",\n \"File\": \"maozi.exp3.json\"\n },\n {\n \"Name\": \"mouth open\",\n \"File\": \"mouth open.exp3.json\"\n },\n {\n \"Name\": \"shibai\",\n \"File\": \"shibai.exp3.json\"\n },\n {\n \"Name\": \"yinchen\",\n \"File\": \"yinchen.exp3.json\"\n }\n ],\n \"Motions\": {\n \"\": [\n {\n \"File\": \"mouth.motion3.json\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthForm\",\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '{\n \"bot_id\": \"7504596188201746470\",\n \"user_id\": \"7376476310010937396\",\n \"stream\": true,\n \"auto_save_history\": true\n}', '2025-09-29 16:36:46', '2025-09-29 16:36:46', 0, 1, NULL, NULL, '1', 0);
|
||||
INSERT INTO `aihuman_config` VALUES (10, '关爱老婆数字人(K)', 'kei_vowels_pro', '/Live2D/models/kei_vowels_pro/kei_vowels_pro.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"kei_vowels_pro.moc3\",\n \"Textures\": [\n \"kei_vowels_pro.2048/texture_00.png\"\n ],\n \"Physics\": \"kei_vowels_pro.physics3.json\",\n \"DisplayInfo\": \"kei_vowels_pro.cdi3.json\",\n \"MotionSync\": \"kei_vowels_pro.motionsync3.json\",\n \"Motions\": {\n \"\": [\n {\n \"File\": \"motions/01_kei_en.motion3.json\",\n \"Sound\": \"sounds/01_kei_en.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_jp.motion3.json\",\n \"Sound\": \"sounds/01_kei_jp.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_ko.motion3.json\",\n \"Sound\": \"sounds/01_kei_ko.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_zh.motion3.json\",\n \"Sound\": \"sounds/01_kei_zh.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": [\n {\n \"Id\": \"HitAreaHead\",\n \"Name\": \"Head\"\n }\n ]\n}', '3', '2025-09-29 16:35:27', '2025-09-29 16:35:27', 0, 1, NULL, NULL, '1', 0);
|
||||
INSERT INTO `aihuman_config` VALUES (11, '关爱老婆数字人(March 7th)', 'March 7th', '/Live2D/models/March 7th/March 7th.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"March 7th.moc3\",\n \"Textures\": [\n \"March 7th.4096/texture_00.png\",\n \"March 7th.4096/texture_01.png\"\n ],\n \"Physics\": \"March 7th.physics3.json\",\n \"DisplayInfo\": \"March 7th.cdi3.json\",\n \"Expressions\": [\n {\n \"Name\": \"捂脸\",\n \"File\": \"1.exp3.json\"\n },\n {\n \"Name\": \"比耶\",\n \"File\": \"2.exp3.json\"\n },\n {\n \"Name\": \"照相\",\n \"File\": \"3.exp3.json\"\n },\n {\n \"Name\": \"脸红\",\n \"File\": \"4.exp3.json\"\n },\n {\n \"Name\": \"黑脸\",\n \"File\": \"5.exp3.json\"\n },\n {\n \"Name\": \"哭\",\n \"File\": \"6.exp3.json\"\n },\n {\n \"Name\": \"流汗\",\n \"File\": \"7.exp3.json\"\n },\n {\n \"Name\": \"星星\",\n \"File\": \"8.exp3.json\"\n }\n ]\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '3', '2025-09-29 21:09:26', '2025-09-29 21:09:28', 0, 1, NULL, NULL, NULL, 0);
|
||||
INSERT INTO `aihuman_config` VALUES (12, '关爱老婆数字人(pachan)', 'pachan', '/Live2D/models/pachan/pachan.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"pachirisu anime girl - top half.moc3\",\n \"Textures\": [\n \"pachirisu anime girl - top half.4096/texture_00.png\"\n ],\n \"Physics\": \"pachirisu anime girl - top half.physics3.json\",\n \"DisplayInfo\": \"pachirisu anime girl - top half.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n }\n ]\n}', NULL, '2025-10-05 19:49:56', '2025-10-05 19:49:56', 0, 1, NULL, NULL, NULL, 0);
|
||||
INSERT INTO `aihuman_config` VALUES (13, '关爱老婆数字人(230108)', '230108', '/Live2D/models/230108/230108.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"230108.moc3\",\n \"Textures\": [\n \"230108.4096/texture_00.png\"\n ],\n \"Physics\": \"230108.physics3.json\",\n \"DisplayInfo\": \"230108.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ]\n}', NULL, '2025-10-06 19:28:20', '2025-10-06 19:28:23', 0, 1, NULL, NULL, NULL, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for aihuman_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `aihuman_info`;
|
||||
CREATE TABLE `aihuman_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '交互名称',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '交互内容',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'AI人类交互信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of aihuman_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `aihuman_info` VALUES (1, '1', '1', '2025-09-26 18:02:00', '2025-09-26 18:02:02', '0', 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_config
|
||||
-- ----------------------------
|
||||
@@ -154,8 +103,8 @@ CREATE TABLE `chat_model` (
|
||||
-- ----------------------------
|
||||
-- Records of chat_model
|
||||
-- ----------------------------
|
||||
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'openai', 'deepseek', 1, '1', 'Y', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-02-06 01:02:31', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'openai', 'bge-m3', 0, '1', 'N', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-02-06 01:02:35', 'bge-large-zh-v1.5', 0);
|
||||
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'ppio', 'deepseek', 1, '1', 'Y', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-02-25 21:46:08', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 0, '1', 'N', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-02-25 21:15:14', 'bge-large-zh-v1.5', 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_provider
|
||||
@@ -188,11 +137,11 @@ CREATE TABLE `chat_provider` (
|
||||
-- ----------------------------
|
||||
-- Records of chat_provider
|
||||
-- ----------------------------
|
||||
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/9d944a6abfcd46e2bd6e364f07202589.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-01-22 15:05:55', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/039ad13f690649f0ade139f8c803727b.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:58:22', '阿里云厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (3, '智谱AI', 'zhipu', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/a43e98fb7b3b4861b8caa6184e6fa40a.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 3, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:49:14', '智谱AI厂商', NULL, '1', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/2ff984bc9e4249df992733b31959056b.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2025-12-15 00:49:05', 'ollama厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/c4f8e304ce7740029b0024934d4625bc.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-01-02 00:54:45', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_session
|
||||
@@ -1485,7 +1434,7 @@ CREATE TABLE `mcp_market_info` (
|
||||
INDEX `idx_name`(`name` ASC) USING BTREE,
|
||||
INDEX `idx_status`(`status` ASC) USING BTREE,
|
||||
INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of mcp_market_info
|
||||
@@ -1509,7 +1458,7 @@ CREATE TABLE `mcp_market_tool` (
|
||||
INDEX `idx_market_id`(`market_id` ASC) USING BTREE,
|
||||
INDEX `idx_tool_name`(`tool_name` ASC) USING BTREE,
|
||||
INDEX `idx_is_loaded`(`is_loaded` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场工具关联表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场工具关联表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of mcp_market_tool
|
||||
@@ -1652,7 +1601,7 @@ CREATE TABLE `sj_job_executor` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_executor
|
||||
@@ -1678,7 +1627,7 @@ CREATE TABLE `sj_job_log_message` (
|
||||
INDEX `idx_task_batch_id_task_id`(`task_batch_id` ASC, `task_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_log_message
|
||||
@@ -1741,7 +1690,7 @@ CREATE TABLE `sj_job_task` (
|
||||
INDEX `idx_task_batch_id_task_status`(`task_batch_id` ASC, `task_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_task
|
||||
@@ -1773,7 +1722,7 @@ CREATE TABLE `sj_job_task_batch` (
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_workflow_task_batch_id_workflow_node_id`(`workflow_task_batch_id` ASC, `workflow_node_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_task_batch
|
||||
@@ -1823,7 +1772,7 @@ CREATE TABLE `sj_notify_config` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_notify_config
|
||||
@@ -1844,7 +1793,7 @@ CREATE TABLE `sj_notify_recipient` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id`(`namespace_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_notify_recipient
|
||||
@@ -1883,7 +1832,7 @@ CREATE TABLE `sj_retry` (
|
||||
INDEX `idx_retry_status_bucket_index`(`retry_status` ASC, `bucket_index` ASC) USING BTREE,
|
||||
INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry
|
||||
@@ -1912,7 +1861,7 @@ CREATE TABLE `sj_retry_dead_letter` (
|
||||
INDEX `idx_idempotent_id`(`idempotent_id` ASC) USING BTREE,
|
||||
INDEX `idx_biz_no`(`biz_no` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_dead_letter
|
||||
@@ -1947,7 +1896,7 @@ CREATE TABLE `sj_retry_scene_config` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC, `scene_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_scene_config
|
||||
@@ -2000,7 +1949,7 @@ CREATE TABLE `sj_retry_task` (
|
||||
INDEX `task_status`(`task_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_retry_id`(`retry_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_task
|
||||
@@ -2023,7 +1972,7 @@ CREATE TABLE `sj_retry_task_log_message` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name_retry_task_id`(`namespace_id` ASC, `group_name` ASC, `retry_task_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_task_log_message
|
||||
@@ -2050,7 +1999,7 @@ CREATE TABLE `sj_server_node` (
|
||||
UNIQUE INDEX `uk_host_id_host_ip`(`host_id` ASC, `host_ip` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_expire_at_node_type`(`expire_at` ASC, `node_type` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_server_node
|
||||
@@ -2089,7 +2038,7 @@ CREATE TABLE `sj_system_user_permission` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_namespace_id_group_name_system_user_id`(`namespace_id` ASC, `group_name` ASC, `system_user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_system_user_permission
|
||||
@@ -2124,7 +2073,7 @@ CREATE TABLE `sj_workflow` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow
|
||||
@@ -2155,7 +2104,7 @@ CREATE TABLE `sj_workflow_node` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow_node
|
||||
@@ -2184,7 +2133,7 @@ CREATE TABLE `sj_workflow_task_batch` (
|
||||
INDEX `idx_job_id_task_batch_status`(`workflow_id` ASC, `task_batch_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow_task_batch
|
||||
@@ -2387,6 +2336,11 @@ INSERT INTO `sys_dict_data` VALUES (2018858143757504522, '154726', 0, 'PC', 'pc'
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698817, '154726', 0, '安卓', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '安卓');
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698818, '154726', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', 'iOS');
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698819, '154726', 0, '小程序', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '小程序');
|
||||
INSERT INTO `sys_dict_data` VALUES (2026642472673288194, '000000', 0, '对话', 'chat', 'chat_model_category', NULL, 'cyan', 'N', 103, 1, '2026-02-25 20:56:33', 1, '2026-02-25 21:01:42', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026642525081116674, '000000', 1, '图像', 'image', 'chat_model_category', NULL, 'success', 'N', 103, 1, '2026-02-25 20:56:46', 1, '2026-02-25 21:01:37', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026643983713247233, '000000', 1, '次数计费', '1', 'sys_model_billing', NULL, 'green', 'N', 103, 1, '2026-02-25 21:02:34', 1, '2026-02-25 21:02:56', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026644058522853378, '000000', 2, 'token计费', '2', 'sys_model_billing', NULL, 'primary', 'N', 103, 1, '2026-02-25 21:02:51', 1, '2026-02-25 21:02:51', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2027261114955931650, '000000', 2, '向量', 'vector', 'chat_model_category', NULL, 'default', 'N', 103, 1, '2026-02-27 13:54:49', 1, '2026-02-27 13:54:54', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_dict_type
|
||||
@@ -2430,6 +2384,8 @@ INSERT INTO `sys_dict_type` VALUES (2018858143723950085, '154726', '操作类型
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950086, '154726', '系统状态', 'sys_common_status', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '登录状态列表');
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950087, '154726', '授权类型', 'sys_grant_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '认证授权类型');
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950088, '154726', '设备类型', 'sys_device_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '客户端设备类型');
|
||||
INSERT INTO `sys_dict_type` VALUES (2026642112982360066, '000000', '模型分类', 'chat_model_category', 103, 1, '2026-02-25 20:55:08', 1, '2026-02-25 20:55:08', '模型分类');
|
||||
INSERT INTO `sys_dict_type` VALUES (2026642183606050817, '000000', '计费方式', 'sys_model_billing', 103, 1, '2026-02-25 20:55:24', 1, '2026-02-25 20:55:24', '计费方式');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_logininfor
|
||||
@@ -2668,6 +2624,15 @@ INSERT INTO `sys_logininfor` VALUES (2019224998575153153, '000000', 'admin', 'pc
|
||||
INSERT INTO `sys_logininfor` VALUES (2019225059417726977, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 09:42:24');
|
||||
INSERT INTO `sys_logininfor` VALUES (2019240817392693249, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 10:45:01');
|
||||
INSERT INTO `sys_logininfor` VALUES (2019447979716972545, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-06 00:28:13');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026536636865163265, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 13:56:00');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026556949535502337, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 15:16:43');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026578433112911874, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 16:42:05');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026638437400518657, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 20:40:31');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026647463072952321, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:16:23');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026653919016968194, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '退出成功', '2026-02-25 21:42:02');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026654082020204546, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:42:41');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026654455514587138, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:44:10');
|
||||
INSERT INTO `sys_logininfor` VALUES (2027260957187186689, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-27 13:54:12');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_menu
|
||||
@@ -2703,7 +2668,7 @@ CREATE TABLE `sys_menu` (
|
||||
INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 3, 'system', '', '', 1, 0, 'M', '0', '0', '', 'eos-icons:system-group', 103, 1, '2025-12-14 16:11:49', 1, '2026-01-01 19:06:19', '系统管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 3, 'monitor', '', '', 1, 0, 'M', '0', '0', '', 'solar:monitor-camera-outline', 103, 1, '2025-12-14 16:11:49', 1, '2025-12-14 17:56:44', '系统监控目录');
|
||||
INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 4, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'ant-design:tool-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '系统工具目录');
|
||||
INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 2, 'tenant', NULL, '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '租户管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 8, 'tenant', '', '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', 1, '2026-02-25 20:42:14', '租户管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'ant-design:user-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '用户管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'eos-icons:role-binding-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '角色管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'ic:sharp-menu', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '菜单管理菜单');
|
||||
@@ -2807,8 +2772,8 @@ INSERT INTO `sys_menu` VALUES (1620, '配置列表', 118, 5, '#', '', '', 1, 0,
|
||||
INSERT INTO `sys_menu` VALUES (1621, '配置添加', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1622, '配置编辑', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1623, '配置删除', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000, 'MCP管理', 0, 5, 'mcp', '', '', 1, 0, 'M', '0', '0', '', 'mdi:robot-industrial', 103, 1, '2026-02-24 20:02:47', NULL, NULL, 'MCP模块管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2001, 'MCP工具管理', 2000, 1, 'tool', 'mcp/tool/index', '', 1, 0, 'C', '0', '0', 'mcp:tool:list', 'material-symbols:tools-hammer-outline', 103, 1, '2026-02-24 20:02:47', NULL, NULL, 'MCP工具管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2000, 'MCP管理', 0, 2, 'mcp', '', '', 1, 0, 'M', '0', '0', '', 'mdi:robot-industrial', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:54', 'MCP模块管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2001, 'MCP工具管理', 2000, 1, 'tool', 'mcp/tool/index', '', 1, 0, 'C', '0', '0', 'mcp:tool:list', 'octicon:tools-24', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:27', 'MCP工具管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2002, 'MCP工具查询', 2001, 1, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:query', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2003, 'MCP工具新增', 2001, 2, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:add', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2004, 'MCP工具修改', 2001, 3, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:edit', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
@@ -2847,13 +2812,6 @@ INSERT INTO `sys_menu` VALUES (11803, '流程达式定义新增', 11801, 2, '#',
|
||||
INSERT INTO `sys_menu` VALUES (11804, '流程达式定义修改', 11801, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11805, '流程达式定义删除', 11801, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11806, '流程达式定义导出', 11801, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597696, '数字人体验', 2019459914910994434, 10, 'aihumanPublish', 'aihuman/aihumanPublish/index', NULL, 1, 0, 'C', '0', '0', '', 'mdi:human-child', 103, 1, '2026-02-06 01:13:22', 1, '2026-02-06 01:29:34', '数字人信息管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597697, '数字人信息管理查询', 1971546066781597696, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:query', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597698, '数字人信息管理新增', 1971546066781597696, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:add', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597699, '数字人信息管理修改', 1971546066781597696, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:edit', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597700, '数字人信息管理删除', 1971546066781597696, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:remove', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597701, '数字人信息管理导出', 1971546066781597696, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:export', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1980480880138051584, '数字人配置', 2019459914910994434, 1, 'aihumanConfig', 'aihuman/aihumanConfig/index', NULL, 1, 0, 'C', '0', '0', 'aihuman:aihumanConfig:list', 'mdi:human-child', 103, 1, '2026-02-06 01:13:35', 1, '2026-02-06 01:28:12', '');
|
||||
INSERT INTO `sys_menu` VALUES (2000209300188356609, '对话管理', 0, 0, 'chat', '', NULL, 1, 0, 'M', '0', '0', NULL, 'material-symbols:chat-outline', 103, 1, '2025-12-14 22:20:34', 1, '2025-12-14 22:21:24', '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913451892738, '厂商管理', 2000209300188356609, 1, 'provider', 'chat/provider/index', NULL, 1, 0, 'C', '0', '0', 'system:provider:list', 'tabler:cube-spark', 103, 1, '2025-12-14 22:28:05', 1, '2025-12-14 23:42:55', '厂商管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913451892739, '厂商管理查询', 2000210913451892738, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:provider:query', '#', 103, 1, '2025-12-14 22:28:05', NULL, NULL, '');
|
||||
@@ -2886,7 +2844,6 @@ INSERT INTO `sys_menu` VALUES (2006681261898813444, '知识库修改', 200668126
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813445, '知识库删除', 2006681261898813441, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:remove', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813446, '知识库导出', 2006681261898813441, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:export', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006683336984580098, '知识管理', 0, 2, 'knowledge', '', NULL, 1, 0, 'M', '0', '0', NULL, 'bx:book', 103, 1, '2026-01-01 19:06:05', 1, '2026-01-01 19:06:05', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019459914910994434, '数字人管理', 0, 2, 'human', '', NULL, 1, 0, 'M', '0', '0', NULL, 'tdesign:user', 103, 1, '2026-02-06 01:15:38', 1, '2026-02-06 01:16:58', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464280262905857, '图谱实例', 2019464531388469250, 1, 'graphInstance', 'graph/graphInstance/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:list', 'ant-design:node-index-outlined', 103, 1, '2026-02-06 01:32:59', 1, '2026-02-06 01:40:06', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464531388469250, '知识图谱', 2006683336984580098, 15, 'graph', '', NULL, 1, 0, 'M', '0', '0', NULL, 'carbon:chart-relationship', 103, 1, '2026-02-06 01:33:59', 1, '2026-02-06 01:33:59', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464779217309697, '图谱可视化', 2019464531388469250, 2, 'graphVisualization', 'graph/graphVisualization/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:view', 'carbon:chart-network', 103, 1, '2026-02-06 01:34:58', 1, '2026-02-06 01:40:14', '');
|
||||
@@ -2975,6 +2932,12 @@ CREATE TABLE `sys_oss` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_oss
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_oss` VALUES (2026580908423340033, '000000', '2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', '{\"fileSize\":\"183613\",\"contentType\":\"image/png\"}', 103, '2026-02-25 16:51:55', 1, '2026-02-25 16:51:55', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640059920883713, '000000', '2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'openai.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', '{\"fileSize\":\"11297\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:46:58', 1, '2026-02-25 20:46:58', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640515967557633, '000000', '2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', '{\"fileSize\":\"8746\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:47', 1, '2026-02-25 20:48:47', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640548213366785, '000000', '2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:55', 1, '2026-02-25 20:48:55', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640572443860993, '000000', '2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:00', 1, '2026-02-25 20:49:00', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640621945036802, '000000', '2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', 'bailian-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '{\"fileSize\":\"5901\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:12', 1, '2026-02-25 20:49:12', 1, 'qcloud');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_oss_config
|
||||
@@ -3007,10 +2970,10 @@ CREATE TABLE `sys_oss_config` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_oss_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 15:44:13', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '', 'N', 'ap-beijing', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'ruoyiai-1254149996', '', 'cos.ap-guangzhou.myqcloud.com', '', 'Y', 'ap-guangzhou', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 16:51:41', '');
|
||||
INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
@@ -3457,7 +3420,7 @@ CREATE TABLE `sys_user` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_user
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-02-06 00:28:13', 103, 1, '2026-02-05 09:22:12', -1, '2026-02-06 00:28:13', '管理员', NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-02-27 13:54:12', 103, 1, '2026-02-05 09:22:12', -1, '2026-02-27 13:54:12', '管理员', NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (3, '000000', 108, 'test', '本部门及以下 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 3, '2026-02-05 09:22:12', NULL, NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (4, '000000', 102, 'test1', '仅本人 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 4, '2026-02-05 09:22:12', NULL, NULL, 0.00);
|
||||
|
||||
|
||||
7
pom.xml
7
pom.xml
@@ -390,13 +390,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数字人模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AI流程编排模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
|
||||
@@ -104,12 +104,6 @@
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数字人模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AI流程编排模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-aihuman</module>
|
||||
<module>ruoyi-aiflow</module>
|
||||
<module>ruoyi-chat</module>
|
||||
<module>ruoyi-demo</module>
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
|
||||
<description>
|
||||
aihuman模块
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<easyexcel.version>3.2.1</easyexcel.version>
|
||||
<jna.version>5.13.0</jna.version>
|
||||
<java-websocket.version>1.5.5</java-websocket.version>
|
||||
</properties>
|
||||
|
||||
<!-- 按照用户要求,不添加任何依赖 -->
|
||||
<dependencies>
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-idempotent</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--velocity代码生成使用模板 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba</groupId>-->
|
||||
<!-- <artifactId>easyexcel</artifactId>-->
|
||||
<!-- <version>${easyexcel.version}</version>-->
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>org.apache.poi</groupId>-->
|
||||
<!-- <artifactId>poi-ooxml-schemas</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>${java-websocket.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.ruoyi.aihuman.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 映射/voice/**路径到classpath:/voice/目录
|
||||
registry.addResourceHandler("/voice/**")
|
||||
.addResourceLocations("classpath:/voice/")
|
||||
.setCachePeriod(3600);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.aihuman.service.AihumanConfigService;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 交互数字人配置
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
|
||||
//临时免登录
|
||||
@SaIgnore
|
||||
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/aihumanConfig")
|
||||
public class AihumanConfigController extends BaseController {
|
||||
|
||||
private final AihumanConfigService aihumanConfigService;
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AihumanConfigVo> list(AihumanConfigBo bo, PageQuery pageQuery) {
|
||||
return aihumanConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:export")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(AihumanConfigBo bo, HttpServletResponse response) {
|
||||
List<AihumanConfigVo> list = aihumanConfigService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "交互数字人配置", AihumanConfigVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取交互数字人配置详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AihumanConfigVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Integer id) {
|
||||
return R.ok(aihumanConfigService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:add")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody AihumanConfigBo bo) {
|
||||
return toAjax(aihumanConfigService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:edit")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody AihumanConfigBo bo) {
|
||||
return toAjax(aihumanConfigService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除交互数字人配置
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:remove")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Integer[] ids) {
|
||||
return toAjax(aihumanConfigService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已发布的交互数字人配置列表
|
||||
* 只返回 publish = 1 的数据
|
||||
*/
|
||||
@GetMapping("/publishedList")
|
||||
public TableDataInfo<AihumanConfigVo> publishedList(PageQuery pageQuery) {
|
||||
// 创建查询条件对象并设置publish=1
|
||||
AihumanConfigBo bo = new AihumanConfigBo();
|
||||
bo.setPublish(1);
|
||||
// 调用现有的查询方法,传入预设了publish=1条件的bo对象
|
||||
return aihumanConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.aihuman.service.IAihumanInfoService;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Controller
|
||||
* 免登录接口,方便验证
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@SaIgnore
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/info")
|
||||
public class AihumanInfoController {
|
||||
|
||||
private final IAihumanInfoService aihumanInfoService;
|
||||
|
||||
/**
|
||||
* 获取AI人类交互信息详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R<AihumanInfoVo> getInfo(@PathVariable Long id) {
|
||||
return R.ok(aihumanInfoService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<TableDataInfo<AihumanInfoVo>> list(AihumanInfo aihumanInfo, PageQuery pageQuery) {
|
||||
TableDataInfo<AihumanInfoVo> tableDataInfo = aihumanInfoService.queryPageList(aihumanInfo, pageQuery);
|
||||
return R.ok(tableDataInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
@PostMapping
|
||||
public R<Integer> add(@Validated @RequestBody AihumanInfo aihumanInfo) {
|
||||
return R.ok(aihumanInfoService.insert(aihumanInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
@PutMapping
|
||||
public R<Integer> edit(@Validated @RequestBody AihumanInfo aihumanInfo) {
|
||||
return R.ok(aihumanInfoService.update(aihumanInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除AI人类交互信息
|
||||
*/
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Integer> remove(@PathVariable Long[] ids) {
|
||||
return R.ok(aihumanInfoService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口
|
||||
* 提供一个简单的GET接口用于快速验证控制器是否正常工作
|
||||
*/
|
||||
@GetMapping("/test")
|
||||
public R<String> test() {
|
||||
return R.ok("AI Human Controller is working!");
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo;
|
||||
import org.ruoyi.aihuman.service.AihumanRealConfigService;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
//临时免登录
|
||||
@SaIgnore
|
||||
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/aihumanRealConfig")
|
||||
public class AihumanRealConfigController extends BaseController {
|
||||
|
||||
private final AihumanRealConfigService aihumanRealConfigService;
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AihumanRealConfigVo> list(AihumanRealConfigBo bo, PageQuery pageQuery) {
|
||||
return aihumanRealConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出真人交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:export")
|
||||
@Log(title = "真人交互数字人配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(AihumanRealConfigBo bo, HttpServletResponse response) {
|
||||
List<AihumanRealConfigVo> list = aihumanRealConfigService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "真人交互数字人配置", AihumanRealConfigVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取真人交互数字人配置详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AihumanRealConfigVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Integer id) {
|
||||
return R.ok(aihumanRealConfigService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增真人交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:add")
|
||||
@Log(title = "真人交互数字人配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody AihumanRealConfigBo bo) {
|
||||
return toAjax(aihumanRealConfigService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改真人交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:edit")
|
||||
@Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) {
|
||||
return toAjax(aihumanRealConfigService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除真人交互数字人配置
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:remove")
|
||||
@Log(title = "真人交互数字人配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Integer[] ids) {
|
||||
return toAjax(aihumanRealConfigService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 1.执行以下命令:
|
||||
* cd F:\Projects\AI-Human\LiveTalking
|
||||
* conda activate D:\zg117\C\Users\zg117\.conda\envs\livetalking_new
|
||||
* python app.py --transport webrtc --model wav2lip --avatar_id wav2lip256_avatar1
|
||||
* <p>
|
||||
* 2.监听 python app.py --transport webrtc --model wav2lip --avatar_id wav2lip256_avatar1 执行情况
|
||||
* <p>
|
||||
* 3.返回执行结果并打开页面
|
||||
* http://127.0.0.1:8010/webrtcapi-diy.html
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:run")
|
||||
//@Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE, operatorType = OperatorType.OTHER)
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/run")
|
||||
public R<String> run(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) {
|
||||
boolean result = aihumanRealConfigService.runByBo(bo);
|
||||
if (result) {
|
||||
// 返回前端页面URL,前端可以根据这个URL跳转或打开新页面
|
||||
// http://127.0.0.1:8010/webrtcapi-diy.html 其中的 http://127.0.0.1 获取当前java服务的IP地址
|
||||
// return R.ok("http://127.0.0.1:8010/webrtcapi-diy.html");
|
||||
// 运行状态
|
||||
bo.setRunStatus("1");
|
||||
return R.ok("http://127.0.0.1:8010/webrtcapi-diy.html");
|
||||
} else {
|
||||
return R.fail("启动真人交互数字人失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止真人交互数字人配置任务
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanRealConfig:stop")
|
||||
//@Log(title = "真人交互数字人配置", businessType = BusinessType.UPDATE, operatorType = OperatorType.OTHER)
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/stop")
|
||||
public R<String> stop(@Validated(EditGroup.class) @RequestBody AihumanRealConfigBo bo) {
|
||||
boolean result = aihumanRealConfigService.stopByBo(bo);
|
||||
if (result) {
|
||||
// 运行状态
|
||||
bo.setRunStatus("0");
|
||||
return R.ok("真人交互数字人任务已停止");
|
||||
} else {
|
||||
return R.fail("停止真人交互数字人任务失败或没有正在运行的任务");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.aihuman.domain.VoiceRequest;
|
||||
import org.ruoyi.aihuman.protocol.EventType;
|
||||
import org.ruoyi.aihuman.protocol.Message;
|
||||
import org.ruoyi.aihuman.protocol.MsgType;
|
||||
import org.ruoyi.aihuman.protocol.SpeechWebSocketClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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 java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 火山引擎相关接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
// 临时免登录
|
||||
@SaIgnore
|
||||
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/volcengine")
|
||||
public class AihumanVolcengineController {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final Logger logger = LoggerFactory.getLogger(AihumanVolcengineController.class);
|
||||
@Autowired
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@PostMapping("/generate-voice-direct")
|
||||
public ResponseEntity<byte[]> generateVoiceDirect(@RequestBody VoiceRequest request) {
|
||||
try {
|
||||
// 生成唯一的语音ID
|
||||
String voiceId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
log.info("开始生成语音,voiceId: {}", voiceId);
|
||||
|
||||
// 调用火山引擎TTS API获取音频数据
|
||||
byte[] audioData = generateVoiceData(request, voiceId);
|
||||
|
||||
// 设置响应头,返回音频数据
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType("audio/wav"));
|
||||
headers.setContentDispositionFormData("attachment", "voice_" + System.currentTimeMillis() + ".wav");
|
||||
headers.setContentLength(audioData.length);
|
||||
|
||||
log.info("语音生成成功并返回,长度: {} bytes", audioData.length);
|
||||
return new ResponseEntity<>(audioData, headers, HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
log.error("生成语音失败", e);
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] generateVoiceData(VoiceRequest request, String voiceId) {
|
||||
try {
|
||||
// 这里是调用火山引擎TTS API的核心逻辑
|
||||
// 您需要根据火山引擎的API文档实现具体的调用逻辑
|
||||
// 注意:这只是一个示例框架,您需要根据实际情况进行实现
|
||||
|
||||
// 调用火山引擎API并获取音频数据
|
||||
// 假设您已经有现有的调用逻辑,这里保留原有的实现
|
||||
String endpoint = request.getEndpoint();
|
||||
String appId = request.getAppId();
|
||||
String accessToken = request.getAccessToken();
|
||||
String resourceId = request.getResourceId();
|
||||
String voice = request.getVoice();
|
||||
String text = request.getText();
|
||||
String encoding = request.getEncoding();
|
||||
|
||||
// 调用原有的火山引擎API调用方法(如果有)
|
||||
// 或者直接在这里实现API调用逻辑
|
||||
byte[] audioData = callVolcengineTtsApiByte(endpoint, appId, accessToken,
|
||||
resourceId, voice, text, encoding);
|
||||
|
||||
log.info("成功生成语音数据,大小: {} bytes", audioData.length);
|
||||
return audioData;
|
||||
} catch (Exception e) {
|
||||
log.error("生成语音数据失败", e);
|
||||
throw new RuntimeException("生成语音数据失败", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private byte[] callVolcengineTtsApiByte(String endpoint, String appId, String accessToken,
|
||||
String resourceId, String voice, String text, String encoding) {
|
||||
try {
|
||||
// 确保resourceId不为空,如果为空则根据voice类型获取默认值
|
||||
if (resourceId == null || resourceId.isEmpty()) {
|
||||
resourceId = voiceToResourceId(voice);
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Api-App-Key", appId);
|
||||
headers.put("X-Api-Access-Key", accessToken);
|
||||
headers.put("X-Api-Resource-Id", resourceId);
|
||||
headers.put("X-Api-Connect-Id", UUID.randomUUID().toString());
|
||||
|
||||
// 创建WebSocket客户端
|
||||
SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(endpoint), headers);
|
||||
ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
|
||||
boolean audioReceived = false;
|
||||
|
||||
try {
|
||||
// 连接WebSocket
|
||||
client.connectBlocking();
|
||||
|
||||
// 构建请求参数
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("user", Map.of("uid", UUID.randomUUID().toString()));
|
||||
request.put("namespace", "BidirectionalTTS");
|
||||
|
||||
Map<String, Object> reqParams = new HashMap<>();
|
||||
reqParams.put("speaker", voice);
|
||||
|
||||
Map<String, Object> audioParams = new HashMap<>();
|
||||
audioParams.put("format", encoding);
|
||||
audioParams.put("sample_rate", 24000);
|
||||
audioParams.put("enable_timestamp", true);
|
||||
|
||||
reqParams.put("audio_params", audioParams);
|
||||
reqParams.put("additions", objectMapper.writeValueAsString(Map.of("disable_markdown_filter", false)));
|
||||
|
||||
request.put("req_params", reqParams);
|
||||
|
||||
// 开始连接
|
||||
client.sendStartConnection();
|
||||
// 等待连接成功
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED);
|
||||
|
||||
// 处理每个句子
|
||||
String[] sentences = text.split("。");
|
||||
for (int i = 0; i < sentences.length; i++) {
|
||||
if (sentences[i].trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
ByteArrayOutputStream sentenceAudioStream = new ByteArrayOutputStream();
|
||||
|
||||
// 开始会话
|
||||
Map<String, Object> startReq = new HashMap<>();
|
||||
startReq.put("user", request.get("user"));
|
||||
startReq.put("namespace", request.get("namespace"));
|
||||
startReq.put("req_params", request.get("req_params"));
|
||||
startReq.put("event", EventType.START_SESSION.getValue());
|
||||
client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId);
|
||||
// 等待会话开始
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED);
|
||||
|
||||
// 发送文本内容
|
||||
for (char c : sentences[i].toCharArray()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> currentReqParams = new HashMap<>((Map<String, Object>) request.get("req_params"));
|
||||
currentReqParams.put("text", String.valueOf(c));
|
||||
|
||||
Map<String, Object> currentRequest = new HashMap<>();
|
||||
currentRequest.put("user", request.get("user"));
|
||||
currentRequest.put("namespace", request.get("namespace"));
|
||||
currentRequest.put("req_params", currentReqParams);
|
||||
currentRequest.put("event", EventType.TASK_REQUEST.getValue());
|
||||
|
||||
client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId);
|
||||
}
|
||||
|
||||
// 结束会话
|
||||
client.sendFinishSession(sessionId);
|
||||
|
||||
// 接收响应
|
||||
while (true) {
|
||||
Message msg = client.receiveMessage();
|
||||
switch (msg.getType()) {
|
||||
case FULL_SERVER_RESPONSE:
|
||||
break;
|
||||
case AUDIO_ONLY_SERVER:
|
||||
if (!audioReceived && sentenceAudioStream.size() > 0) {
|
||||
audioReceived = true;
|
||||
}
|
||||
if (msg.getPayload() != null) {
|
||||
sentenceAudioStream.write(msg.getPayload());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// 不抛出异常,记录日志并继续处理
|
||||
log.warn("Unexpected message type: {}", msg.getType());
|
||||
}
|
||||
if (msg.getEvent() == EventType.SESSION_FINISHED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 将当前句子的音频追加到总音频流
|
||||
if (sentenceAudioStream.size() > 0) {
|
||||
totalAudioStream.write(sentenceAudioStream.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
// 验证是否收到音频数据
|
||||
if (totalAudioStream.size() > 0) {
|
||||
log.info("Audio data generated successfully, size: {} bytes", totalAudioStream.size());
|
||||
return totalAudioStream.toByteArray();
|
||||
} else {
|
||||
throw new RuntimeException("No audio data received");
|
||||
}
|
||||
} finally {
|
||||
// 结束连接
|
||||
client.sendFinishConnection();
|
||||
client.closeBlocking();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error calling Volcengine TTS API: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to generate voice", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成语音文件接口
|
||||
* 用户传入JSON参数,返回音频文件的播放地址
|
||||
*/
|
||||
@PostMapping("/generate-voice")
|
||||
public ResponseEntity<?> generateVoice(@RequestBody VoiceRequest request) {
|
||||
try {
|
||||
// 1. 解析请求参数
|
||||
String endpoint = request.getEndpoint();
|
||||
String appId = request.getAppId();
|
||||
String accessToken = request.getAccessToken();
|
||||
String resourceId = request.getResourceId();
|
||||
String voice = request.getVoice();
|
||||
String text = request.getText();
|
||||
String encoding = request.getEncoding();
|
||||
|
||||
// 1.1 验证必要参数
|
||||
if (endpoint == null || endpoint.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "endpoint cannot be null or empty"));
|
||||
}
|
||||
if (appId == null || appId.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "appId cannot be null or empty"));
|
||||
}
|
||||
if (accessToken == null || accessToken.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "accessToken cannot be null or empty"));
|
||||
}
|
||||
if (text == null || text.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "text cannot be null or empty"));
|
||||
}
|
||||
|
||||
// 1.2 设置默认值
|
||||
if (encoding == null || encoding.isEmpty()) {
|
||||
encoding = "mp3";
|
||||
}
|
||||
|
||||
// 2. 调用火山引擎API生成音频文件
|
||||
String audioUrl = callVolcengineTtsApi(endpoint, appId, accessToken, resourceId, voice, text, encoding);
|
||||
|
||||
// 3. 构造并返回响应
|
||||
Map<String, String> response = new HashMap<>();
|
||||
response.put("audioUrl", audioUrl);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
// 处理异常情况
|
||||
Map<String, String> errorResponse = new HashMap<>();
|
||||
errorResponse.put("error", "生成音频文件失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用火山引擎TTS API生成音频文件
|
||||
*/
|
||||
private String callVolcengineTtsApi(String endpoint, String appId, String accessToken,
|
||||
String resourceId, String voice, String text, String encoding) {
|
||||
try {
|
||||
// 确保resourceId不为空,如果为空则根据voice类型获取默认值
|
||||
if (resourceId == null || resourceId.isEmpty()) {
|
||||
resourceId = voiceToResourceId(voice);
|
||||
}
|
||||
|
||||
// 生成唯一的文件名
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||
String randomId = UUID.randomUUID().toString().substring(0, 8);
|
||||
String fileName = "voice_" + timestamp + "_" + randomId + "." + encoding;
|
||||
|
||||
// 获取resources/voice目录路径
|
||||
String voiceDirPath = getVoiceDirectoryPath();
|
||||
File voiceDir = new File(voiceDirPath);
|
||||
if (!voiceDir.exists()) {
|
||||
voiceDir.mkdirs();
|
||||
}
|
||||
|
||||
String filePath = voiceDirPath + File.separator + fileName;
|
||||
|
||||
// 设置请求头
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Api-App-Key", appId);
|
||||
headers.put("X-Api-Access-Key", accessToken);
|
||||
headers.put("X-Api-Resource-Id", resourceId);
|
||||
headers.put("X-Api-Connect-Id", UUID.randomUUID().toString());
|
||||
|
||||
// 创建WebSocket客户端
|
||||
SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(endpoint), headers);
|
||||
ByteArrayOutputStream totalAudioStream = new ByteArrayOutputStream();
|
||||
boolean audioReceived = false;
|
||||
|
||||
try {
|
||||
// 连接WebSocket
|
||||
client.connectBlocking();
|
||||
|
||||
// 构建请求参数
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("user", Map.of("uid", UUID.randomUUID().toString()));
|
||||
request.put("namespace", "BidirectionalTTS");
|
||||
|
||||
Map<String, Object> reqParams = new HashMap<>();
|
||||
reqParams.put("speaker", voice);
|
||||
|
||||
Map<String, Object> audioParams = new HashMap<>();
|
||||
audioParams.put("format", encoding);
|
||||
audioParams.put("sample_rate", 24000);
|
||||
audioParams.put("enable_timestamp", true);
|
||||
|
||||
reqParams.put("audio_params", audioParams);
|
||||
reqParams.put("additions", objectMapper.writeValueAsString(Map.of("disable_markdown_filter", false)));
|
||||
|
||||
request.put("req_params", reqParams);
|
||||
|
||||
// 开始连接
|
||||
client.sendStartConnection();
|
||||
// 等待连接成功
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED);
|
||||
|
||||
// 处理每个句子
|
||||
String[] sentences = text.split("。");
|
||||
for (int i = 0; i < sentences.length; i++) {
|
||||
if (sentences[i].trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
ByteArrayOutputStream sentenceAudioStream = new ByteArrayOutputStream();
|
||||
|
||||
// 开始会话
|
||||
Map<String, Object> startReq = new HashMap<>();
|
||||
startReq.put("user", request.get("user"));
|
||||
startReq.put("namespace", request.get("namespace"));
|
||||
startReq.put("req_params", request.get("req_params"));
|
||||
startReq.put("event", EventType.START_SESSION.getValue());
|
||||
client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId);
|
||||
// 等待会话开始
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED);
|
||||
|
||||
// 发送文本内容
|
||||
for (char c : sentences[i].toCharArray()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> currentReqParams = new HashMap<>((Map<String, Object>) request.get("req_params"));
|
||||
currentReqParams.put("text", String.valueOf(c));
|
||||
|
||||
Map<String, Object> currentRequest = new HashMap<>();
|
||||
currentRequest.put("user", request.get("user"));
|
||||
currentRequest.put("namespace", request.get("namespace"));
|
||||
currentRequest.put("req_params", currentReqParams);
|
||||
currentRequest.put("event", EventType.TASK_REQUEST.getValue());
|
||||
|
||||
client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId);
|
||||
}
|
||||
|
||||
// 结束会话
|
||||
client.sendFinishSession(sessionId);
|
||||
|
||||
// 接收响应
|
||||
while (true) {
|
||||
Message msg = client.receiveMessage();
|
||||
switch (msg.getType()) {
|
||||
case FULL_SERVER_RESPONSE:
|
||||
break;
|
||||
case AUDIO_ONLY_SERVER:
|
||||
if (!audioReceived && sentenceAudioStream.size() > 0) {
|
||||
audioReceived = true;
|
||||
}
|
||||
if (msg.getPayload() != null) {
|
||||
sentenceAudioStream.write(msg.getPayload());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// 不抛出异常,记录日志并继续处理
|
||||
log.warn("Unexpected message type: {}", msg.getType());
|
||||
}
|
||||
if (msg.getEvent() == EventType.SESSION_FINISHED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 将当前句子的音频追加到总音频流
|
||||
if (sentenceAudioStream.size() > 0) {
|
||||
totalAudioStream.write(sentenceAudioStream.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
// 保存音频文件
|
||||
if (totalAudioStream.size() > 0) {
|
||||
Files.write(Paths.get(filePath), totalAudioStream.toByteArray(), StandardOpenOption.CREATE);
|
||||
log.info("Audio saved to file: {}", filePath);
|
||||
} else {
|
||||
throw new RuntimeException("No audio data received");
|
||||
}
|
||||
|
||||
// 结束连接
|
||||
client.sendFinishConnection();
|
||||
} finally {
|
||||
client.closeBlocking();
|
||||
}
|
||||
|
||||
// 返回音频文件的访问路径
|
||||
return "/voice/" + fileName;
|
||||
} catch (Exception e) {
|
||||
log.error("Error calling Volcengine TTS API: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to generate voice", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据voice类型获取resourceId
|
||||
*/
|
||||
private String voiceToResourceId(String voice) {
|
||||
if (voice != null && voice.startsWith("S_")) {
|
||||
return "volc.megatts.default";
|
||||
}
|
||||
return "volc.service_type.10029";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取voice目录路径
|
||||
*/
|
||||
private String getVoiceDirectoryPath() {
|
||||
try {
|
||||
// 获取当前项目根目录
|
||||
String projectRoot = System.getProperty("user.dir");
|
||||
|
||||
// 构建目标目录路径:ruoyi-ai/ruoyi-modules/ruoyi-aihuman/src/main/resources/voice
|
||||
File targetDir = new File(projectRoot, "ruoyi-modules/ruoyi-aihuman/src/main/resources/voice");
|
||||
|
||||
// 确保目录存在
|
||||
if (!targetDir.exists()) {
|
||||
boolean created = targetDir.mkdirs();
|
||||
if (created) {
|
||||
logger.info("成功创建目录: {}", targetDir.getAbsolutePath());
|
||||
} else {
|
||||
logger.warn("无法创建目录: {}", targetDir.getAbsolutePath());
|
||||
|
||||
// 降级方案:直接使用项目根目录下的voice文件夹
|
||||
File fallbackDir = new File(projectRoot, "voice");
|
||||
if (!fallbackDir.exists()) {
|
||||
fallbackDir.mkdirs();
|
||||
}
|
||||
return fallbackDir.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
return targetDir.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
logger.error("获取音频目录路径失败", e);
|
||||
|
||||
// 异常情况下的安全降级
|
||||
File safeDir = new File("voice");
|
||||
if (!safeDir.exists()) {
|
||||
safeDir.mkdirs();
|
||||
}
|
||||
return safeDir.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 交互数字人配置对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@TableName("aihuman_config")
|
||||
public class AihumanConfig implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
private String modelParams;
|
||||
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
private String agentParams;
|
||||
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
private Integer publish;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI人类交互信息实体类
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Data
|
||||
@TableName("aihuman_info")
|
||||
public class AihumanInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 交互名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 交互内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置对象 aihuman_real_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@TableName("aihuman_real_config")
|
||||
public class AihumanRealConfig implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* 主键id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 场景名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 真人形象名称
|
||||
*/
|
||||
private String avatars;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String models;
|
||||
|
||||
/**
|
||||
* 形象参数(预留)
|
||||
*/
|
||||
private String avatarsParams;
|
||||
|
||||
/**
|
||||
* 模型参数(预留)
|
||||
*/
|
||||
private String modelsParams;
|
||||
|
||||
/**
|
||||
* 智能体参数(扣子)
|
||||
*/
|
||||
private String agentParams;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
private Integer publish;
|
||||
|
||||
/**
|
||||
* 运行参数
|
||||
*/
|
||||
private String runParams;
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
private String runStatus;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private String createDept;
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 语音请求参数实体类
|
||||
*/
|
||||
@Data
|
||||
public class VoiceRequest {
|
||||
|
||||
@JsonProperty("ENDPOINT")
|
||||
private String endpoint;
|
||||
private String appId;
|
||||
private String accessToken;
|
||||
private String resourceId;
|
||||
private String voice;
|
||||
private String text;
|
||||
private String encoding;
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 交互数字人配置业务对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
|
||||
@AutoMapper(target = AihumanConfig.class, reverseConvertGenerate = false)
|
||||
public class AihumanConfigBo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
private String modelName;
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
private String modelPath;
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
private String modelParams;
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
private String agentParams;
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
@NotNull(message = "status不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Integer status;
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
@NotNull(message = "publish不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Integer publish;
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数字人信息管理业务对象 aihuman_info
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 20:03:06 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
|
||||
@AutoMapper(target = AihumanInfo.class, reverseConvertGenerate = false)
|
||||
public class AihumanInfoBo implements Serializable {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 交互名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 交互内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanRealConfig;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置业务对象 aihuman_real_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
|
||||
@AutoMapper(target = AihumanRealConfig.class, reverseConvertGenerate = false)
|
||||
public class AihumanRealConfigBo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 场景名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 真人形象名称
|
||||
*/
|
||||
private String avatars;
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String models;
|
||||
/**
|
||||
* 形象参数(预留)
|
||||
*/
|
||||
private String avatarsParams;
|
||||
/**
|
||||
* 模型参数(预留)
|
||||
*/
|
||||
private String modelsParams;
|
||||
/**
|
||||
* 智能体参数(扣子)
|
||||
*/
|
||||
private String agentParams;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
private Integer publish;
|
||||
|
||||
/**
|
||||
* 运行参数
|
||||
*/
|
||||
private String runParams;
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
private String runStatus;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private String createDept;
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
private String createBy;
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 交互数字人配置视图对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = AihumanConfig.class)
|
||||
public class AihumanConfigVo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
@ExcelProperty(value = "name")
|
||||
private String name;
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
@ExcelProperty(value = "modelName")
|
||||
private String modelName;
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
@ExcelProperty(value = "modelPath")
|
||||
private String modelPath;
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
@ExcelProperty(value = "modelParams")
|
||||
private String modelParams;
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
@ExcelProperty(value = "agentParams")
|
||||
private String agentParams;
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
@ExcelProperty(value = "createTime")
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
@ExcelProperty(value = "updateTime")
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
@ExcelProperty(value = "status", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_common_status")
|
||||
private Integer status;
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
@ExcelProperty(value = "publish", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_common_status")
|
||||
private Integer publish;
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI人类交互信息视图对象
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = AihumanInfo.class)
|
||||
public class AihumanInfoVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 交互名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 交互内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.ruoyi.aihuman.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanRealConfig;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置视图对象 aihuman_real_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = AihumanRealConfig.class)
|
||||
public class AihumanRealConfigVo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
/**
|
||||
* 场景名称
|
||||
*/
|
||||
@ExcelProperty(value = "场景名称")
|
||||
private String name;
|
||||
/**
|
||||
* 真人形象名称
|
||||
*/
|
||||
@ExcelProperty(value = "真人形象名称")
|
||||
private String avatars;
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@ExcelProperty(value = "模型名称")
|
||||
private String models;
|
||||
/**
|
||||
* 形象参数(预留)
|
||||
*/
|
||||
@ExcelProperty(value = "形象参数", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "$column.readConverterExp()")
|
||||
private String avatarsParams;
|
||||
/**
|
||||
* 模型参数(预留)
|
||||
*/
|
||||
@ExcelProperty(value = "模型参数", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "$column.readConverterExp()")
|
||||
private String modelsParams;
|
||||
/**
|
||||
* 智能体参数(扣子)
|
||||
*/
|
||||
@ExcelProperty(value = "智能体参数", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "$column.readConverterExp()")
|
||||
private String agentParams;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ExcelProperty(value = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ExcelProperty(value = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@ExcelProperty(value = "状态")
|
||||
private Integer status;
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
@ExcelProperty(value = "发布状态")
|
||||
private Integer publish;
|
||||
|
||||
/**
|
||||
* 运行参数
|
||||
*/
|
||||
@ExcelProperty(value = "运行参数")
|
||||
private String runParams;
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
@ExcelProperty(value = "运行状态")
|
||||
private String runStatus;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
@ExcelProperty(value = "创建部门")
|
||||
private String createDept;
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
@ExcelProperty(value = "创建用户")
|
||||
private String createBy;
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
@ExcelProperty(value = "更新用户")
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.ruoyi.aihuman.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Mapper接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Mapper
|
||||
public interface AihumanConfigMapper extends BaseMapperPlus<AihumanConfig, AihumanConfigVo> {
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.ruoyi.aihuman.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Mapper接口
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Mapper
|
||||
public interface AihumanInfoMapper extends BaseMapperPlus<AihumanInfo, AihumanInfoVo> {
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.ruoyi.aihuman.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.ruoyi.aihuman.domain.AihumanRealConfig;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo;
|
||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置Mapper接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
@Mapper
|
||||
public interface AihumanRealConfigMapper extends BaseMapperPlus<AihumanRealConfig, AihumanRealConfigVo> {
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum CompressionBits {
|
||||
None_((byte) 0),
|
||||
Gzip((byte) 0b1),
|
||||
Custom((byte) 0b11),
|
||||
;
|
||||
|
||||
private final byte value;
|
||||
|
||||
CompressionBits(byte b) {
|
||||
this.value = b;
|
||||
}
|
||||
|
||||
public static CompressionBits fromValue(int value) {
|
||||
for (CompressionBits type : CompressionBits.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown CompressionBits value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum EventType {
|
||||
// Default event
|
||||
NONE(0),
|
||||
|
||||
// Upstream Connection events (1-49)
|
||||
START_CONNECTION(1),
|
||||
START_TASK(1),
|
||||
FINISH_CONNECTION(2),
|
||||
FINISH_TASK(2),
|
||||
|
||||
// Downstream Connection events (50-99)
|
||||
CONNECTION_STARTED(50),
|
||||
TASK_STARTED(50),
|
||||
CONNECTION_FAILED(51),
|
||||
TASK_FAILED(51),
|
||||
CONNECTION_FINISHED(52),
|
||||
TASK_FINISHED(52),
|
||||
|
||||
// Upstream Session events (100-149)
|
||||
START_SESSION(100),
|
||||
CANCEL_SESSION(101),
|
||||
FINISH_SESSION(102),
|
||||
|
||||
// Downstream Session events (150-199)
|
||||
SESSION_STARTED(150),
|
||||
SESSION_CANCELED(151),
|
||||
SESSION_FINISHED(152),
|
||||
SESSION_FAILED(153),
|
||||
USAGE_RESPONSE(154),
|
||||
CHARGE_DATA(154),
|
||||
|
||||
// Upstream General events (200-249)
|
||||
TASK_REQUEST(200),
|
||||
UPDATE_CONFIG(201),
|
||||
|
||||
// Downstream General events (250-299)
|
||||
AUDIO_MUTED(250),
|
||||
|
||||
// Upstream TTS events (300-349)
|
||||
SAY_HELLO(300),
|
||||
|
||||
// Downstream TTS events (350-399)
|
||||
TTS_SENTENCE_START(350),
|
||||
TTS_SENTENCE_END(351),
|
||||
TTS_RESPONSE(352),
|
||||
TTS_ENDED(359),
|
||||
PODCAST_ROUND_START(360),
|
||||
PODCAST_ROUND_RESPONSE(361),
|
||||
PODCAST_ROUND_END(362),
|
||||
|
||||
// Downstream ASR events (450-499)
|
||||
ASR_INFO(450),
|
||||
ASR_RESPONSE(451),
|
||||
ASR_ENDED(459),
|
||||
|
||||
// Upstream Chat events (500-549)
|
||||
CHAT_TTS_TEXT(500),
|
||||
|
||||
// Downstream Chat events (550-599)
|
||||
CHAT_RESPONSE(550),
|
||||
CHAT_ENDED(559),
|
||||
|
||||
// Subtitle events (650-699)
|
||||
SOURCE_SUBTITLE_START(650),
|
||||
SOURCE_SUBTITLE_RESPONSE(651),
|
||||
SOURCE_SUBTITLE_END(652),
|
||||
TRANSLATION_SUBTITLE_START(653),
|
||||
TRANSLATION_SUBTITLE_RESPONSE(654),
|
||||
TRANSLATION_SUBTITLE_END(655);
|
||||
|
||||
private final int value;
|
||||
|
||||
EventType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static EventType fromValue(int value) {
|
||||
for (EventType type : EventType.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown EventType value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum HeaderSizeBits {
|
||||
HeaderSize4((byte) 1),
|
||||
HeaderSize8((byte) 2),
|
||||
HeaderSize12((byte) 3),
|
||||
HeaderSize16((byte) 4),
|
||||
;
|
||||
|
||||
private final byte value;
|
||||
|
||||
HeaderSizeBits(byte b) {
|
||||
this.value = b;
|
||||
}
|
||||
|
||||
public static HeaderSizeBits fromValue(int value) {
|
||||
for (HeaderSizeBits type : HeaderSizeBits.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown HeaderSizeBits value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
public class Message {
|
||||
private byte version = VersionBits.Version1.getValue();
|
||||
private byte headerSize = HeaderSizeBits.HeaderSize4.getValue();
|
||||
private MsgType type;
|
||||
private MsgTypeFlagBits flag;
|
||||
private byte serialization = SerializationBits.JSON.getValue();
|
||||
private byte compression = 0;
|
||||
|
||||
private EventType event;
|
||||
private String sessionId;
|
||||
private String connectId;
|
||||
private int sequence;
|
||||
private int errorCode;
|
||||
|
||||
private byte[] payload;
|
||||
|
||||
public Message(MsgType type, MsgTypeFlagBits flag) {
|
||||
this.type = type;
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public static Message unmarshal(byte[] data) throws Exception {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
|
||||
byte type_and_flag = data[1];
|
||||
MsgType type = MsgType.fromValue((type_and_flag >> 4) & 0x0F);
|
||||
MsgTypeFlagBits flag = MsgTypeFlagBits.fromValue(type_and_flag & 0x0F);
|
||||
|
||||
// Read version and header size
|
||||
int versionAndHeaderSize = buffer.get();
|
||||
VersionBits version = VersionBits.fromValue((versionAndHeaderSize >> 4) & 0x0F);
|
||||
HeaderSizeBits headerSize = HeaderSizeBits.fromValue(versionAndHeaderSize & 0x0F);
|
||||
|
||||
// Skip second byte
|
||||
buffer.get();
|
||||
|
||||
// Read serialization and compression method
|
||||
int serializationCompression = buffer.get();
|
||||
SerializationBits serialization = SerializationBits.fromValue((serializationCompression >> 4) & 0x0F);
|
||||
CompressionBits compression = CompressionBits.fromValue(serializationCompression & 0x0F);
|
||||
|
||||
// Skip padding bytes
|
||||
int headerSizeInt = 4 * (int) headerSize.getValue();
|
||||
int paddingSize = headerSizeInt - 3;
|
||||
while (paddingSize > 0) {
|
||||
buffer.get();
|
||||
paddingSize -= 1;
|
||||
}
|
||||
|
||||
Message message = new Message(type, flag);
|
||||
message.setVersion(version.getValue());
|
||||
message.setHeaderSize(headerSize.getValue());
|
||||
message.setSerialization(serialization.getValue());
|
||||
message.setCompression(compression.getValue());
|
||||
|
||||
// Read sequence if present
|
||||
if (flag == MsgTypeFlagBits.POSITIVE_SEQ || flag == MsgTypeFlagBits.NEGATIVE_SEQ) {
|
||||
// Read 4 bytes from ByteBuffer and parse as int (big-endian)
|
||||
byte[] sequeueBytes = new byte[4];
|
||||
if (buffer.remaining() >= 4) {
|
||||
buffer.get(sequeueBytes); // Read 4 bytes into array
|
||||
ByteBuffer wrapper = ByteBuffer.wrap(sequeueBytes);
|
||||
wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order
|
||||
message.setSequence(wrapper.getInt());
|
||||
}
|
||||
}
|
||||
|
||||
// Read event if present
|
||||
if (flag == MsgTypeFlagBits.WITH_EVENT) {
|
||||
// Read 4 bytes from ByteBuffer and parse as int (big-endian)
|
||||
byte[] eventBytes = new byte[4];
|
||||
if (buffer.remaining() >= 4) {
|
||||
buffer.get(eventBytes); // Read 4 bytes into array
|
||||
ByteBuffer wrapper = ByteBuffer.wrap(eventBytes);
|
||||
wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order
|
||||
message.setEvent(EventType.fromValue(wrapper.getInt()));
|
||||
}
|
||||
|
||||
if (type != MsgType.ERROR && !(message.event == EventType.START_CONNECTION
|
||||
|| message.event == EventType.FINISH_CONNECTION ||
|
||||
message.event == EventType.CONNECTION_STARTED
|
||||
|| message.event == EventType.CONNECTION_FAILED ||
|
||||
message.event == EventType.CONNECTION_FINISHED)) {
|
||||
// Read sessionId if present
|
||||
int sessionIdLength = buffer.getInt();
|
||||
if (sessionIdLength > 0) {
|
||||
byte[] sessionIdBytes = new byte[sessionIdLength];
|
||||
buffer.get(sessionIdBytes);
|
||||
message.setSessionId(new String(sessionIdBytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
if (message.event == EventType.CONNECTION_STARTED || message.event == EventType.CONNECTION_FAILED
|
||||
|| message.event == EventType.CONNECTION_FINISHED) {
|
||||
// Read connectId if present
|
||||
int connectIdLength = buffer.getInt();
|
||||
if (connectIdLength > 0) {
|
||||
byte[] connectIdBytes = new byte[connectIdLength];
|
||||
buffer.get(connectIdBytes);
|
||||
message.setConnectId(new String(connectIdBytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read errorCode if present
|
||||
if (type == MsgType.ERROR) {
|
||||
// Read 4 bytes from ByteBuffer and parse as int (big-endian)
|
||||
byte[] errorCodeBytes = new byte[4];
|
||||
if (buffer.remaining() >= 4) {
|
||||
buffer.get(errorCodeBytes); // Read 4 bytes into array
|
||||
ByteBuffer wrapper = ByteBuffer.wrap(errorCodeBytes);
|
||||
wrapper.order(ByteOrder.BIG_ENDIAN); // Set big-endian order
|
||||
message.setErrorCode(wrapper.getInt());
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining bytes as payload
|
||||
if (buffer.remaining() > 0) {
|
||||
// 4 bytes length
|
||||
int payloadLength = buffer.getInt();
|
||||
if (payloadLength > 0) {
|
||||
byte[] payloadBytes = new byte[payloadLength];
|
||||
buffer.get(payloadBytes);
|
||||
message.setPayload(payloadBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public byte[] marshal() throws Exception {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
// Write header
|
||||
buffer.write((version & 0x0F) << 4 | (headerSize & 0x0F));
|
||||
buffer.write((type.getValue() & 0x0F) << 4 | (flag.getValue() & 0x0F));
|
||||
buffer.write((serialization & 0x0F) << 4 | (compression & 0x0F));
|
||||
|
||||
int headerSizeInt = 4 * (int) headerSize;
|
||||
int padding = headerSizeInt - buffer.size();
|
||||
while (padding > 0) {
|
||||
buffer.write(0);
|
||||
padding -= 1;
|
||||
}
|
||||
|
||||
// Write event if present
|
||||
if (event != null) {
|
||||
byte[] eventBytes = ByteBuffer.allocate(4).putInt(event.getValue()).array();
|
||||
buffer.write(eventBytes);
|
||||
}
|
||||
|
||||
// Write sessionId if present
|
||||
if (sessionId != null) {
|
||||
byte[] sessionIdBytes = sessionId.getBytes(StandardCharsets.UTF_8);
|
||||
buffer.write(ByteBuffer.allocate(4).putInt(sessionIdBytes.length).array());
|
||||
buffer.write(sessionIdBytes);
|
||||
}
|
||||
|
||||
// Write connectId if present
|
||||
if (connectId != null) {
|
||||
byte[] connectIdBytes = connectId.getBytes(StandardCharsets.UTF_8);
|
||||
buffer.write(ByteBuffer.allocate(4).putInt(connectIdBytes.length).array());
|
||||
buffer.write(connectIdBytes);
|
||||
}
|
||||
|
||||
// Write sequence if present
|
||||
if (sequence != 0) {
|
||||
buffer.write(ByteBuffer.allocate(4).putInt(sequence).array());
|
||||
}
|
||||
|
||||
// Write errorCode if present
|
||||
if (errorCode != 0) {
|
||||
buffer.write(ByteBuffer.allocate(4).putInt(errorCode).array());
|
||||
}
|
||||
|
||||
// Write payload if present
|
||||
if (payload != null && payload.length > 0) {
|
||||
buffer.write(ByteBuffer.allocate(4).putInt(payload.length).array());
|
||||
buffer.write(payload);
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (this.type) {
|
||||
case AUDIO_ONLY_SERVER:
|
||||
case AUDIO_ONLY_CLIENT:
|
||||
if (this.flag == MsgTypeFlagBits.POSITIVE_SEQ || this.flag == MsgTypeFlagBits.NEGATIVE_SEQ) {
|
||||
return String.format("MsgType: %s, EventType: %s, Sequence: %d, PayloadSize: %d", this.type, this.event, this.sequence,
|
||||
this.payload != null ? this.payload.length : 0);
|
||||
}
|
||||
return String.format("MsgType: %s, EventType: %s, PayloadSize: %d", this.type, this.event,
|
||||
this.payload != null ? this.payload.length : 0);
|
||||
case ERROR:
|
||||
return String.format("MsgType: %s, EventType: %s, ErrorCode: %d, Payload: %s", this.type, this.event, this.errorCode,
|
||||
this.payload != null ? new String(this.payload) : "null");
|
||||
default:
|
||||
if (this.flag == MsgTypeFlagBits.POSITIVE_SEQ || this.flag == MsgTypeFlagBits.NEGATIVE_SEQ) {
|
||||
return String.format("MsgType: %s, EventType: %s, Sequence: %d, Payload: %s",
|
||||
this.type, this.event, this.sequence,
|
||||
this.payload != null ? new String(this.payload) : "null");
|
||||
}
|
||||
return String.format("MsgType: %s, EventType: %s, Payload: %s", this.type, this.event,
|
||||
this.payload != null ? new String(this.payload) : "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum MsgType {
|
||||
INVALID((byte) 0),
|
||||
FULL_CLIENT_REQUEST((byte) 0b1),
|
||||
AUDIO_ONLY_CLIENT((byte) 0b10),
|
||||
FULL_SERVER_RESPONSE((byte) 0b1001),
|
||||
AUDIO_ONLY_SERVER((byte) 0b1011),
|
||||
FRONT_END_RESULT_SERVER((byte) 0b1100),
|
||||
ERROR((byte) 0b1111);
|
||||
|
||||
private final byte value;
|
||||
|
||||
MsgType(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static MsgType fromValue(int value) {
|
||||
for (MsgType type : MsgType.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown MsgType value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum MsgTypeFlagBits {
|
||||
NO_SEQ((byte) 0), // Non-terminating packet without sequence number
|
||||
POSITIVE_SEQ((byte) 0b1), // Non-terminating packet with positive sequence number
|
||||
LAST_NO_SEQ((byte) 0b10), // Terminating packet without sequence number
|
||||
NEGATIVE_SEQ((byte) 0b11), // Terminating packet with negative sequence number
|
||||
WITH_EVENT((byte) 0b100); // Packet containing event number
|
||||
|
||||
private final byte value;
|
||||
|
||||
MsgTypeFlagBits(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static MsgTypeFlagBits fromValue(int value) {
|
||||
for (MsgTypeFlagBits flag : MsgTypeFlagBits.values()) {
|
||||
if (flag.value == value) {
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown MsgTypeFlagBits value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum SerializationBits {
|
||||
Raw((byte) 0),
|
||||
JSON((byte) 0b1),
|
||||
Thrift((byte) 0b11),
|
||||
Custom((byte) 0b1111),
|
||||
;
|
||||
|
||||
private final byte value;
|
||||
|
||||
SerializationBits(byte b) {
|
||||
this.value = b;
|
||||
}
|
||||
|
||||
public static SerializationBits fromValue(int value) {
|
||||
for (SerializationBits type : SerializationBits.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown SerializationBits value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@Slf4j
|
||||
public class SpeechWebSocketClient extends WebSocketClient {
|
||||
private final BlockingQueue<Message> messageQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
public SpeechWebSocketClient(URI serverUri, Map<String, String> headers) {
|
||||
super(serverUri, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
log.info("WebSocket connection established, Logid: {}", handshakedata.getFieldValue("x-tt-logid"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
log.warn("Received unexpected text message: {}", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer bytes) {
|
||||
try {
|
||||
Message message = Message.unmarshal(bytes.array());
|
||||
messageQueue.put(message);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
log.info("WebSocket connection closed: code={}, reason={}, remote={}", code, reason, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
log.error("WebSocket error", ex);
|
||||
}
|
||||
|
||||
public void sendStartConnection() throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT);
|
||||
message.setEvent(EventType.START_CONNECTION);
|
||||
message.setPayload("{}".getBytes());
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendFinishConnection() throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT);
|
||||
message.setEvent(EventType.FINISH_CONNECTION);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendStartSession(byte[] payload, String sessionId) throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT);
|
||||
message.setEvent(EventType.START_SESSION);
|
||||
message.setSessionId(sessionId);
|
||||
message.setPayload(payload);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendFinishSession(String sessionId) throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT);
|
||||
message.setEvent(EventType.FINISH_SESSION);
|
||||
message.setSessionId(sessionId);
|
||||
message.setPayload("{}".getBytes());
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendTaskRequest(byte[] payload, String sessionId) throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.WITH_EVENT);
|
||||
message.setEvent(EventType.TASK_REQUEST);
|
||||
message.setSessionId(sessionId);
|
||||
message.setPayload(payload);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendFullClientMessage(byte[] payload) throws Exception {
|
||||
Message message = new Message(MsgType.FULL_CLIENT_REQUEST, MsgTypeFlagBits.NO_SEQ);
|
||||
message.setPayload(payload);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendMessage(Message message) throws Exception {
|
||||
log.info("Send: {}", message);
|
||||
send(message.marshal());
|
||||
}
|
||||
|
||||
public Message receiveMessage() throws InterruptedException {
|
||||
Message message = messageQueue.take();
|
||||
log.info("Receive: {}", message);
|
||||
return message;
|
||||
}
|
||||
|
||||
public Message waitForMessage(MsgType type, EventType event) throws InterruptedException {
|
||||
while (true) {
|
||||
Message message = receiveMessage();
|
||||
if (message.getType() == type && message.getEvent() == event) {
|
||||
return message;
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected message: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.ruoyi.aihuman.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum VersionBits {
|
||||
Version1((byte) 1),
|
||||
Version2((byte) 2),
|
||||
Version3((byte) 3),
|
||||
Version4((byte) 4),
|
||||
;
|
||||
|
||||
private final byte value;
|
||||
|
||||
VersionBits(byte b) {
|
||||
this.value = b;
|
||||
}
|
||||
|
||||
public static VersionBits fromValue(int value) {
|
||||
for (VersionBits type : VersionBits.values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown VersionBits value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
public interface AihumanConfigService {
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置
|
||||
*/
|
||||
AihumanConfigVo queryById(Integer id);
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
TableDataInfo<AihumanConfigVo> queryPageList(AihumanConfigBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
List<AihumanConfigVo> queryList(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
Boolean insertByBo(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
Boolean updateByBo(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除交互数字人配置信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
public interface AihumanRealConfigService {
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置
|
||||
*/
|
||||
AihumanRealConfigVo queryById(Integer id);
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置列表
|
||||
*/
|
||||
TableDataInfo<AihumanRealConfigVo> queryPageList(AihumanRealConfigBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置列表
|
||||
*/
|
||||
List<AihumanRealConfigVo> queryList(AihumanRealConfigBo bo);
|
||||
|
||||
/**
|
||||
* 新增真人交互数字人配置
|
||||
*/
|
||||
Boolean insertByBo(AihumanRealConfigBo bo);
|
||||
|
||||
/**
|
||||
* 修改真人交互数字人配置
|
||||
*/
|
||||
Boolean updateByBo(AihumanRealConfigBo bo);
|
||||
|
||||
/**
|
||||
* 执行真人交互数字人配置
|
||||
*/
|
||||
Boolean runByBo(AihumanRealConfigBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除真人交互数字人配置信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid);
|
||||
|
||||
// 在AihumanRealConfigService接口中添加
|
||||
Boolean stopByBo(AihumanRealConfigBo bo);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
public interface AihumanVolcengineService {
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Service接口
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
public interface IAihumanInfoService {
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息
|
||||
*/
|
||||
AihumanInfoVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
TableDataInfo<AihumanInfoVo> queryPageList(AihumanInfo record, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
List<AihumanInfoVo> queryList(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
int insert(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
int update(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 批量删除AI人类交互信息
|
||||
*/
|
||||
int deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.aihuman.mapper.AihumanConfigMapper;
|
||||
import org.ruoyi.aihuman.service.AihumanConfigService;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Service业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AihumanConfigServiceImpl implements AihumanConfigService {
|
||||
|
||||
private final AihumanConfigMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public AihumanConfigVo queryById(Integer id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AihumanConfigVo> queryPageList(AihumanConfigBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = buildQueryWrapper(bo);
|
||||
Page<AihumanConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<AihumanConfigVo> queryList(AihumanConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<AihumanConfig> buildQueryWrapper(AihumanConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getName()), AihumanConfig::getName, bo.getName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelName()), AihumanConfig::getModelName, bo.getModelName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelPath()), AihumanConfig::getModelPath, bo.getModelPath());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelParams()), AihumanConfig::getModelParams, bo.getModelParams());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAgentParams()), AihumanConfig::getAgentParams, bo.getAgentParams());
|
||||
lqw.eq(bo.getCreateTime() != null, AihumanConfig::getCreateTime, bo.getCreateTime());
|
||||
lqw.eq(bo.getUpdateTime() != null, AihumanConfig::getUpdateTime, bo.getUpdateTime());
|
||||
lqw.eq(bo.getStatus() != null, AihumanConfig::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getPublish() != null, AihumanConfig::getPublish, bo.getPublish());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(AihumanConfigBo bo) {
|
||||
AihumanConfig add = MapstructUtils.convert(bo, AihumanConfig.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(AihumanConfigBo bo) {
|
||||
AihumanConfig update = MapstructUtils.convert(bo, AihumanConfig.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(AihumanConfig entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.aihuman.mapper.AihumanInfoMapper;
|
||||
import org.ruoyi.aihuman.service.IAihumanInfoService;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Service业务层处理
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AihumanInfoServiceImpl implements IAihumanInfoService {
|
||||
|
||||
private final AihumanInfoMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public AihumanInfoVo queryById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AihumanInfoVo> queryPageList(AihumanInfo record, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = buildQueryWrapper(record);
|
||||
Page<AihumanInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<AihumanInfoVo> queryList(AihumanInfo record) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = buildQueryWrapper(record);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private LambdaQueryWrapper<AihumanInfo> buildQueryWrapper(AihumanInfo record) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(record.getId() != null, AihumanInfo::getId, record.getId());
|
||||
lqw.like(StringUtils.isNotBlank(record.getName()), AihumanInfo::getName, record.getName());
|
||||
lqw.like(StringUtils.isNotBlank(record.getContent()), AihumanInfo::getContent, record.getContent());
|
||||
lqw.orderByDesc(AihumanInfo::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int insert(AihumanInfo record) {
|
||||
return baseMapper.insert(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int update(AihumanInfo record) {
|
||||
return baseMapper.updateById(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// 如果需要逻辑删除,MyBatis-Plus会自动处理
|
||||
// 这里的@TableLogic注解已经在实体类中配置
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids);
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.platform.win32.WinNT;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.aihuman.domain.AihumanRealConfig;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanRealConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanRealConfigVo;
|
||||
import org.ruoyi.aihuman.mapper.AihumanRealConfigMapper;
|
||||
import org.ruoyi.aihuman.service.AihumanRealConfigService;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 真人交互数字人配置Service业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Tue Oct 21 11:46:52 GMT+08:00 2025
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AihumanRealConfigServiceImpl implements AihumanRealConfigService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AihumanRealConfigServiceImpl.class);
|
||||
private final AihumanRealConfigMapper baseMapper;
|
||||
// 存储当前运行的进程,用于停止操作
|
||||
private volatile Process runningProcess = null;
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public AihumanRealConfigVo queryById(Integer id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AihumanRealConfigVo> queryPageList(AihumanRealConfigBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AihumanRealConfig> lqw = buildQueryWrapper(bo);
|
||||
Page<AihumanRealConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询真人交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<AihumanRealConfigVo> queryList(AihumanRealConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanRealConfig> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<AihumanRealConfig> buildQueryWrapper(AihumanRealConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanRealConfig> lqw = Wrappers.lambdaQuery();
|
||||
lqw.like(StringUtils.isNotBlank(bo.getName()), AihumanRealConfig::getName, bo.getName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getAvatars()), AihumanRealConfig::getAvatars, bo.getAvatars());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getModels()), AihumanRealConfig::getModels, bo.getModels());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAvatarsParams()), AihumanRealConfig::getAvatarsParams, bo.getAvatarsParams());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelsParams()), AihumanRealConfig::getModelsParams, bo.getModelsParams());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAgentParams()), AihumanRealConfig::getAgentParams, bo.getAgentParams());
|
||||
lqw.eq(bo.getCreateTime() != null, AihumanRealConfig::getCreateTime, bo.getCreateTime());
|
||||
lqw.eq(bo.getUpdateTime() != null, AihumanRealConfig::getUpdateTime, bo.getUpdateTime());
|
||||
lqw.eq(bo.getStatus() != null, AihumanRealConfig::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getPublish() != null, AihumanRealConfig::getPublish, bo.getPublish());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getRunParams()), AihumanRealConfig::getRunParams, bo.getRunParams());
|
||||
// 添加runStatus字段的查询条件
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getRunStatus()), AihumanRealConfig::getRunStatus, bo.getRunStatus());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getCreateDept()), AihumanRealConfig::getCreateDept, bo.getCreateDept());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getCreateBy()), AihumanRealConfig::getCreateBy, bo.getCreateBy());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), AihumanRealConfig::getUpdateBy, bo.getUpdateBy());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增真人交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(AihumanRealConfigBo bo) {
|
||||
AihumanRealConfig add = MapstructUtils.convert(bo, AihumanRealConfig.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改真人交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(AihumanRealConfigBo bo) {
|
||||
AihumanRealConfig update = MapstructUtils.convert(bo, AihumanRealConfig.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(AihumanRealConfig entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除真人交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行真人交互数字人配置
|
||||
* 通过主键获取数据库记录,然后从run_params字段读取命令并执行
|
||||
*/
|
||||
@Override
|
||||
public Boolean runByBo(AihumanRealConfigBo bo) {
|
||||
try {
|
||||
// 1. 通过主键获取数据库记录
|
||||
Integer id = bo.getId();
|
||||
if (id == null) {
|
||||
log.error("执行命令失败:主键ID为空");
|
||||
throw new RuntimeException("执行命令失败:主键ID为空");
|
||||
}
|
||||
|
||||
// 检查是否已经有对应的进程在运行
|
||||
String redisKey = "aihuman:process:" + id;
|
||||
String existingPid = RedisUtils.getCacheObject(redisKey);
|
||||
if (StringUtils.isNotEmpty(existingPid) && isProcessRunning(existingPid)) {
|
||||
log.warn("ID为{}的配置已有进程在运行,进程ID: {}", id, existingPid);
|
||||
// 刷新run_status状态为运行中
|
||||
AihumanRealConfig updateStatus = new AihumanRealConfig();
|
||||
updateStatus.setId(id);
|
||||
updateStatus.setRunStatus("1"); // 1表示运行中
|
||||
baseMapper.updateById(updateStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 查询数据库记录
|
||||
AihumanRealConfig config = baseMapper.selectById(id);
|
||||
if (config == null) {
|
||||
log.error("执行命令失败:未找到ID为{}的配置记录", id);
|
||||
throw new RuntimeException("执行命令失败:未找到对应的配置记录");
|
||||
}
|
||||
|
||||
// 2. 从记录中获取run_params字段
|
||||
String runParams = config.getRunParams();
|
||||
if (StringUtils.isBlank(runParams)) {
|
||||
log.error("执行命令失败:ID为{}的记录中run_params字段为空", id);
|
||||
throw new RuntimeException("执行命令失败:run_params字段为空");
|
||||
}
|
||||
|
||||
// 3. 解析并执行命令
|
||||
// 将多行命令合并为一个命令字符串
|
||||
String[] commands = runParams.split("\\r?\\n");
|
||||
if (commands.length == 0) {
|
||||
log.error("执行命令失败:runParams中没有有效的命令");
|
||||
throw new RuntimeException("执行命令失败:runParams中没有有效的命令");
|
||||
}
|
||||
|
||||
// 将所有命令合并到一个命令字符串中,使用&&连接,确保在同一个进程中执行
|
||||
StringBuilder mergedCmd = new StringBuilder();
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
String command = commands[i].trim();
|
||||
if (command.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mergedCmd.length() > 0) {
|
||||
mergedCmd.append(" && ");
|
||||
}
|
||||
|
||||
mergedCmd.append(command);
|
||||
}
|
||||
|
||||
String cmd = "cmd.exe /c " + mergedCmd.toString();
|
||||
log.info("准备执行合并命令:{}", cmd);
|
||||
|
||||
// 更新数据库中的运行状态为运行中
|
||||
AihumanRealConfig updateStatus = new AihumanRealConfig();
|
||||
updateStatus.setId(id);
|
||||
updateStatus.setRunStatus("1"); // 1表示运行中
|
||||
baseMapper.updateById(updateStatus);
|
||||
|
||||
// 使用线程池执行命令并监听输出
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(cmd);
|
||||
// 保存进程引用,用于后续停止操作
|
||||
runningProcess = process;
|
||||
|
||||
// 获取进程ID并保存到Redis
|
||||
String pid = getProcessId(process);
|
||||
if (!"unknown".equals(pid)) {
|
||||
RedisUtils.setCacheObject(redisKey, pid);
|
||||
log.info("保存进程ID到Redis:key={}, pid={}", redisKey, pid);
|
||||
}
|
||||
|
||||
// 读取标准输出
|
||||
new Thread(() -> {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.info("[LiveTalking] {}", line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("读取命令输出失败", e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
// 读取debug输出
|
||||
new Thread(() -> {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.debug("[LiveTalking DEBUG] {}", line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("读取命令debug输出失败", e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
// 等待进程结束
|
||||
int exitCode = process.waitFor();
|
||||
log.info("LiveTalking进程结束,退出码: {}", exitCode);
|
||||
|
||||
// 进程结束后更新数据库状态为已停止
|
||||
AihumanRealConfig endStatus = new AihumanRealConfig();
|
||||
endStatus.setId(id);
|
||||
endStatus.setRunStatus("0"); // 0表示已停止
|
||||
baseMapper.updateById(endStatus);
|
||||
|
||||
// 进程结束后从Redis中删除进程ID
|
||||
RedisUtils.deleteObject(redisKey);
|
||||
log.info("从Redis中删除进程ID:key={}", redisKey);
|
||||
|
||||
// 进程结束后清空引用
|
||||
runningProcess = null;
|
||||
} catch (Exception e) {
|
||||
log.error("执行命令失败", e);
|
||||
// 发生异常时更新数据库状态为失败
|
||||
try {
|
||||
AihumanRealConfig errorStatus = new AihumanRealConfig();
|
||||
errorStatus.setId(id);
|
||||
errorStatus.setRunStatus("2"); // 2表示启动失败
|
||||
baseMapper.updateById(errorStatus);
|
||||
} catch (Exception ex) {
|
||||
log.error("更新状态失败", ex);
|
||||
}
|
||||
// 发生异常时从Redis中删除进程ID
|
||||
RedisUtils.deleteObject(redisKey);
|
||||
// 发生异常时清空引用
|
||||
runningProcess = null;
|
||||
}
|
||||
});
|
||||
|
||||
executor.shutdown();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("执行命令过程中发生异常", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查进程是否正在运行
|
||||
*
|
||||
* @param pid 进程ID
|
||||
* @return 是否正在运行
|
||||
*/
|
||||
private boolean isProcessRunning(String pid) {
|
||||
if (StringUtils.isEmpty(pid) || "unknown".equals(pid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
|
||||
ProcessBuilder processBuilder;
|
||||
|
||||
if (isWindows) {
|
||||
processBuilder = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid);
|
||||
} else {
|
||||
processBuilder = new ProcessBuilder("ps", "-p", pid);
|
||||
}
|
||||
|
||||
Process process = processBuilder.start();
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
// 在Windows上,tasklist命令如果找不到进程,退出码也是0,但输出中不会包含PID
|
||||
if (isWindows) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.contains(pid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// 在Linux/Mac上,ps命令如果找不到进程,退出码不为0
|
||||
return exitCode == 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("检查进程是否运行失败, pid={}", pid, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止正在运行的真人交互数字人配置任务
|
||||
*/
|
||||
@Override
|
||||
public Boolean stopByBo(AihumanRealConfigBo bo) {
|
||||
try {
|
||||
Integer id = bo.getId();
|
||||
String redisKey = "aihuman:process:" + id;
|
||||
|
||||
// 首先检查Redis中是否有对应的进程ID
|
||||
String pid = RedisUtils.getCacheObject(redisKey);
|
||||
if (StringUtils.isNotEmpty(pid)) {
|
||||
// 如果Redis中有进程ID,先尝试通过进程ID停止进程
|
||||
try {
|
||||
// 根据操作系统类型,使用不同的命令终止进程树
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
// Windows系统使用taskkill命令终止进程树
|
||||
log.info("通过Redis中的PID停止进程: taskkill /F /T /PID {}", pid);
|
||||
Process killProcess = Runtime.getRuntime().exec("taskkill /F /T /PID " + pid);
|
||||
// 等待kill命令执行完成
|
||||
killProcess.waitFor(5, TimeUnit.SECONDS);
|
||||
} else {
|
||||
// Linux/Mac系统使用pkill命令终止进程树
|
||||
Runtime.getRuntime().exec("pkill -P " + pid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("通过Redis中的PID停止进程失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 然后检查本地runningProcess引用
|
||||
if (runningProcess != null && runningProcess.isAlive()) {
|
||||
log.info("正在停止LiveTalking进程...");
|
||||
// 强制销毁进程树,确保完全停止
|
||||
destroyProcessTree(runningProcess);
|
||||
|
||||
// 更新数据库中的运行状态为已停止
|
||||
AihumanRealConfig updateStatus = new AihumanRealConfig();
|
||||
updateStatus.setId(id);
|
||||
updateStatus.setRunStatus("0"); // 0表示已停止
|
||||
baseMapper.updateById(updateStatus);
|
||||
|
||||
runningProcess = null;
|
||||
log.info("LiveTalking进程已停止");
|
||||
} else {
|
||||
log.warn("没有正在运行的LiveTalking进程");
|
||||
// 确保数据库中的状态也是已停止
|
||||
AihumanRealConfig updateStatus = new AihumanRealConfig();
|
||||
updateStatus.setId(id);
|
||||
updateStatus.setRunStatus("0"); // 0表示已停止
|
||||
baseMapper.updateById(updateStatus);
|
||||
}
|
||||
|
||||
// 无论如何都从Redis中删除进程ID
|
||||
RedisUtils.deleteObject(redisKey);
|
||||
log.info("从Redis中删除进程ID:key={}", redisKey);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("停止进程时发生异常", e);
|
||||
// 发生异常时也尝试从Redis中删除进程ID
|
||||
try {
|
||||
RedisUtils.deleteObject("aihuman:process:" + bo.getId());
|
||||
} catch (Exception ex) {
|
||||
log.error("从Redis中删除进程ID失败", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁进程及其子进程(进程树)
|
||||
*
|
||||
* @param process 要销毁的进程
|
||||
*/
|
||||
private void destroyProcessTree(Process process) {
|
||||
try {
|
||||
if (process.isAlive()) {
|
||||
// 获取进程ID
|
||||
String pid = getProcessId(process);
|
||||
log.info("获取到进程ID: {}", pid);
|
||||
|
||||
// 根据操作系统类型,使用不同的命令终止进程树
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
// Windows系统使用taskkill命令终止进程树
|
||||
log.info("执行taskkill命令终止进程树: taskkill /F /T /PID {}", pid);
|
||||
Process killProcess = Runtime.getRuntime().exec("taskkill /F /T /PID " + pid);
|
||||
// 等待kill命令执行完成
|
||||
killProcess.waitFor(5, TimeUnit.SECONDS);
|
||||
} else {
|
||||
// Linux/Mac系统使用pkill命令终止进程树
|
||||
Runtime.getRuntime().exec("pkill -P " + pid);
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("销毁进程树时发生异常", e);
|
||||
// 如果出现异常,尝试使用普通销毁方法
|
||||
process.destroy();
|
||||
try {
|
||||
// 强制销毁
|
||||
if (process.isAlive()) {
|
||||
process.destroyForcibly();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("强制销毁进程失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程ID
|
||||
*
|
||||
* @param process 进程对象
|
||||
* @return 进程ID
|
||||
*/
|
||||
private String getProcessId(Process process) {
|
||||
try {
|
||||
// 不同JVM实现可能有所不同,这里尝试通过反射获取
|
||||
if (process.getClass().getName().equals("java.lang.Win32Process") ||
|
||||
process.getClass().getName().equals("java.lang.ProcessImpl")) {
|
||||
Field f = process.getClass().getDeclaredField("handle");
|
||||
f.setAccessible(true);
|
||||
long handl = f.getLong(process);
|
||||
Kernel32 kernel = Kernel32.INSTANCE;
|
||||
WinNT.HANDLE handle = new WinNT.HANDLE();
|
||||
handle.setPointer(Pointer.createConstant(handl));
|
||||
return String.valueOf(kernel.GetProcessId(handle));
|
||||
} else if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
|
||||
Field f = process.getClass().getDeclaredField("pid");
|
||||
f.setAccessible(true);
|
||||
return String.valueOf(f.getInt(process));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取进程ID失败", e);
|
||||
}
|
||||
|
||||
// 如果反射获取失败,尝试通过wmic命令获取
|
||||
try {
|
||||
// 对于Windows系统,可以尝试使用wmic命令获取进程ID
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
ProcessHandle.Info info = process.toHandle().info();
|
||||
return String.valueOf(process.toHandle().pid());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("通过ProcessHandle获取进程ID失败", e);
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void onDestroy() {
|
||||
if (runningProcess != null && runningProcess.isAlive()) {
|
||||
try {
|
||||
log.info("应用关闭,正在停止数字人进程");
|
||||
destroyProcessTree(runningProcess);
|
||||
|
||||
// 查找所有运行状态为运行中的配置,并更新为已停止
|
||||
LambdaQueryWrapper<AihumanRealConfig> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(AihumanRealConfig::getRunStatus, "1");
|
||||
List<AihumanRealConfig> runningConfigs = baseMapper.selectList(lqw);
|
||||
for (AihumanRealConfig config : runningConfigs) {
|
||||
config.setRunStatus("0");
|
||||
baseMapper.updateById(config);
|
||||
|
||||
// 从Redis中删除对应的进程ID记录
|
||||
String redisKey = "aihuman:process:" + config.getId();
|
||||
RedisUtils.deleteObject(redisKey);
|
||||
log.info("应用关闭,从Redis中删除进程ID:key={}", redisKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("停止数字人进程失败", e);
|
||||
// 即使发生异常,也尝试清理Redis中的进程ID记录
|
||||
try {
|
||||
LambdaQueryWrapper<AihumanRealConfig> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(AihumanRealConfig::getRunStatus, "1");
|
||||
List<AihumanRealConfig> runningConfigs = baseMapper.selectList(lqw);
|
||||
for (AihumanRealConfig config : runningConfigs) {
|
||||
RedisUtils.deleteObject("aihuman:process:" + config.getId());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("清理Redis中的进程ID记录失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JNA接口定义,用于Windows系统获取进程ID
|
||||
interface Kernel32 extends Library {
|
||||
Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
|
||||
|
||||
int GetProcessId(WinNT.HANDLE hProcess);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
public class AihumanVolcengineServiceImpl {
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package org.ruoyi.aihuman.volcengine;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.aihuman.protocol.EventType;
|
||||
import org.ruoyi.aihuman.protocol.Message;
|
||||
import org.ruoyi.aihuman.protocol.MsgType;
|
||||
import org.ruoyi.aihuman.protocol.SpeechWebSocketClient;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class Bidirection {
|
||||
private static final String ENDPOINT = "wss://openspeech.bytedance.com/api/v3/tts/bidirection";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* Get resource ID based on voice type
|
||||
*
|
||||
* @param voice Voice type string
|
||||
* @return Corresponding resource ID
|
||||
*/
|
||||
public static String voiceToResourceId(String voice) {
|
||||
// Map different voice types to resource IDs based on actual needs
|
||||
if (voice.startsWith("S_")) {
|
||||
return "volc.megatts.default";
|
||||
}
|
||||
return "volc.service_type.10029";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Configure parameters
|
||||
String appId = System.getProperty("appId", "1055299334");
|
||||
String accessToken = System.getProperty("accessToken", "fOHuq4R4dirMYiOruCU3Ek9q75zV0KVW");
|
||||
String resourceId = System.getProperty("resourceId", "seed-tts-2.0");
|
||||
String voice = System.getProperty("voice", "zh_female_vv_uranus_bigtts");
|
||||
String text = System.getProperty("text", "你好呀!我是AI合成的语音,很高兴认识你。");
|
||||
String encoding = System.getProperty("encoding", "mp3");
|
||||
|
||||
if (appId.isEmpty() || accessToken.isEmpty()) {
|
||||
throw new IllegalArgumentException("Please set appId and accessToken system properties");
|
||||
}
|
||||
|
||||
// Set request headers
|
||||
Map<String, String> headers = Map.of(
|
||||
"X-Api-App-Key", appId,
|
||||
"X-Api-Access-Key", accessToken,
|
||||
"X-Api-Resource-Id", resourceId.isEmpty() ? voiceToResourceId(voice) : resourceId,
|
||||
"X-Api-Connect-Id", UUID.randomUUID().toString());
|
||||
|
||||
// Create WebSocket client
|
||||
SpeechWebSocketClient client = new SpeechWebSocketClient(new URI(ENDPOINT), headers);
|
||||
try {
|
||||
client.connectBlocking();
|
||||
Map<String, Object> request = Map.of(
|
||||
"user", Map.of("uid", UUID.randomUUID().toString()),
|
||||
"namespace", "BidirectionalTTS",
|
||||
"req_params", Map.of(
|
||||
"speaker", voice,
|
||||
"audio_params", Map.of(
|
||||
"format", encoding,
|
||||
"sample_rate", 24000,
|
||||
"enable_timestamp", true),
|
||||
// additions requires a JSON string
|
||||
"additions", objectMapper.writeValueAsString(Map.of(
|
||||
"disable_markdown_filter", false))));
|
||||
|
||||
// Start connection
|
||||
client.sendStartConnection();
|
||||
// Wait for connection started
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.CONNECTION_STARTED);
|
||||
|
||||
// Process each sentence
|
||||
String[] sentences = text.split("。");
|
||||
boolean audioReceived = false;
|
||||
for (int i = 0; i < sentences.length; i++) {
|
||||
if (sentences[i].trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
ByteArrayOutputStream audioStream = new ByteArrayOutputStream();
|
||||
|
||||
// Start session
|
||||
Map<String, Object> startReq = Map.of(
|
||||
"user", request.get("user"),
|
||||
"namespace", request.get("namespace"),
|
||||
"req_params", request.get("req_params"),
|
||||
"event", EventType.START_SESSION.getValue());
|
||||
client.sendStartSession(objectMapper.writeValueAsBytes(startReq), sessionId);
|
||||
// Wait for session started
|
||||
client.waitForMessage(MsgType.FULL_SERVER_RESPONSE, EventType.SESSION_STARTED);
|
||||
|
||||
// Send text
|
||||
for (char c : sentences[i].toCharArray()) {
|
||||
// Create new req_params with text
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> currentReqParams = new HashMap<>(
|
||||
(Map<String, Object>) request.get("req_params"));
|
||||
currentReqParams.put("text", String.valueOf(c));
|
||||
|
||||
// Create current request
|
||||
Map<String, Object> currentRequest = Map.of(
|
||||
"user", request.get("user"),
|
||||
"namespace", request.get("namespace"),
|
||||
"req_params", currentReqParams,
|
||||
"event", EventType.TASK_REQUEST.getValue());
|
||||
|
||||
client.sendTaskRequest(objectMapper.writeValueAsBytes(currentRequest), sessionId);
|
||||
}
|
||||
|
||||
// End session
|
||||
client.sendFinishSession(sessionId);
|
||||
|
||||
// Receive response
|
||||
while (true) {
|
||||
Message msg = client.receiveMessage();
|
||||
switch (msg.getType()) {
|
||||
case FULL_SERVER_RESPONSE:
|
||||
break;
|
||||
case AUDIO_ONLY_SERVER:
|
||||
if (!audioReceived && audioStream.size() > 0) {
|
||||
audioReceived = true;
|
||||
}
|
||||
if (msg.getPayload() != null) {
|
||||
audioStream.write(msg.getPayload());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected message: " + msg);
|
||||
}
|
||||
if (msg.getEvent() == EventType.SESSION_FINISHED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioStream.size() > 0) {
|
||||
String fileName = String.format("%s_session_%d.%s", voice, i, encoding);
|
||||
Files.write(new File(fileName).toPath(), audioStream.toByteArray());
|
||||
log.info("Audio saved to file: {}", fileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!audioReceived) {
|
||||
throw new RuntimeException("No audio data received");
|
||||
}
|
||||
|
||||
// End connection
|
||||
client.sendFinishConnection();
|
||||
} finally {
|
||||
client.closeBlocking();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?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="org.ruoyi.aihuman.mapper.AihumanInfoMapper">
|
||||
|
||||
<!-- 可在此添加自定义SQL语句 -->
|
||||
|
||||
</mapper>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?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="org.ruoyi.aihuman.mapper.AihumanConfigMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?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="org.ruoyi.aihuman.mapper.AihumanRealConfigMapper">
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user