From a6faaf100e88d6375d188ba172cfeb1f88d1c0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=BE=E4=B9=BE?= <1046762075@qq.com> Date: Fri, 31 Oct 2025 19:02:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=B9=E6=8E=A5=E6=9C=8B=E5=8F=8B?= =?UTF-8?q?=E5=9C=88=E3=80=81ai=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install/sql/luohuo_dev.sql | 123 ++----- docs/install/sql/luohuo_im_01.sql | 40 ++- .../chat/AiChatConversationController.java | 8 +- .../chat/AiChatMessageController.java | 8 +- .../chat/vo/conversation/AiDelReqVO.java | 15 + .../controller/model/AiApiKeyController.java | 11 +- .../model/AiChatRoleController.java | 97 +++-- .../controller/model/AiModelController.java | 127 ++++--- .../model/vo/apikey/AiApiKeyPageReqVO.java | 3 + .../model/vo/apikey/AiApiKeyRespVO.java | 1 + .../model/vo/apikey/AiApiKeySaveReqVO.java | 3 + .../vo/chatRole/AiChatRoleSaveMyReqVO.java | 4 + .../model/vo/model/AiModelPageReqVO.java | 3 + .../model/vo/model/AiModelRespVO.java | 3 + .../model/vo/model/AiModelSaveMyReqVO.java | 48 +++ .../model/vo/model/AiModelSaveReqVO.java | 3 + .../flex/ai/controller/package-info.java | 1 - .../luohuo/flex/ai/dal/model/AiApiKeyDO.java | 17 + .../luohuo/flex/ai/dal/model/AiModelDO.java | 18 + .../flex/ai/mapper/model/AiApiKeyMapper.java | 56 ++- .../flex/ai/mapper/model/AiChatMapper.java | 89 +++-- .../ai/mapper/model/AiChatRoleMapper.java | 74 ++-- .../chat/AiChatConversationService.java | 7 +- .../chat/AiChatConversationServiceImpl.java | 20 +- .../ai/service/chat/AiChatMessageService.java | 5 +- .../chat/AiChatMessageServiceImpl.java | 19 +- .../ai/service/model/AiApiKeyService.java | 14 +- .../ai/service/model/AiApiKeyServiceImpl.java | 160 +++++---- .../ai/service/model/AiChatRoleService.java | 4 +- .../service/model/AiChatRoleServiceImpl.java | 39 +- .../flex/ai/service/model/AiModelService.java | 49 +++ .../ai/service/model/AiModelServiceImpl.java | 337 +++++++++++------- .../common/interceptor/BlackInterceptor.java | 5 +- .../chat/service/impl/RoomAppServiceImpl.java | 4 - .../luohuo/flex/im/core/user/dao/FeedDao.java | 12 +- .../user/service/cache/UserSummaryCache.java | 2 + .../user/service/impl/FeedServiceImpl.java | 84 ++++- .../oauth/granter/RefreshTokenGranter.java | 63 +++- 38 files changed, 1023 insertions(+), 553 deletions(-) create mode 100644 luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiDelReqVO.java create mode 100644 luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveMyReqVO.java delete mode 100644 luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/package-info.java diff --git a/docs/install/sql/luohuo_dev.sql b/docs/install/sql/luohuo_dev.sql index c7b100b8..4cc2ab30 100644 --- a/docs/install/sql/luohuo_dev.sql +++ b/docs/install/sql/luohuo_dev.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 28/10/2025 17:10:28 + Date: 31/10/2025 19:00:36 */ SET NAMES utf8mb4; @@ -23,6 +23,8 @@ SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `ai_api_key`; CREATE TABLE `ai_api_key` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '创建模型的uid', + `public_status` bit(1) NOT NULL DEFAULT b'1' COMMENT '公开、私有', `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', `api_key` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密钥', `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '平台', @@ -35,33 +37,12 @@ CREATE TABLE `ai_api_key` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90154862069761 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_api_key -- ---------------------------- -INSERT INTO `ai_api_key` VALUES (1, '【OpenAI】ChatGPT', 'sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17', 'OpenAI', 'https://api.holdai.top', 0, '', '2024-05-10 01:37:55', '1', '2025-02-23 16:58:46', b'0', 1); -INSERT INTO `ai_api_key` VALUES (2, '呵呵', '123321', 'OpenAI', NULL, 0, '1', '2024-05-11 16:46:18', '1', '2024-05-17 15:15:08', b'1', 1); -INSERT INTO `ai_api_key` VALUES (3, '【Meta】Ollama', ' ', 'Ollama', 'http://127.0.0.1:11434', 0, '1', '2024-05-17 23:04:13', '1', '2024-07-05 01:30:10', b'0', 1); -INSERT INTO `ai_api_key` VALUES (4, '【百度】文心一言', 'x0cuLZ7XsaTCU08vuJWO87Lg|R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK', 'YiYan', NULL, 0, '1', '2024-05-18 09:26:02', '1', '2024-07-05 01:30:10', b'0', 1); -INSERT INTO `ai_api_key` VALUES (5, '【讯飞】星火', '75b161ed2aef4719b275d6e7f2a4d4cd|YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz', 'XingHuo', NULL, 0, '1', '2024-05-18 10:09:42', '1', '2025-02-23 18:35:06', b'0', 1); -INSERT INTO `ai_api_key` VALUES (6, '【阿里】通义千问', 'sk-7d903764249848cfa912733146da12d1', 'TongYi', NULL, 0, '1', '2024-05-18 10:33:12', '1', '2025-02-24 10:24:50', b'0', 1); -INSERT INTO `ai_api_key` VALUES (7, 'StableDiffusion', 'sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx', 'StableDiffusion', NULL, 0, '1', '2024-06-01 15:11:18', '1', '2024-07-05 01:30:10', b'0', 1); -INSERT INTO `ai_api_key` VALUES (8, '【本地】Ollama', 'null', 'Ollama', 'http://localhost:11434', 0, '1', '2024-06-01 20:04:43', '1', '2025-02-24 09:21:16', b'0', 1); -INSERT INTO `ai_api_key` VALUES (9, 'Suno', 'Suno', 'Suno', 'http://127.0.0.1:3001', 0, '1', '2024-06-29 09:14:28', '1', '2024-07-05 01:30:10', b'0', 1); -INSERT INTO `ai_api_key` VALUES (10, 'Midjourney', 'sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf', 'Midjourney', 'https://api.holdai.top/mj', 0, '1', '2024-06-29 09:40:20', '1', '2024-07-05 01:30:10', b'0', 1); -INSERT INTO `ai_api_key` VALUES (11, 'DeepSeek', 'sk-5b612c071f904fd59808dc07c9a4f1b8', 'DeepSeek', NULL, 0, '1', '2024-07-06 12:06:04', '1', '2025-03-13 21:18:31', b'0', 1); -INSERT INTO `ai_api_key` VALUES (12, '智谱', '2f35fb6ca4ea41fab898729b7fac086c.6ESSfPcCkxaKEUlR', 'ZhiPu', NULL, 0, '1', '2024-07-06 18:01:15', '1', '2025-03-11 07:47:46', b'0', 1); -INSERT INTO `ai_api_key` VALUES (13, '【微软 OpenAI】ChatGPT', 'XXX', 'AzureOpenAI', 'https://eastusprejade.openai.azure.com', 0, '1', '2024-08-10 14:09:27', '1', '2024-08-10 15:08:27', b'0', 1); -INSERT INTO `ai_api_key` VALUES (14, '字节豆包', '5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272', 'DouBao', NULL, 0, '1', '2025-02-23 19:52:37', '1', '2025-02-23 19:52:37', b'0', 1); -INSERT INTO `ai_api_key` VALUES (15, '腾讯混元', 'sk-bcd', 'HunYuan', NULL, 0, '1', '2025-02-23 20:59:10', '1', '2025-02-24 09:20:04', b'0', 1); -INSERT INTO `ai_api_key` VALUES (16, '腾讯知识引擎', 'sk-abc', 'HunYuan', 'https://api.lkeap.cloud.tencent.com', 0, '1', '2025-02-23 20:59:49', '1', '2025-02-24 09:20:00', b'0', 1); -INSERT INTO `ai_api_key` VALUES (17, '【本地】deepseek-r1', 'null', 'Ollama', 'http://localhost:11434', 0, '1', '2025-02-24 09:20:31', '1', '2025-02-24 09:21:40', b'1', 1); -INSERT INTO `ai_api_key` VALUES (18, '硅基流动', 'sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz', 'SiliconFlow', '', 0, '1', '2025-02-24 20:34:19', '1', '2025-02-24 20:34:19', b'0', 1); -INSERT INTO `ai_api_key` VALUES (19, 'MiniMax', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw', 'MiniMax', NULL, 0, '1', '2025-03-11 20:06:35', '1', '2025-03-11 20:06:35', b'0', 1); -INSERT INTO `ai_api_key` VALUES (20, '月之暗灭', 'sk-g9ZpHUj5nEBwY9JJCyUxaxKOwC5M8cvpQFg211ksPJ64yIlm', 'Moonshot', NULL, 0, '1', '2025-03-11 20:07:27', '1', '2025-10-28 09:03:46', b'0', 1); -INSERT INTO `ai_api_key` VALUES (21, 'FastGPT', 'fastgpt-aqcc61kFtF8CeaglnGAfQOCIDWwjGdJVJHv6hIlMo28otFlva2aZNK', 'OpenAI', 'https://cloud.fastgpt.cn/api', 0, '1', '2025-03-12 13:51:06', '1', '2025-03-12 20:06:12', b'0', 1); -INSERT INTO `ai_api_key` VALUES (22, 'Dify', 'app-4hy2d7fJauSbrKbzTKX1afuP', 'OpenAI', 'http://127.0.0.1:3000', 0, '1', '2025-03-12 21:26:34', '1', '2025-03-12 21:50:29', b'0', 1); +INSERT INTO `ai_api_key` VALUES (90154862069760, 20901198351872, b'1', 'kimi', 'sk-HD0GKyxX3bYuycGuV5de202U418gczeAYcZVYZucwWrBEu2V', 'Moonshot', NULL, 0, NULL, '2025-10-30 18:43:15', NULL, '2025-10-30 11:26:16', b'0', 1); -- ---------------------------- -- Table structure for ai_chat_conversation @@ -87,15 +68,14 @@ CREATE TABLE `ai_chat_conversation` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NULL DEFAULT NULL COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1781604279872581774 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90190392010241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_conversation -- ---------------------------- -INSERT INTO `ai_chat_conversation` VALUES (1781604279872581684, 20901198351872, 10, '新对话', 53, 'qwen-72b-chat', b'0', NULL, '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议!!!!', 1, 20, 2, '1', '2024-05-20 22:22:39', '1', '2025-10-28 03:38:52', b'1', 1); -INSERT INTO `ai_chat_conversation` VALUES (1781604279872581685, 20901198351872, 10, 'gpt测试', 53, 'gpt-3.5-turbo', b'1', '2024-05-21 16:26:29', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议!!!!', 1, 20, 20, '1', '2024-05-21 09:37:38', '1', '2025-10-28 03:38:52', b'1', 1); -INSERT INTO `ai_chat_conversation` VALUES (1781604279872581686, 20901198351872, 10, '新的gpt测试', 53, 'gpt-3.5-turbo', b'0', NULL, '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议!!!!', 1, 20, 20, '1', '2024-05-21 14:43:32', '1', '2025-10-28 03:38:52', b'1', 1); -INSERT INTO `ai_chat_conversation` VALUES (1781604279872581687, 20901198351872, 10, '新对话', 53, 'gpt-3.5-turbo', b'0', NULL, '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议!!!!', 1, 20, 20, '1', '2024-05-21 16:37:12', '1', '2025-10-28 03:38:52', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90159593245184, 20901198351872, 90159576467968, 'Hula官方角色', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '我是Hula官方机器人,非常聪明', 0.8, 4096, 10, NULL, '2025-10-30 19:02:02', NULL, '2025-10-30 19:02:02', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (90160268528128, 20901198351872, 90159576467968, 'Hula官方角色', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '我是Hula官方机器人,非常聪明', 0.8, 4096, 10, NULL, '2025-10-30 19:04:43', NULL, '2025-10-30 19:04:43', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (90190392010240, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-10-30 21:04:26', NULL, '2025-10-30 21:04:26', b'0', 1); -- ---------------------------- -- Table structure for ai_chat_message @@ -118,17 +98,13 @@ CREATE TABLE `ai_chat_message` ( `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人', `update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint NULL DEFAULT NULL COMMENT '租户编号', + `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2737 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90190480090626 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_message -- ---------------------------- -INSERT INTO `ai_chat_message` VALUES (1481, 1781604279872581686, NULL, 1, 10, 'user', 'gpt-3.5-turbo', 11, '测试小苹果?', b'0', NULL, '1', '2024-05-21 14:47:37', '1', '2024-07-05 01:30:39', b'0', 1); -INSERT INTO `ai_chat_message` VALUES (1482, 1781604279872581686, 1481, 1, 10, 'assistant', 'gpt-3.5-turbo', 11, '很,我抱无法提供果歉关于\"测试小苹\"的信息。请问您有其他问题或者需要帮助吗?我会尽力回答提或者供帮助。', b'0', NULL, '1', '2024-05-21 14:47:37', NULL, '2024-07-05 01:30:39', b'0', 1); -INSERT INTO `ai_chat_message` VALUES (1483, 1781604279872581686, NULL, 1, 10, 'user', 'gpt-3.5-turbo', 11, '真的么?', b'0', NULL, '1', '2024-05-21 14:47:59', '1', '2024-07-05 01:30:39', b'0', 1); -INSERT INTO `ai_chat_message` VALUES (1484, 1781604279872581686, 1483, 1, 10, 'assistant', 'gpt-3.5-turbo', 11, '', b'0', NULL, '1', '2024-05-21 14:47:59', '1', '2024-07-05 01:30:39', b'0', 1); -- ---------------------------- -- Table structure for ai_chat_role @@ -155,19 +131,13 @@ CREATE TABLE `ai_chat_role` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90190358455809 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_role -- ---------------------------- -INSERT INTO `ai_chat_role` VALUES (10, NULL, 11, '通用 AI 助手', 'http://test.yudao.iocoder.cn/eaef5f41acb911dd718429a0702dcc3c61160d16e57ba1d543132fab58934f9f.png', '助手', 1, '默认角色', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议!!!!', NULL, NULL, b'1', 0, '1', '2024-05-13 20:44:48', '1', '2024-07-05 01:30:30', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (11, NULL, 14, 'Python 专家', 'http://test.yudao.iocoder.cn/5c5528504c307d34af504f39bc4e7007d2f6f31ee00dab699cc91584d1af8aca.png', '编程开发', 2, 'Python', '我希望你能作为一名 python 专家。我将向你提供有关我的技术问题的所有信息,而你的角色是解决我的问题。你应该用你的计算机科学、网络基础设施和 IT 安全知识来解决我的问题。在你的回答中,使用聪明的、简单的、为各种层次的人所理解的语言会有帮助。逐步解释你的解决方案并使用要点是很有帮助的。尽量避免过多的技术细节,但在必要时使用它们。我希望你用解决方案来回答,而不是写任何解释。', NULL, '17', b'1', 0, '1', '2024-05-17 23:23:20', '1', '2025-03-14 21:10:47', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (12, 1, NULL, '小可爱角色', 'http://test.yudao.iocoder.cn/5752750225f522ba794b16ad0b0e73d268093760546ebc2519b1a5dfc5b064d1.jpg', NULL, 0, '卡比兽,爱睡觉', '你好,我是宠物小精灵大师!', NULL, NULL, b'0', 0, '1', '2024-05-25 13:25:46', '1', '2024-07-05 01:30:30', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (13, NULL, 17, '写作助手', 'http://test.yudao.iocoder.cn/2a124ba5743f9572fcbd2718a64ba599618c96ddba6c7391ad35906cd3f37f94.png', '写作', 10, '一个厉害的写手高手', '你是一个非常会写作的人!', NULL, NULL, b'1', 0, '1', '2024-07-10 22:55:58', '1', '2024-07-10 22:56:04', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (14, NULL, 17, '脑图助手', 'http://test.yudao.iocoder.cn/7401c394a43280732e6aaa715fbfefadc33eeb8fab8f45f6b53f1acf6b22ae29.png', '写作', 11, '一个厉害的思维大师!', '你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:\n # Geek-AI 助手\n ## 完整的开源系统\n ### 前端开源\n ### 后端开源\n ## 支持各种大模型\n ### OpenAI\n ### Azure\n ### 文心一言\n ### 通义千问\n ## 集成多种收费方式\n ### 支付宝\n ### 微信\n 除此之外不要任何解释性语句。', NULL, NULL, b'1', 0, '1', '2024-07-29 21:47:20', '1', '2024-07-29 21:48:00', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (15, NULL, 26, '测试知识库', 'http://test.yudao.iocoder.cn/d3a7656aae53c8f063d4578c7a65f9045ef25420ff32cc32dd121a60abcf9127.png', '助手', 10, '我只是个小助手', '一个问答助手', '3', NULL, b'1', 0, '1', '2025-03-09 17:35:36', '1', '2025-03-09 18:32:34', b'1', 1); -INSERT INTO `ai_chat_role` VALUES (16, 1, 21, '测试知识库', 'http://test.yudao.iocoder.cn/a2cfbf52d157911ecfb29c4a26b4a19247f6bb2c97a5104c46c970fc235880ef.png', '助手', 0, '测试一下', '一个小助手', '2', NULL, b'0', 0, '1', '2025-03-09 18:33:01', '1', '2025-03-09 18:51:04', b'0', 1); -INSERT INTO `ai_chat_role` VALUES (17, 1, NULL, '一个可以查询天气的小助手', 'http://test.yudao.iocoder.cn/de74fdbe9ad40173345d4cd1e2b684fcbb5e797f7b7cc37b42dcbaa20dcf6bb3.png', NULL, 0, '查查查天气', '天气小助手', '', '18', b'0', 0, '1', '2025-03-14 20:03:27', '1', '2025-03-14 20:03:27', b'0', 1); +INSERT INTO `ai_chat_role` VALUES (90159576467968, 20901198351872, 90158209124864, 'Hula官方角色', 'https://cdn.hulaspark.com/avatar/1046762075/43de7a13eda6b8c29622f5b4ca429156.webp', NULL, 0, '我是Hula官方机器人', '我是Hula官方机器人,非常聪明', NULL, NULL, b'0', 0, NULL, '2025-10-30 19:01:58', NULL, '2025-10-30 19:01:58', b'0', 1); +INSERT INTO `ai_chat_role` VALUES (90190358455808, NULL, 90158209124864, '测试HuLa', 'https://cdn.hulaspark.com/avatar/2439646234/993c2cfc546fabc1ee127037102378d9.webp', '0', 0, '测试', '你是一个vue专家', NULL, NULL, b'1', 0, NULL, '2025-10-30 21:04:17', NULL, '2025-10-30 21:04:17', b'0', 1); -- ---------------------------- -- Table structure for ai_image @@ -356,6 +326,7 @@ INSERT INTO `ai_mind_map` VALUES (3, 1, '生成一个 Vue 核心关键点', '# V DROP TABLE IF EXISTS `ai_model`; CREATE TABLE `ai_model` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '创建模型的uid', `key_id` bigint NOT NULL COMMENT 'API 秘钥编号', `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模型名字', `model` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模型标识', @@ -363,6 +334,7 @@ CREATE TABLE `ai_model` ( `type` tinyint NOT NULL COMMENT '模型类型', `sort` int NOT NULL COMMENT '排序', `status` tinyint NOT NULL COMMENT '状态', + `public_status` bit(1) NOT NULL DEFAULT b'1' COMMENT '公开、私有', `temperature` double NULL DEFAULT NULL COMMENT '温度参数', `max_tokens` int NULL DEFAULT NULL COMMENT '单条回复的最大 Token 数量', `max_contexts` int NULL DEFAULT NULL COMMENT '上下文的最大 Message 数量', @@ -372,59 +344,16 @@ CREATE TABLE `ai_model` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 模型表' ROW_FORMAT = Dynamic; + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`user_id` ASC) USING BTREE, + INDEX `idx_public_status`(`public_status` ASC) USING BTREE, + INDEX `idx_user_public`(`user_id` ASC, `public_status` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 90158209124865 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 模型表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_model -- ---------------------------- -INSERT INTO `ai_model` VALUES (9, 4, 'ernie-tiny-8k', 'ernie-tiny-8k', 'YiYan', 1, 100, 0, NULL, NULL, NULL, '', '2024-05-10 01:38:04', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (10, 4, 'ERNIE 4.0', 'ERNIE 4.0', 'YiYan', 1, 100, 0, 1, 4096, 10, '', '2024-05-10 01:38:04', '1', '2025-03-03 01:26:22', b'1', 1); -INSERT INTO `ai_model` VALUES (11, 1, 'gpt-3.5-turbo', 'gpt-3.5-turbo', 'OpenAI', 1, 0, 0, 1, 4096, 20, '1', '2024-05-11 17:57:56', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (12, 3, 'llama3', 'llama3', 'Ollama', 1, 1, 0, 1, 4096, 10, '1', '2024-05-17 23:16:38', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (13, 5, '星火大模型3.5', 'generalv3.5', 'XingHuo', 1, 5, 0, NULL, NULL, NULL, '1', '2024-05-18 10:10:21', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (14, 6, 'qwen-72b-chat', 'qwen-72b-chat', 'TongYi', 1, 6, 0, 1, 2000, 20, '1', '2024-05-18 10:33:47', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (15, 6, 'Qwen-7B', 'Qwen-7B', 'TongYi', 1, 1000, 0, 1, 4096, 20, '1', '2024-06-01 20:05:14', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (16, 4, 'ernie_speed', 'ernie_speed', 'YiYan', 1, 2000, 0, 1, 4096, 10, '1', '2024-06-02 09:00:21', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (17, 11, 'deepseek-chat', 'deepseek-chat', 'DeepSeek', 1, 300, 0, 0.75, 4096, 20, '1', '2024-07-06 12:06:47', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (18, 11, 'deepseek-reasoner', 'deepseek-reasoner', 'DeepSeek', 1, 301, 0, 1, 4096, 20, '1', '2024-07-06 12:07:25', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (19, 12, 'GLM-4', 'GLM-4', 'ZhiPu', 1, 400, 0, 0.75, 2000, 20, '1', '2024-07-06 18:01:45', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (20, 13, '【微软】gpt-35-turbo', 'gpt-35-turbo', 'AzureOpenAI', 1, 2000, 0, 1, 4096, 20, '1', '2024-08-10 14:10:07', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (21, 8, 'qwen:4b', 'qwen:4b', 'Ollama', 1, 9999, 0, NULL, NULL, NULL, '1', '2025-02-23 19:05:00', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (22, 14, 'doubao-1-5-lite-32k', 'doubao-1-5-lite-32k-250115', 'DouBao', 1, 350, 0, NULL, NULL, NULL, '1', '2025-02-23 19:53:24', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (23, 14, 'deepseek-r1-zijie', 'deepseek-r1-250120', 'DouBao', 1, 351, 0, NULL, NULL, NULL, '1', '2025-02-23 19:58:32', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (24, 15, 'hunyuan-turbo', 'hunyuan-turbo', 'HunYuan', 1, 380, 0, NULL, NULL, NULL, '1', '2025-02-23 21:00:37', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (25, 16, 'deepseek-r1-tengxun', 'deepseek-r1', 'HunYuan', 1, 381, 0, NULL, NULL, NULL, '1', '2025-02-23 21:01:20', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (26, 8, 'deepseek-r1-local', 'deepseek-r1', 'Ollama', 1, 10000, 0, NULL, NULL, NULL, '1', '2025-02-24 09:22:32', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (27, 6, 'deepseek-r1-aliyun', 'deepseek-r1', 'TongYi', 1, 7, 0, NULL, NULL, NULL, '1', '2025-02-24 10:05:51', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (28, 6, 'deepseek-v3-aliyun', 'deepseek-v3', 'TongYi', 1, 8, 0, NULL, NULL, NULL, '1', '2025-02-24 10:24:29', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (29, 18, 'deepseek-r1-siliconflow', 'deepseek-ai/DeepSeek-R1', 'SiliconFlow', 1, 12000, 0, NULL, NULL, NULL, '1', '2025-02-24 20:35:18', '1', '2025-03-03 01:26:22', b'0', 1); -INSERT INTO `ai_model` VALUES (30, 8, 'nomic-embed-text', 'nomic-embed-text', 'Ollama', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-02-24 20:35:18', '1', '2025-03-03 01:51:28', b'0', 1); -INSERT INTO `ai_model` VALUES (31, 6, 'wanx-v1', 'wanx-v1', 'TongYi', 2, 3005, 0, NULL, NULL, NULL, '1', '2025-03-03 20:08:49', '1', '2025-03-03 21:36:24', b'0', 1); -INSERT INTO `ai_model` VALUES (32, 6, 'wanx-sketch-to-image-v1', 'wanx-sketch-to-image-v1', 'TongYi', 2, 3005, 0, NULL, NULL, NULL, '1', '2025-03-03 20:09:05', '1', '2025-03-03 21:36:20', b'0', 1); -INSERT INTO `ai_model` VALUES (33, 4, 'sd_xl', 'sd_xl', 'YiYan', 2, 3004, 0, NULL, NULL, NULL, '1', '2025-03-03 20:30:49', '1', '2025-03-03 21:36:14', b'0', 1); -INSERT INTO `ai_model` VALUES (34, 12, 'cogview-3', 'cogview-3', 'ZhiPu', 2, 3003, 0, NULL, NULL, NULL, '1', '2025-03-03 20:31:40', '1', '2025-03-03 21:36:07', b'0', 1); -INSERT INTO `ai_model` VALUES (35, 1, 'dall-e-3', 'dall-e-3', 'OpenAI', 2, 3002, 0, NULL, NULL, NULL, '1', '2025-03-03 20:56:55', '1', '2025-03-03 21:36:02', b'0', 1); -INSERT INTO `ai_model` VALUES (36, 1, 'dall-e-2', 'dall-e-2', 'OpenAI', 2, 3002, 0, NULL, NULL, NULL, '1', '2025-03-03 20:57:57', '1', '2025-03-03 21:35:58', b'0', 1); -INSERT INTO `ai_model` VALUES (37, 7, 'stable-diffusion-v1-6', 'stable-diffusion-v1-6', 'StableDiffusion', 2, 3001, 0, NULL, NULL, NULL, '1', '2025-03-03 21:30:59', '1', '2025-03-03 21:35:48', b'0', 1); -INSERT INTO `ai_model` VALUES (38, 10, 'midjourney', 'midjourney', 'Midjourney', 2, 3000, 0, NULL, NULL, NULL, '1', '2025-03-03 21:35:01', '1', '2025-03-03 21:35:01', b'0', 1); -INSERT INTO `ai_model` VALUES (39, 10, 'niji', 'niji', 'Midjourney', 2, 3000, 0, NULL, NULL, NULL, '1', '2025-03-03 21:35:23', '1', '2025-03-03 21:35:23', b'0', 1); -INSERT INTO `ai_model` VALUES (40, 8, 'mxbai-embed-large', 'mxbai-embed-large', 'Ollama', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-03-10 09:10:33', '1', '2025-03-10 09:10:33', b'0', 1); -INSERT INTO `ai_model` VALUES (41, 6, 'text-embedding-v3', 'text-embedding-v3', 'TongYi', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-03-10 22:04:30', '1', '2025-03-10 22:04:30', b'0', 1); -INSERT INTO `ai_model` VALUES (42, 12, 'embedding-3', 'embedding-3', 'ZhiPu', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-03-11 07:48:21', '1', '2025-03-11 07:48:21', b'0', 1); -INSERT INTO `ai_model` VALUES (43, 1, 'text-embedding-ada-002 ', 'text-embedding-ada-002 ', 'OpenAI', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-03-11 09:38:09', '1', '2025-03-11 09:40:54', b'0', 1); -INSERT INTO `ai_model` VALUES (44, 19, 'abab6.5g-chat', 'abab6.5g-chat', 'MiniMax', 1, 500, 0, NULL, NULL, NULL, '1', '2025-03-11 20:08:03', '1', '2025-03-11 20:08:15', b'0', 1); -INSERT INTO `ai_model` VALUES (45, 19, 'embo-01', 'embo-01', 'MiniMax', 5, 20000, 0, NULL, NULL, NULL, '1', '2025-03-11 20:08:47', '1', '2025-03-11 20:15:17', b'0', 1); -INSERT INTO `ai_model` VALUES (46, 20, 'moonshot-v1-8k', 'moonshot-v1-8k', 'Moonshot', 1, 600, 0, NULL, NULL, NULL, '1', '2025-03-11 20:10:24', '1', '2025-03-11 20:10:24', b'0', 1); -INSERT INTO `ai_model` VALUES (47, 21, 'FastGPT', 'FastGPT', 'OpenAI', 1, 8888, 0, NULL, NULL, NULL, '1', '2025-03-12 13:51:46', '1', '2025-03-12 13:51:46', b'0', 1); -INSERT INTO `ai_model` VALUES (48, 22, 'Dify', 'Dify', 'OpenAI', 1, 8888, 0, NULL, NULL, NULL, '1', '2025-03-12 21:27:34', '1', '2025-03-12 21:27:34', b'0', 1); -INSERT INTO `ai_model` VALUES (49, 1, 'gpt-4o-mini', 'gpt-4o-mini', 'OpenAI', 1, 0, 0, 1, 4096, 20, '1', '2025-03-13 12:46:02', '1', '2025-03-13 12:47:02', b'0', 1); -INSERT INTO `ai_model` VALUES (50, 8, 'qwen2.5', 'qwen2.5', 'Ollama', 1, 9999, 0, NULL, NULL, NULL, '1', '2025-03-13 15:05:15', '1', '2025-03-13 20:38:28', b'0', 1); -INSERT INTO `ai_model` VALUES (51, 19, 'MiniMax-Text-01', 'MiniMax-Text-01', 'MiniMax', 1, 500, 0, NULL, NULL, NULL, '1', '2025-03-13 20:37:48', '1', '2025-03-13 20:38:06', b'0', 1); -INSERT INTO `ai_model` VALUES (52, 18, 'deepseek-v3-siliconflow-vip', 'Pro/deepseek-ai/DeepSeek-V3', 'SiliconFlow', 1, 12000, 0, NULL, NULL, NULL, '1', '2025-03-13 21:06:01', '1', '2025-03-13 21:06:01', b'0', 1); -INSERT INTO `ai_model` VALUES (53, 20, 'moonshot-v1-128k', 'moonshot-v1-128k', 'Moonshot', 1, 600, 0, NULL, NULL, NULL, '1', '2025-03-13 21:15:16', '1', '2025-03-13 21:15:16', b'0', 1); -INSERT INTO `ai_model` VALUES (54, 6, 'qwen-max-latest', 'qwen-max-latest', 'TongYi', 1, 6, 0, NULL, NULL, NULL, '1', '2025-03-13 21:32:27', '1', '2025-03-13 21:34:12', b'0', 1); -INSERT INTO `ai_model` VALUES (55, 5, '4.0Ultra', '4.0Ultra', 'XingHuo', 1, 5, 0, NULL, NULL, NULL, '1', '2025-03-14 11:24:14', '1', '2025-03-14 11:24:14', b'0', 1); +INSERT INTO `ai_model` VALUES (90158209124864, 20901198351872, 90154862069760, 'Hula小模型', 'moonshot-v1-128k', 'Moonshot', 1, 0, 0, b'1', 0.8, 4096, 10, NULL, '2025-10-30 18:56:33', NULL, '2025-10-30 11:05:33', b'0', 1); -- ---------------------------- -- Table structure for ai_music @@ -2302,8 +2231,8 @@ CREATE TABLE `def_user` ( -- ---------------------------- -- Records of def_user -- ---------------------------- -INSERT INTO `def_user` VALUES (61170828519936, 2, 'bot', 'HuLa小管家', '', '022', NULL, NULL, '', NULL, b'0', '', '', '1', b'1', '', '2025-08-11 11:11:03.139', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"120.231.232.41\", \"createIpDetail\": null, \"updateIpDetail\": null}', '2025-08-19 10:05:05', 1, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-07-07 15:27:02', 1, '2025-03-27 04:23:08', NULL, '2025-07-16 12:26:15', 0, 1); -INSERT INTO `def_user` VALUES (61170828519937, 2, '2439646234', 'Dawn', '2439646234@qq.com', 'https://cdn.hulaspark.com/avatar/2439646234/6ec99d37b8ba1296c325d2d36b46a14d.webp', NULL, NULL, '', NULL, b'0', '', '', '1', b'1', '', '2025-08-11 11:11:03.189', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"183.15.179.234\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"183.15.179.234\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', NULL, 0, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-10-28 16:38:01', 1, '2025-03-27 04:23:08', NULL, '2025-10-28 16:55:55', 0, 1); +INSERT INTO `def_user` VALUES (61170828519936, 2, 'bot', 'HuLa小管家', '', '022', NULL, NULL, '', NULL, b'0', '', '', '1', b'1', '', '2025-08-11 11:11:03.139', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"120.231.232.41\", \"createIpDetail\": null, \"updateIpDetail\": null}', '2025-10-28 17:08:01', 2, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-07-07 15:27:02', 1, '2025-03-27 04:23:08', NULL, '2025-07-16 12:26:15', 0, 1); +INSERT INTO `def_user` VALUES (61170828519937, 2, '2439646234', 'Dawn', '2439646234@qq.com', 'https://cdn.hulaspark.com/avatar/2439646234/6ec99d37b8ba1296c325d2d36b46a14d.webp', NULL, NULL, '', NULL, b'0', '', '', '1', b'1', '', '2025-08-11 11:11:03.189', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"183.15.179.234\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"183.15.179.234\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', NULL, 0, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-10-31 18:11:19', 1, '2025-03-27 04:23:08', NULL, '2025-10-31 18:36:18', 0, 1); -- ---------------------------- -- Table structure for def_user_application @@ -2388,7 +2317,7 @@ CREATE TABLE `extend_interface_log` ( -- Records of extend_interface_log -- ---------------------------- INSERT INTO `extend_interface_log` VALUES (66567882983426, 244439130119864323, '阿里短信', 0, 1, '2025-08-26 16:37:01', '2025-08-26 16:37:00', NULL, '2025-08-26 16:37:00', NULL, 0, 0); -INSERT INTO `extend_interface_log` VALUES (655249535051914248, 244881451621810192, '腾讯邮件', 576, 56, '2025-10-28 16:44:42', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); +INSERT INTO `extend_interface_log` VALUES (655249535051914248, 244881451621810192, '腾讯邮件', 624, 57, '2025-10-31 17:41:02', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); -- ---------------------------- -- Table structure for extend_interface_logging @@ -2602,7 +2531,7 @@ CREATE TABLE `worker_node` ( `created` timestamp NULL DEFAULT NULL COMMENT '创建时间', `is_del` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 891 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 921 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/docs/install/sql/luohuo_im_01.sql b/docs/install/sql/luohuo_im_01.sql index b37b5636..8424d31c 100644 --- a/docs/install/sql/luohuo_im_01.sql +++ b/docs/install/sql/luohuo_im_01.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 28/10/2025 17:10:36 + Date: 31/10/2025 19:00:29 */ SET NAMES utf8mb4; @@ -151,7 +151,7 @@ CREATE TABLE `im_contact` ( INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE, INDEX `idx_contact_room_uid_hide`(`room_id` ASC, `uid` ASC, `hide` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 69082079590943 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 69082079592702 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_contact @@ -168,7 +168,7 @@ CREATE TABLE `im_feed` ( `id` bigint NOT NULL, `uid` bigint NOT NULL COMMENT '用户id', `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL COMMENT '朋友圈文案', - `permission` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL DEFAULT '1' COMMENT 'privacy -> 私密 open -> 公开 partVisible -> 部分可见 notAnyone -> 不给谁看', + `permission` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL DEFAULT '1' COMMENT 'privacy -> 私密 open -> 公开 partVisible -> 部分可见 notAnyone -> 不给谁看', `media_type` tinyint NULL DEFAULT NULL COMMENT '朋友圈内容类型(0: 纯文字 1: 图片, 2: 视频)', `create_time` datetime NOT NULL COMMENT '创建时间', `tenant_id` bigint NOT NULL DEFAULT 1, @@ -183,6 +183,8 @@ CREATE TABLE `im_feed` ( -- ---------------------------- -- Records of im_feed -- ---------------------------- +INSERT INTO `im_feed` VALUES (90012511218176, 43329605667840, 'Kkk', 'open', 0, '2025-10-30 09:17:35', 1, 43329605667840, 0, 1, NULL); +INSERT INTO `im_feed` VALUES (90191368924672, 10937855681024, '你好,测试一下朋友圈吧🤯', 'open', 0, '2025-10-30 21:08:18', 1, 10937855681024, 0, 1, NULL); -- ---------------------------- -- Table structure for im_feed_media @@ -277,7 +279,7 @@ CREATE TABLE `im_group_member` ( INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE, INDEX `idx_group_member_uid_isdel_groupid`(`uid` ASC, `is_del` ASC, `group_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89400339964930 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90516414902273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_group_member @@ -340,7 +342,7 @@ CREATE TABLE `im_message` ( INDEX `idx_from_uid`(`from_uid` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89403284366339 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90516414902274 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message @@ -370,7 +372,7 @@ CREATE TABLE `im_message_mark` ( INDEX `idx_uid`(`uid` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89397676581889 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90188940422657 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message_mark @@ -402,7 +404,7 @@ CREATE TABLE `im_notice` ( INDEX `idx_receiver_type`(`receiver_id` ASC, `event_type` ASC) USING BTREE, INDEX `idx_sender`(`sender_id` ASC) USING BTREE, INDEX `idx_related`(`apply_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89403246617603 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90516389736451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_notice @@ -452,12 +454,12 @@ CREATE TABLE `im_room` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89400339964931 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90505799118849 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room -- ---------------------------- -INSERT INTO `im_room` VALUES (1, 1, 1, '2025-10-28 16:45:02.174', 89400339964935, NULL, '2024-07-10 11:17:15.521', '2025-10-28 08:45:02.398', 1, 1, NULL, 0); +INSERT INTO `im_room` VALUES (1, 1, 1, '2025-10-31 17:41:26.004', 90501697089536, NULL, '2024-07-10 11:17:15.521', '2025-10-31 09:41:26.041', 1, 1, NULL, 0); -- ---------------------------- -- Table structure for im_room_friend @@ -482,7 +484,7 @@ CREATE TABLE `im_room_friend` ( INDEX `idx_room_id`(`room_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89400339964932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90505799118850 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room_friend @@ -511,7 +513,7 @@ CREATE TABLE `im_room_group` ( INDEX `idx_room_id`(`room_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89398838404099 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群聊房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90207575715331 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群聊房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room_group @@ -594,13 +596,13 @@ CREATE TABLE `im_user` ( INDEX `idx_update_time`(`update_time` ASC) USING BTREE, INDEX `idx_active_status_last_opt_time`(`last_opt_time` ASC) USING BTREE, INDEX `account_UNIQUE`(`account` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89400339964929 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90501692895233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user -- ---------------------------- INSERT INTO `im_user` VALUES (1, 61170828519936, 2, 'HuLa小管家', '022', '', 'bot', NULL, '', 0, '2025-07-07 15:27:01.711', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"120.231.232.41\", \"createIpDetail\": {\"ip\": \"206.237.119.215\", \"isp\": \"\", \"area\": \"\", \"city\": \"\", \"isp_id\": \"\", \"region\": \"\", \"city_id\": \"\", \"country\": \"美国\", \"region_id\": \"\", \"country_id\": \"US\"}, \"updateIpDetail\": {\"ip\": \"120.231.232.41\", \"isp\": \"移动\", \"area\": \"\", \"city\": \"\", \"isp_id\": \"100025\", \"region\": \"广东\", \"city_id\": \"\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', 6, 0, '', '2025-03-27 04:23:08.393', '2025-10-11 10:14:24.150', 'k.23772439646234', 0, NULL, 0, '2025-05-09 18:24:37.089', 99978, 0, 1); -INSERT INTO `im_user` VALUES (10937855681024, 61170828519937, 3, 'Dawn', 'https://cdn.hulaspark.com/avatar/2439646234/97320189485dca88dcc7a70054445a56.webp', '2439646234@qq.com', '2439646234', NULL, '', 15, '2025-07-30 15:31:57.651', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"183.15.179.234\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"183.15.179.234\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', 6, 0, '', '2025-03-27 04:23:08.393', '2025-10-28 16:08:55.831', 'k.2439646234', 0, NULL, 0, '2025-09-20 21:35:31.415', 99978, 0, 1); +INSERT INTO `im_user` VALUES (10937855681024, 61170828519937, 3, 'Dawn', 'https://cdn.hulaspark.com/avatar/2439646234/97320189485dca88dcc7a70054445a56.webp', '2439646234@qq.com', '2439646234', NULL, '', 15, '2025-07-30 15:31:57.651', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"183.15.179.234\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"183.15.179.234\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', 6, 0, '', '2025-03-27 04:23:08.393', '2025-10-31 14:03:50.763', 'k.2439646234', 0, NULL, 0, '2025-09-20 21:35:31.415', 99978, 0, 1); -- ---------------------------- -- Table structure for im_user_apply @@ -629,7 +631,7 @@ CREATE TABLE `im_user_apply` ( INDEX `idx_target_id`(`target_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89403246617601 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90516389736449 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_apply @@ -656,7 +658,7 @@ CREATE TABLE `im_user_backpack` ( INDEX `idx_uid`(`uid` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89400339964939 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90501697089540 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_backpack @@ -699,7 +701,7 @@ CREATE TABLE `im_user_emoji` ( `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', PRIMARY KEY (`id`) USING BTREE, INDEX `IDX_USER_EMOJIS_UID`(`uid` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 86588445005313 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90111324826625 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_emoji @@ -732,7 +734,7 @@ CREATE TABLE `im_user_friend` ( INDEX `idx_uid_friend_uid`(`uid` ASC, `friend_uid` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89403284366338 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90505799118852 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_friend @@ -910,7 +912,7 @@ CREATE TABLE `secure_invoke_record` ( `is_del` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_next_retry_time`(`next_retry_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89403284366340 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90516414902275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of secure_invoke_record @@ -929,7 +931,7 @@ CREATE TABLE `worker_node` ( `modified` timestamp NULL DEFAULT NULL COMMENT '修改时间', `created` timestamp NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 211 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 217 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatConversationController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatConversationController.java index d189b6a8..b01b0a37 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatConversationController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatConversationController.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationPageReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationRespVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationUpdateMyReqVO; @@ -40,7 +41,7 @@ public class AiChatConversationController { @PostMapping("/create-my") @Operation(summary = "创建【我的】聊天对话") - public R createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) { + public R createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) { return success(chatConversationService.createChatConversationMy(createReqVO, ContextUtil.getUid())); } @@ -71,9 +72,8 @@ public class AiChatConversationController { @DeleteMapping("/delete-my") @Operation(summary = "删除聊天对话") - @Parameter(name = "id", required = true, description = "对话编号", example = "1024") - public R deleteChatConversationMy(@RequestParam("id") Long id) { - chatConversationService.deleteChatConversationMy(id, ContextUtil.getUid()); + public R deleteChatConversationMy(@RequestBody AiDelReqVO reqVO) { + chatConversationService.deleteChatConversationMy(reqVO, ContextUtil.getUid()); return success(true); } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatMessageController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatMessageController.java index fd283c22..21394e0e 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatMessageController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/AiChatMessageController.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import com.luohuo.basic.context.ContextUtil; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessagePageReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageRespVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageSendReqVO; @@ -78,8 +79,7 @@ public class AiChatMessageController { @Operation(summary = "获得指定对话的消息列表") @GetMapping("/list-by-conversation-id") @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024") - public R> getChatMessageListByConversationId( - @RequestParam("conversationId") Long conversationId) { + public R> getChatMessageListByConversationId(@RequestParam("conversationId") Long conversationId) { AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId); if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), ContextUtil.getUid())) { return success(Collections.emptyList()); @@ -129,8 +129,8 @@ public class AiChatMessageController { @Operation(summary = "删除指定对话的消息") @DeleteMapping("/delete-by-conversation-id") @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024") - public R deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) { - chatMessageService.deleteChatMessageByConversationId(conversationId, ContextUtil.getUid()); + public R deleteChatMessageByConversationId(@RequestBody AiDelReqVO reqVOS) { + chatMessageService.deleteChatMessageByConversationId(reqVOS, ContextUtil.getUid()); return success(true); } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiDelReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiDelReqVO.java new file mode 100644 index 00000000..61d203d7 --- /dev/null +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiDelReqVO.java @@ -0,0 +1,15 @@ +package com.luohuo.flex.ai.controller.chat.vo.conversation; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "会话id") +@Data +public class AiDelReqVO { + + @Schema(description = "会话id", example = "1") + private List conversationIdList; + +} diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java index 7d71b2ae..68494d4f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java @@ -9,6 +9,7 @@ import com.luohuo.flex.ai.dal.model.AiApiKeyDO; import com.luohuo.flex.ai.service.model.AiApiKeyService; import com.luohuo.flex.ai.utils.BeanUtils; import com.luohuo.basic.base.R; +import com.luohuo.basic.context.ContextUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -35,13 +36,13 @@ public class AiApiKeyController { @PostMapping("/create") @Operation(summary = "创建 API 密钥") public R createApiKey(@Valid @RequestBody AiApiKeySaveReqVO createReqVO) { - return success(apiKeyService.createApiKey(createReqVO)); + return success(apiKeyService.createApiKey(createReqVO, ContextUtil.getUid())); } @PutMapping("/update") @Operation(summary = "更新 API 密钥") public R updateApiKey(@Valid @RequestBody AiApiKeySaveReqVO updateReqVO) { - apiKeyService.updateApiKey(updateReqVO); + apiKeyService.updateApiKey(updateReqVO, ContextUtil.getUid()); return success(true); } @@ -49,7 +50,7 @@ public class AiApiKeyController { @Operation(summary = "删除 API 密钥") @Parameter(name = "id", description = "编号", required = true) public R deleteApiKey(@RequestParam("id") Long id) { - apiKeyService.deleteApiKey(id); + apiKeyService.deleteApiKey(id, ContextUtil.getUid()); return success(true); } @@ -62,9 +63,9 @@ public class AiApiKeyController { } @GetMapping("/page") - @Operation(summary = "获得 API 密钥分页") + @Operation(summary = "获得 API 密钥分页(包含系统公开密钥和用户私有密钥)") public R> getApiKeyPage(@Valid AiApiKeyPageReqVO pageReqVO) { - PageResult pageResult = apiKeyService.getApiKeyPage(pageReqVO); + PageResult pageResult = apiKeyService.getApiKeyPage(pageReqVO, ContextUtil.getUid()); return success(BeanUtils.toBean(pageResult, AiApiKeyRespVO.class)); } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiChatRoleController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiChatRoleController.java index bd52cd45..234f964a 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiChatRoleController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiChatRoleController.java @@ -20,6 +20,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; import static com.luohuo.basic.base.R.success; @@ -33,63 +34,36 @@ public class AiChatRoleController { @Resource private AiChatRoleService chatRoleService; - @GetMapping("/my-page") - @Operation(summary = "获得【我的】聊天角色分页") - public R> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) { - PageResult pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, ContextUtil.getUid()); - return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class)); - } - - @GetMapping("/get-my") - @Operation(summary = "获得【我的】聊天角色") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public R getChatRoleMy(@RequestParam("id") Long id) { - AiChatRoleDO chatRole = chatRoleService.getChatRole(id); - if (ObjUtil.notEqual(chatRole.getUserId(), ContextUtil.getUid())) { - return success(null); - } - return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class)); - } - - @PostMapping("/create-my") - @Operation(summary = "创建【我的】聊天角色") - public R createChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO createReqVO) { - return success(chatRoleService.createChatRoleMy(createReqVO, ContextUtil.getUid())); - } - - @PutMapping("/update-my") - @Operation(summary = "更新【我的】聊天角色") - public R updateChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO updateReqVO) { - chatRoleService.updateChatRoleMy(updateReqVO, ContextUtil.getUid()); - return success(true); - } - - @DeleteMapping("/delete-my") - @Operation(summary = "删除【我的】聊天角色") - @Parameter(name = "id", description = "编号", required = true) - public R deleteChatRoleMy(@RequestParam("id") Long id) { - chatRoleService.deleteChatRoleMy(id, ContextUtil.getUid()); - return success(true); - } - - @GetMapping("/category-list") - @Operation(summary = "获得聊天角色的分类列表") - public R> getChatRoleCategoryList() { - return success(chatRoleService.getChatRoleCategoryList()); - } - - // ========== 角色管理 ========== - @PostMapping("/create") @Operation(summary = "创建聊天角色") public R createChatRole(@Valid @RequestBody AiChatRoleSaveReqVO createReqVO) { + if (createReqVO.getPublicStatus() == null || Boolean.FALSE.equals(createReqVO.getPublicStatus())) { + AiChatRoleSaveMyReqVO myReqVO = BeanUtils.toBean(createReqVO, AiChatRoleSaveMyReqVO.class); + return success(chatRoleService.createChatRoleMy(myReqVO, ContextUtil.getUid())); + } + // 管理员创建公开角色 return success(chatRoleService.createChatRole(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新聊天角色") public R updateChatRole(@Valid @RequestBody AiChatRoleSaveReqVO updateReqVO) { - chatRoleService.updateChatRole(updateReqVO); + AiChatRoleDO chatRole = chatRoleService.getChatRole(updateReqVO.getId()); + if (chatRole == null) { + return success(false); + } + + Long uid = ContextUtil.getUid(); + if (Boolean.FALSE.equals(chatRole.getPublicStatus())) { + if (ObjUtil.notEqual(chatRole.getUserId(), uid)) { + return success(false); // 无权限 + } + chatRoleService.updateChatRoleMy(updateReqVO, uid); + } else { + if (uid < 10937855681025L) { + chatRoleService.updateChatRole(updateReqVO); + } + } return success(true); } @@ -97,7 +71,22 @@ public class AiChatRoleController { @Operation(summary = "删除聊天角色") @Parameter(name = "id", description = "编号", required = true) public R deleteChatRole(@RequestParam("id") Long id) { - chatRoleService.deleteChatRole(id); + AiChatRoleDO chatRole = chatRoleService.getChatRole(id); + if (chatRole == null) { + return success(false); + } + + Long uid = ContextUtil.getUid(); + if (Boolean.FALSE.equals(chatRole.getPublicStatus())) { + if (ObjUtil.notEqual(chatRole.getUserId(), uid)) { + return success(false); // 无权限 + } + chatRoleService.deleteChatRoleMy(id, uid); + } else { + if (uid < 10937855681025L) { + chatRoleService.deleteChatRole(id); + } + } return success(true); } @@ -110,10 +99,16 @@ public class AiChatRoleController { } @GetMapping("/page") - @Operation(summary = "获得聊天角色分页") + @Operation(summary = "获得聊天角色分页(包含系统公开角色和用户私有角色)") public R> getChatRolePage(@Valid AiChatRolePageReqVO pageReqVO) { - PageResult pageResult = chatRoleService.getChatRolePage(pageReqVO); + PageResult pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, ContextUtil.getUid()); return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class)); } + @GetMapping("/category-list") + @Operation(summary = "获得聊天角色的分类列表") + public R>> getChatRoleCategoryList() { + return success(chatRoleService.getChatRoleCategoryList()); + } + } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiModelController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiModelController.java index c446d660..b0ba178f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiModelController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiModelController.java @@ -1,15 +1,17 @@ package com.luohuo.flex.ai.controller.model; - +import cn.hutool.core.util.ObjUtil; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.model.vo.model.AiModelPageReqVO; import com.luohuo.flex.ai.controller.model.vo.model.AiModelRespVO; +import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveMyReqVO; import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveReqVO; import com.luohuo.flex.ai.dal.model.AiModelDO; import com.luohuo.flex.ai.enums.CommonStatusEnum; import com.luohuo.flex.ai.service.model.AiModelService; import com.luohuo.flex.ai.utils.BeanUtils; import com.luohuo.basic.base.R; +import com.luohuo.basic.context.ContextUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -23,63 +25,94 @@ import java.util.List; import static com.luohuo.basic.base.R.success; import static com.luohuo.basic.utils.collection.CollectionUtils.convertList; - @Tag(name = "管理后台 - AI 模型") @RestController @RequestMapping("/model") @Validated public class AiModelController { - @Resource - private AiModelService modelService; + @Resource + private AiModelService modelService; - @PostMapping("/create") - @Operation(summary = "创建模型") - public R createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) { - return success(modelService.createModel(createReqVO)); - } + @PostMapping("/create") + @Operation(summary = "创建模型") + public R createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) { + if (createReqVO.getPublicStatus() == null || !createReqVO.getPublicStatus()) { + AiModelSaveMyReqVO myReqVO = BeanUtils.toBean(createReqVO, AiModelSaveMyReqVO.class); + return success(modelService.createModelMy(myReqVO, ContextUtil.getUid())); + } + return success(modelService.createModel(createReqVO)); + } - @PutMapping("/update") - @Operation(summary = "更新模型") - public R updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) { - modelService.updateModel(updateReqVO); - return success(true); - } + @PutMapping("/update") + @Operation(summary = "更新模型") + public R updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) { + AiModelDO model = modelService.getModel(updateReqVO.getId()); + if (model == null) { + return success(false); + } - @DeleteMapping("/delete") - @Operation(summary = "删除模型") - @Parameter(name = "id", description = "编号", required = true) - public R deleteModel(@RequestParam("id") Long id) { - modelService.deleteModel(id); - return success(true); - } + Long uid = ContextUtil.getUid(); + if (Boolean.FALSE.equals(model.getPublicStatus())) { + if (ObjUtil.notEqual(model.getUserId(), uid)) { + return success(false); // 无权限 + } + AiModelSaveMyReqVO myReqVO = BeanUtils.toBean(updateReqVO, AiModelSaveMyReqVO.class); + modelService.updateModelMy(myReqVO, ContextUtil.getUid()); + } else { + if (uid < 10937855681025L){ + modelService.updateModel(updateReqVO); + } + } + return success(true); + } - @GetMapping("/get") - @Operation(summary = "获得模型") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public R getModel(@RequestParam("id") Long id) { - AiModelDO model = modelService.getModel(id); - return success(BeanUtils.toBean(model, AiModelRespVO.class)); - } + @DeleteMapping("/delete") + @Operation(summary = "删除模型") + @Parameter(name = "id", description = "编号", required = true) + public R deleteModel(@RequestParam("id") Long id) { + AiModelDO model = modelService.getModel(id); + if (model == null) { + return success(false); + } - @GetMapping("/page") - @Operation(summary = "获得模型分页") - public R> getModelPage(@Valid AiModelPageReqVO pageReqVO) { - PageResult pageResult = modelService.getModelPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiModelRespVO.class)); - } + Long uid = ContextUtil.getUid(); + if (Boolean.FALSE.equals(model.getPublicStatus())) { + if (ObjUtil.notEqual(model.getUserId(), uid)) { + return success(false); // 无权限 + } + modelService.deleteModelMy(id, ContextUtil.getUid()); + } else { + if (uid < 10937855681025L){ + modelService.deleteModel(id); + } + } + return success(true); + } - @GetMapping("/simple-list") - @Operation(summary = "获得模型列表") - @Parameter(name = "type", description = "类型", required = true, example = "1") - @Parameter(name = "platform", description = "平台", example = "midjourney") - public R> getModelSimpleList( - @RequestParam("type") Integer type, - @RequestParam(value = "platform", required = false) String platform) { - List list = modelService.getModelListByStatusAndType( - CommonStatusEnum.ENABLE.getStatus(), type, platform); - return success(convertList(list, model -> new AiModelRespVO().setId(model.getId()) - .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform()))); - } + @GetMapping("/get") + @Operation(summary = "获得模型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public R getModel(@RequestParam("id") Long id) { + AiModelDO model = modelService.getModel(id); + return success(BeanUtils.toBean(model, AiModelRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得模型分页(包含系统公开模型和用户私有模型)") + public R> getModelPage(@Valid AiModelPageReqVO pageReqVO) { + PageResult pageResult = modelService.getModelMyPage(pageReqVO, ContextUtil.getUid()); + return success(BeanUtils.toBean(pageResult, AiModelRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得模型列表(包含系统公开模型和用户私有模型)") + @Parameter(name = "type", description = "类型", required = true, example = "1") + @Parameter(name = "platform", description = "平台", example = "midjourney") + public R> getModelSimpleList(@RequestParam("type") Integer type, @RequestParam(value = "platform", required = false) String platform) { + List list = modelService.getModelListByStatusAndTypeAndUserId(CommonStatusEnum.ENABLE.getStatus(), type, platform, ContextUtil.getUid()); + return success(convertList(list, model -> new AiModelRespVO().setId(model.getId()) + .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform()))); + } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyPageReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyPageReqVO.java index 3d161fce..19701418 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyPageReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyPageReqVO.java @@ -17,4 +17,7 @@ public class AiApiKeyPageReqVO extends PageParam { @Schema(description = "状态", example = "1") private Integer status; + @Schema(description = "是否公开", example = "true") + private Boolean publicStatus; + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyRespVO.java index b20f1fd3..d96d0e73 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyRespVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyRespVO.java @@ -25,4 +25,5 @@ public class AiApiKeyRespVO { @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer status; + private Boolean publicStatus; } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeySaveReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeySaveReqVO.java index 631c0756..9c826420 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeySaveReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeySaveReqVO.java @@ -31,4 +31,7 @@ public class AiApiKeySaveReqVO { @NotNull(message = "状态不能为空") private Integer status; + @Schema(description = "是否公开", example = "false") + private Boolean publicStatus; + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/chatRole/AiChatRoleSaveMyReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/chatRole/AiChatRoleSaveMyReqVO.java index fa2e53cf..960916b1 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/chatRole/AiChatRoleSaveMyReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/chatRole/AiChatRoleSaveMyReqVO.java @@ -18,6 +18,8 @@ public class AiChatRoleSaveMyReqVO { @NotEmpty(message = "角色名称不能为空") private String name; + private String category; + @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") @NotEmpty(message = "角色头像不能为空") @URL(message = "角色头像必须是 URL 格式") @@ -37,4 +39,6 @@ public class AiChatRoleSaveMyReqVO { @Schema(description = "引用的工具编号列表", example = "1,2,3") private List toolIds; + @Schema(description = "模型id", example = "1") + private Long modelId; } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelPageReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelPageReqVO.java index 04130781..5ba84d24 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelPageReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelPageReqVO.java @@ -17,4 +17,7 @@ public class AiModelPageReqVO extends PageParam { @Schema(description = "模型平台", example = "OpenAI") private String platform; + @Schema(description = "是否公开", example = "true") + private Boolean publicStatus; + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java index 1d2ed48c..ecb1fcc0 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java @@ -47,4 +47,7 @@ public class AiModelRespVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; + @Schema(description = "是否公开", example = "false") + private Boolean publicStatus; + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveMyReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveMyReqVO.java new file mode 100644 index 00000000..76faf44c --- /dev/null +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveMyReqVO.java @@ -0,0 +1,48 @@ +package com.luohuo.flex.ai.controller.model.vo.model; + + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - API 模型新增/修改【我的】 Request VO") +@Data +public class AiModelSaveMyReqVO { + + @Schema(description = "编号", example = "2630") + private Long id; + + @Schema(description = "API 秘钥编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22042") + @NotNull(message = "API 秘钥编号不能为空") + private Long keyId; + + @Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotEmpty(message = "模型名字不能为空") + private String name; + + @Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125") + @NotEmpty(message = "模型标识不能为空") + private String model; + + @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") + @NotEmpty(message = "模型平台不能为空") + private String platform; + + @Schema(description = "模型类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "模型类型不能为空") + private Integer type; + + @Schema(description = "排序", example = "1") + private Integer sort; + + @Schema(description = "温度参数", example = "1") + private Double temperature; + + @Schema(description = "单条回复的最大 Token 数量", example = "4096") + private Integer maxTokens; + + @Schema(description = "上下文的最大 Message 数量", example = "8192") + private Integer maxContexts; + +} diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java index 0cfd3ba9..cdc42d5d 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java @@ -50,4 +50,7 @@ public class AiModelSaveReqVO { @Schema(description = "上下文的最大 Message 数量", example = "8192") private Integer maxContexts; + @Schema(description = "是否公开", example = "false") + private Boolean publicStatus; + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/package-info.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/package-info.java deleted file mode 100644 index f39611f1..00000000 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.luohuo.flex.ai.controller; \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiApiKeyDO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiApiKeyDO.java index b2b1ebf7..05a6314d 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiApiKeyDO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiApiKeyDO.java @@ -8,6 +8,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; /** * AI API 秘钥 DO @@ -19,6 +20,7 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@Accessors(chain = true) public class AiApiKeyDO extends BaseDO { /** @@ -26,6 +28,13 @@ public class AiApiKeyDO extends BaseDO { */ @TableId private Long id; + + /** + * 用户编号 + * + * 关联 AdminUserDO 的 userId 字段 + */ + private Long userId; /** * 名称 */ @@ -51,4 +60,12 @@ public class AiApiKeyDO extends BaseDO { */ private Integer status; + /** + * 是否公开 + * + * 1. true - 公开;由管理员在【API 秘钥管理】所创建 + * 2. false - 私有;由个人在【我的 API 秘钥】所创建 + */ + private Boolean publicStatus; + } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java index 56912ae1..a94f9f8e 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java @@ -11,6 +11,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; /** * AI 模型 DO @@ -23,6 +24,7 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@Accessors(chain = true) public class AiModelDO extends BaseDO { /** @@ -30,6 +32,14 @@ public class AiModelDO extends BaseDO { */ @TableId private Long id; + + /** + * 用户编号 + * + * 关联 AdminUserDO 的 userId 字段 + */ + private Long userId; + /** * API 秘钥编号 * @@ -68,6 +78,14 @@ public class AiModelDO extends BaseDO { */ private Integer status; + /** + * 是否公开 + * + * 1. true - 公开;系统级别模型,所有用户可见 + * 2. false - 私有;用户专属模型,仅创建者可见 + */ + private Boolean publicStatus; + // ========== 对话配置 ========== /** diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiApiKeyMapper.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiApiKeyMapper.java index 80ce1c9d..78a0b00a 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiApiKeyMapper.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiApiKeyMapper.java @@ -16,20 +16,48 @@ import org.springframework.stereotype.Repository; @Repository public interface AiApiKeyMapper extends BaseMapperX { - default PageResult selectPage(AiApiKeyPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiApiKeyDO::getName, reqVO.getName()) - .eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform()) - .eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus()) - .orderByDesc(AiApiKeyDO::getId)); - } + default PageResult selectPage(AiApiKeyPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiApiKeyDO::getName, reqVO.getName()) + .eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform()) + .eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus()) + .eqIfPresent(AiApiKeyDO::getPublicStatus, reqVO.getPublicStatus()) + .orderByDesc(AiApiKeyDO::getId)); + } - default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) { - return selectOne(new QueryWrapperX() - .eq("platform", platform) - .eq("status", status) - .limitN(1) - .orderByAsc("id")); - } + default PageResult selectPageByMy(AiApiKeyPageReqVO reqVO, Long userId) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .likeIfPresent(AiApiKeyDO::getName, reqVO.getName()) + .eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform()) + .eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus()); + + // 根据 publicStatus 参数决定查询范围 + if (reqVO.getPublicStatus() == null) { + // 未指定:返回所有公开密钥 + 用户私有密钥 + wrapper.and(w -> w + .eq(AiApiKeyDO::getPublicStatus, true) + .or() + .eq(AiApiKeyDO::getUserId, userId) + ); + } else if (Boolean.TRUE.equals(reqVO.getPublicStatus())) { + // 只查询公开密钥 + wrapper.eq(AiApiKeyDO::getPublicStatus, true); + } else { + // 只查询用户私有密钥 + wrapper.eq(AiApiKeyDO::getUserId, userId) + .eq(AiApiKeyDO::getPublicStatus, false); + } + + wrapper.orderByDesc(AiApiKeyDO::getId); + return selectPage(reqVO, wrapper); + } + + default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) { + return selectOne(new QueryWrapperX() + .eq("platform", platform) + .eq("status", status) + .limitN(1) + .orderByAsc("id")); + } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatMapper.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatMapper.java index dff7e012..c136d088 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatMapper.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatMapper.java @@ -17,29 +17,74 @@ import java.util.List; @Repository public interface AiChatMapper extends BaseMapperX { - default AiModelDO selectFirstByStatus(Integer type, Integer status) { - return selectOne(new QueryWrapperX() - .eq("type", type) - .eq("status", status) - .limitN(1) - .orderByAsc("sort")); - } + default AiModelDO selectFirstByStatus(Integer type, Integer status) { + return selectOne(new QueryWrapperX() + .eq("type", type) + .eq("status", status) + .limitN(1) + .orderByAsc("sort")); + } - default PageResult selectPage(AiModelPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiModelDO::getName, reqVO.getName()) - .eqIfPresent(AiModelDO::getModel, reqVO.getModel()) - .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform()) - .orderByAsc(AiModelDO::getSort)); - } + default PageResult selectPage(AiModelPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiModelDO::getName, reqVO.getName()) + .eqIfPresent(AiModelDO::getModel, reqVO.getModel()) + .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform()) + .eqIfPresent(AiModelDO::getPublicStatus, reqVO.getPublicStatus()) + .orderByAsc(AiModelDO::getSort)); + } - default List selectListByStatusAndType(Integer status, Integer type, - @Nullable String platform) { - return selectList(new LambdaQueryWrapperX() - .eq(AiModelDO::getStatus, status) - .eq(AiModelDO::getType, type) - .eqIfPresent(AiModelDO::getPlatform, platform) - .orderByAsc(AiModelDO::getSort)); - } + default PageResult selectPageByMy(AiModelPageReqVO reqVO, Long userId) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .likeIfPresent(AiModelDO::getName, reqVO.getName()) + .eqIfPresent(AiModelDO::getModel, reqVO.getModel()) + .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform()); + + // 根据 publicStatus 参数决定查询范围 + if (reqVO.getPublicStatus() == null) { + // 未指定:返回所有公开模型 + 用户私有模型 + wrapper.and(w -> w + .eq(AiModelDO::getPublicStatus, true) + .or() + .eq(AiModelDO::getUserId, userId) + ); + } else if (Boolean.TRUE.equals(reqVO.getPublicStatus())) { + // 只查询公开模型 + wrapper.eq(AiModelDO::getPublicStatus, true); + } else { + // 只查询用户私有模型 + wrapper.eq(AiModelDO::getUserId, userId) + .eq(AiModelDO::getPublicStatus, false); + } + + wrapper.orderByAsc(AiModelDO::getSort); + return selectPage(reqVO, wrapper); + } + + default List selectListByStatusAndType(Integer status, Integer type, + @Nullable String platform) { + return selectList(new LambdaQueryWrapperX() + .eq(AiModelDO::getStatus, status) + .eq(AiModelDO::getType, type) + .eqIfPresent(AiModelDO::getPlatform, platform) + .orderByAsc(AiModelDO::getSort)); + } + + default List selectListByStatusAndTypeAndUserId(Integer status, Integer type, + @Nullable String platform, Long userId) { + return selectList(new LambdaQueryWrapperX() + .eq(AiModelDO::getStatus, status) + .eq(AiModelDO::getType, type) + .eqIfPresent(AiModelDO::getPlatform, platform) + .and(wrapper -> wrapper + // 公开模型 + .eq(AiModelDO::getPublicStatus, true) + // 或用户自己的私有模型 + .or() + .eq(AiModelDO::getUserId, userId) + .eq(AiModelDO::getPublicStatus, false) + ) + .orderByAsc(AiModelDO::getSort)); + } } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatRoleMapper.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatRoleMapper.java index 2737e026..f4809ab0 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatRoleMapper.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/mapper/model/AiChatRoleMapper.java @@ -17,37 +17,51 @@ import java.util.List; @Repository public interface AiChatRoleMapper extends BaseMapperX { - default PageResult selectPage(AiChatRolePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) - .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()) - .eqIfPresent(AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus()) - .orderByAsc(AiChatRoleDO::getSort)); - } + default PageResult selectPage(AiChatRolePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) + .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()) + .eqIfPresent(AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus()) + .orderByAsc(AiChatRoleDO::getSort)); + } - default PageResult selectPageByMy(AiChatRolePageReqVO reqVO, Long userId) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) - .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()) - // 情况一:公开 - .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus()) - // 情况二:私有 - .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getUserId, userId) - .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) - .orderByAsc(AiChatRoleDO::getSort)); - } + default PageResult selectPageByMy(AiChatRolePageReqVO reqVO, Long userId) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) + .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()); - default List selectListGroupByCategory(Integer status) { - return selectList(new LambdaQueryWrapperX() - .select(AiChatRoleDO::getCategory) - .eq(AiChatRoleDO::getStatus, status) - .groupBy(AiChatRoleDO::getCategory)); - } + // 根据 publicStatus 参数决定查询范围 + if (reqVO.getPublicStatus() == null) { + // 未指定:返回所有公开角色 + 用户私有角色 + wrapper.and(w -> w + .eq(AiChatRoleDO::getPublicStatus, true) + .or() + .eq(AiChatRoleDO::getUserId, userId) + ); + } else if (Boolean.TRUE.equals(reqVO.getPublicStatus())) { + // 只查询公开角色 + wrapper.eq(AiChatRoleDO::getPublicStatus, true); + } else { + // 只查询用户私有角色 + wrapper.eq(AiChatRoleDO::getUserId, userId) + .eq(AiChatRoleDO::getPublicStatus, false); + } - default List selectListByName(String name) { - return selectList(new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, name) - .orderByAsc(AiChatRoleDO::getSort)); - } + wrapper.orderByAsc(AiChatRoleDO::getSort); + return selectPage(reqVO, wrapper); + } -} + default List selectListGroupByCategory(Integer status) { + return selectList(new LambdaQueryWrapperX() + .select(AiChatRoleDO::getCategory) + .eq(AiChatRoleDO::getStatus, status) + .groupBy(AiChatRoleDO::getCategory)); + } + + default List selectListByName(String name) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(AiChatRoleDO::getName, name) + .orderByAsc(AiChatRoleDO::getSort)); + } + +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationService.java index 09755b97..44dd063f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationService.java @@ -2,6 +2,7 @@ package com.luohuo.flex.ai.service.chat; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationPageReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import com.luohuo.flex.ai.dal.chat.AiChatConversationDO; @@ -21,7 +22,7 @@ public interface AiChatConversationService { * @param userId 用户编号 * @return 编号 */ - Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId); + AiChatConversationDO createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId); /** * 更新【我的】聊天对话 @@ -50,10 +51,10 @@ public interface AiChatConversationService { /** * 删除【我的】聊天对话 * - * @param id 编号 + * @param reqVO 编号 * @param userId 用户编号 */ - void deleteChatConversationMy(Long id, Long userId); + void deleteChatConversationMy(AiDelReqVO reqVO, Long userId); /** * 【管理员】删除聊天对话 diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java index 24f52004..5911b8d4 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationPageReqVO; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import com.luohuo.flex.ai.dal.chat.AiChatConversationDO; @@ -53,7 +54,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService private AiKnowledgeService knowledgeService; @Override - public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { + public AiChatConversationDO createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { // 1.1 获得 AiChatRoleDO 聊天角色 AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null; // 1.2 获得 AiModelDO 聊天模型 @@ -77,7 +78,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService conversation.setTitle(AiChatConversationDO.TITLE_DEFAULT); } chatConversationMapper.insert(conversation); - return conversation.getId(); + return conversation; } @Override @@ -120,14 +121,17 @@ public class AiChatConversationServiceImpl implements AiChatConversationService } @Override - public void deleteChatConversationMy(Long id, Long userId) { + public void deleteChatConversationMy(AiDelReqVO reqVO, Long userId) { // 1. 校验对话是否存在 - AiChatConversationDO conversation = validateChatConversationExists(id); - if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } + reqVO.getConversationIdList().forEach(id -> { + AiChatConversationDO conversation = validateChatConversationExists(id); + if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), userId)) { + throw exception(CHAT_CONVERSATION_NOT_EXISTS); + } + }); + // 2. 执行删除 - chatConversationMapper.deleteById(id); + chatConversationMapper.deleteByIds(reqVO.getConversationIdList()); } @Override diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageService.java index d8f669c8..879ab8e1 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageService.java @@ -2,6 +2,7 @@ package com.luohuo.flex.ai.service.chat; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessagePageReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageSendReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageSendRespVO; @@ -57,10 +58,10 @@ public interface AiChatMessageService { /** * 删除指定对话的消息 * - * @param conversationId 对话编号 + * @param reqVOS 对话编号 * @param userId 用户编号 */ - void deleteChatMessageByConversationId(Long conversationId, Long userId); + void deleteChatMessageByConversationId(AiDelReqVO reqVOS, Long userId); /** * 【管理员】删除消息 diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java index 1a2517d8..c2025f6f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil; import com.luohuo.basic.context.ContextUtil; import com.luohuo.basic.tenant.core.aop.TenantIgnore; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessagePageReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageRespVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageSendReqVO; @@ -341,14 +342,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { } @Override - public void deleteChatMessageByConversationId(Long conversationId, Long userId) { - // 1. 校验消息存在 - List messages = chatMessageMapper.selectListByConversationId(conversationId); - if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { - throw exception(CHAT_MESSAGE_NOT_EXIST); - } - // 2. 执行删除 - chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); + public void deleteChatMessageByConversationId(AiDelReqVO reqVOS, Long userId) { + reqVOS.getConversationIdList().forEach(conversationId -> { + // 1. 校验消息存在 + List messages = chatMessageMapper.selectListByConversationId(conversationId); + if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { + throw exception(CHAT_MESSAGE_NOT_EXIST); + } + // 2. 执行删除 + chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); + }); } @Override diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java index 09bc8624..79ae095e 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java @@ -18,23 +18,26 @@ public interface AiApiKeyService { * 创建 API 密钥 * * @param createReqVO 创建信息 + * @param userId 用户编号 * @return 编号 */ - Long createApiKey(@Valid AiApiKeySaveReqVO createReqVO); + Long createApiKey(@Valid AiApiKeySaveReqVO createReqVO, Long userId); /** * 更新 API 密钥 * * @param updateReqVO 更新信息 + * @param userId 用户编号 */ - void updateApiKey(@Valid AiApiKeySaveReqVO updateReqVO); + void updateApiKey(@Valid AiApiKeySaveReqVO updateReqVO, Long userId); /** * 删除 API 密钥 * - * @param id 编号 + * @param id 编号 + * @param userId 用户编号 */ - void deleteApiKey(Long id); + void deleteApiKey(Long id, Long userId); /** * 获得 API 密钥 @@ -56,9 +59,10 @@ public interface AiApiKeyService { * 获得 API 密钥分页 * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return API 密钥分页 */ - PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO); + PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO, Long userId); /** * 获得 API 密钥列表 diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java index 80c778d5..40b85ff0 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java @@ -1,5 +1,6 @@ package com.luohuo.flex.ai.service.model; +import cn.hutool.core.util.ObjectUtil; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyPageReqVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeySaveReqVO; @@ -17,7 +18,6 @@ import static com.luohuo.flex.ai.enums.ErrorCodeConstants.API_KEY_DISABLE; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.API_KEY_NOT_EXISTS; import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; - /** * AI API 密钥 Service 实现类 * @@ -27,74 +27,112 @@ import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; @Validated public class AiApiKeyServiceImpl implements AiApiKeyService { - @Resource - private AiApiKeyMapper apiKeyMapper; + @Resource + private AiApiKeyMapper apiKeyMapper; - @Override - public Long createApiKey(AiApiKeySaveReqVO createReqVO) { - // 插入 - AiApiKeyDO apiKey = BeanUtils.toBean(createReqVO, AiApiKeyDO.class); - apiKeyMapper.insert(apiKey); - // 返回 - return apiKey.getId(); - } + @Override + public Long createApiKey(AiApiKeySaveReqVO createReqVO, Long userId) { + // 插入 + AiApiKeyDO apiKey = BeanUtils.toBean(createReqVO, AiApiKeyDO.class); - @Override - public void updateApiKey(AiApiKeySaveReqVO updateReqVO) { - // 校验存在 - validateApiKeyExists(updateReqVO.getId()); - // 更新 - AiApiKeyDO updateObj = BeanUtils.toBean(updateReqVO, AiApiKeyDO.class); - apiKeyMapper.updateById(updateObj); - } + // 如果是私有密钥,设置 userId + if (createReqVO.getPublicStatus() == null || Boolean.FALSE.equals(createReqVO.getPublicStatus())) { + apiKey.setUserId(userId) + .setPublicStatus(false); + // 私有密钥默认启用 + if (createReqVO.getStatus() == null) { + apiKey.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + } - @Override - public void deleteApiKey(Long id) { - // 校验存在 - validateApiKeyExists(id); - // 删除 - apiKeyMapper.deleteById(id); - } + apiKeyMapper.insert(apiKey); + return apiKey.getId(); + } - private AiApiKeyDO validateApiKeyExists(Long id) { - AiApiKeyDO apiKey = apiKeyMapper.selectById(id); - if (apiKey == null) { - throw exception(API_KEY_NOT_EXISTS); - } - return apiKey; - } + @Override + public void updateApiKey(AiApiKeySaveReqVO updateReqVO, Long userId) { + // 校验存在 + AiApiKeyDO apiKey = validateApiKeyExists(updateReqVO.getId()); - @Override - public AiApiKeyDO getApiKey(Long id) { - return apiKeyMapper.selectById(id); - } + // 权限校验 + if (Boolean.FALSE.equals(apiKey.getPublicStatus())) { + // 私有密钥,只有创建者可以编辑 + if (ObjectUtil.notEqual(apiKey.getUserId(), userId)) { + throw exception(API_KEY_NOT_EXISTS); + } + } else { + // 公开密钥,需要管理员权限 + if (userId >= 10937855681025L) { + throw exception(API_KEY_NOT_EXISTS); + } + } - @Override - public AiApiKeyDO validateApiKey(Long id) { - AiApiKeyDO apiKey = validateApiKeyExists(id); - if (CommonStatusEnum.isDisable(apiKey.getStatus())) { - throw exception(API_KEY_DISABLE); - } - return apiKey; - } + // 更新 + AiApiKeyDO updateObj = BeanUtils.toBean(updateReqVO, AiApiKeyDO.class); + apiKeyMapper.updateById(updateObj); + } - @Override - public PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO) { - return apiKeyMapper.selectPage(pageReqVO); - } + @Override + public void deleteApiKey(Long id, Long userId) { + // 校验存在 + AiApiKeyDO apiKey = validateApiKeyExists(id); - @Override - public List getApiKeyList() { - return apiKeyMapper.selectList(); - } + // 权限校验 + if (Boolean.FALSE.equals(apiKey.getPublicStatus())) { + // 私有密钥,只有创建者可以删除 + if (ObjectUtil.notEqual(apiKey.getUserId(), userId)) { + throw exception(API_KEY_NOT_EXISTS); + } + } else { + // 公开密钥,需要管理员权限 + if (userId >= 10937855681025L) { + throw exception(API_KEY_NOT_EXISTS); + } + } - @Override - public AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status) { - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform, status); - if (apiKey == null) { - throw exception(API_KEY_NOT_EXISTS); - } - return apiKey; - } + // 删除 + apiKeyMapper.deleteById(id); + } + + private AiApiKeyDO validateApiKeyExists(Long id) { + AiApiKeyDO apiKey = apiKeyMapper.selectById(id); + if (apiKey == null) { + throw exception(API_KEY_NOT_EXISTS); + } + return apiKey; + } + + @Override + public AiApiKeyDO getApiKey(Long id) { + return apiKeyMapper.selectById(id); + } + + @Override + public AiApiKeyDO validateApiKey(Long id) { + AiApiKeyDO apiKey = validateApiKeyExists(id); + if (CommonStatusEnum.isDisable(apiKey.getStatus())) { + throw exception(API_KEY_DISABLE); + } + return apiKey; + } + + @Override + public PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO, Long userId) { + return apiKeyMapper.selectPageByMy(pageReqVO, userId); + } + + @Override + public List getApiKeyList() { + return apiKeyMapper.selectList(); + } + + @Override + public AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status) { + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform, status); + if (apiKey == null) { + throw exception(API_KEY_NOT_EXISTS); + } + return apiKey; + } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleService.java index d8f03ce7..98b7836c 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleService.java @@ -51,7 +51,7 @@ public interface AiChatRoleService { * @param updateReqVO 更新信息 * @param userId 用户编号 */ - void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId); + void updateChatRoleMy(AiChatRoleSaveReqVO updateReqVO, Long userId); /** * 删除聊天角色 @@ -117,7 +117,7 @@ public interface AiChatRoleService { * * @return 分类列表 */ - List getChatRoleCategoryList(); + List> getChatRoleCategoryList(); /** * 根据名字获得聊天角色 diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleServiceImpl.java index 1c0b41bc..3b91339f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiChatRoleServiceImpl.java @@ -16,14 +16,16 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.CHAT_ROLE_DISABLE; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.CHAT_ROLE_NOT_EXISTS; import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; -import static com.luohuo.basic.utils.collection.CollectionUtils.convertList; /** @@ -63,8 +65,7 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { validateTools(createReqVO.getToolIds()); // 保存角色 - AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId) - .setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false); + AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false); chatRoleMapper.insert(chatRole); return chatRole.getId(); } @@ -84,7 +85,7 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { } @Override - public void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId) { + public void updateChatRoleMy(AiChatRoleSaveReqVO updateReqVO, Long userId) { // 校验存在 AiChatRoleDO chatRole = validateChatRoleExists(updateReqVO.getId()); if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) { @@ -185,12 +186,30 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { return chatRoleMapper.selectPageByMy(pageReqVO, userId); } - @Override - public List getChatRoleCategoryList() { - List list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus()); - return convertList(list, AiChatRoleDO::getCategory, - role -> role != null && StrUtil.isNotBlank(role.getCategory())); - } + @Override + public List> getChatRoleCategoryList() { + return CATEGORY_OPTIONS; + } + + public static final List> CATEGORY_OPTIONS = Arrays.asList( + createOption("AI助手", "AI助手"), + createOption("写作", "写作"), + createOption("编程开发", "编程开发"), + createOption("学习教育", "学习教育"), + createOption("生活娱乐", "生活娱乐"), + createOption("商务办公", "商务办公"), + createOption("创意设计", "创意设计"), + createOption("数据分析", "数据分析"), + createOption("翻译", "翻译"), + createOption("其他", "其他") + ); + + private static Map createOption(String label, String value) { + Map option = new HashMap<>(); + option.put("label", label); + option.put("value", value); + return option; + } @Override public List getChatRoleListByName(String name) { diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelService.java index bf66a427..5fea39ef 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelService.java @@ -3,6 +3,7 @@ package com.luohuo.flex.ai.service.model; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.model.vo.model.AiModelPageReqVO; +import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveMyReqVO; import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveReqVO; import com.luohuo.flex.ai.core.model.MidjourneyApi; import com.luohuo.flex.ai.core.model.SunoApi; @@ -34,6 +35,15 @@ public interface AiModelService { */ Long createModel(@Valid AiModelSaveReqVO createReqVO); + /** + * 创建【我的】模型 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createModelMy(@Valid AiModelSaveMyReqVO createReqVO, Long userId); + /** * 更新模型 * @@ -41,6 +51,14 @@ public interface AiModelService { */ void updateModel(@Valid AiModelSaveReqVO updateReqVO); + /** + * 更新【我的】模型 + * + * @param updateReqVO 更新信息 + * @param userId 用户编号 + */ + void updateModelMy(@Valid AiModelSaveMyReqVO updateReqVO, Long userId); + /** * 删除模型 * @@ -48,6 +66,14 @@ public interface AiModelService { */ void deleteModel(Long id); + /** + * 删除【我的】模型 + * + * @param id 编号 + * @param userId 用户编号 + */ + void deleteModelMy(Long id, Long userId); + /** * 获得模型 * @@ -73,6 +99,15 @@ public interface AiModelService { */ PageResult getModelPage(AiModelPageReqVO pageReqVO); + /** + * 获得【我的】模型分页 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 模型分页 + */ + PageResult getModelMyPage(AiModelPageReqVO pageReqVO, Long userId); + /** * 校验模型是否可使用 * @@ -92,6 +127,20 @@ public interface AiModelService { List getModelListByStatusAndType(Integer status, Integer type, @Nullable String platform); + /** + * 获得模型列表(用户可见) + * + * 包含:系统级别的公开模型 + 用户自己的私有模型 + * + * @param status 状态 + * @param type 类型 + * @param platform 平台,允许空 + * @param userId 用户编号 + * @return 模型列表 + */ + List getModelListByStatusAndTypeAndUserId(Integer status, Integer type, + @Nullable String platform, Long userId); + // ========== 与 Spring AI 集成 ========== /** diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelServiceImpl.java index 59f886bf..7d4ea016 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiModelServiceImpl.java @@ -1,12 +1,13 @@ package com.luohuo.flex.ai.service.model; - import com.agentsflex.llm.ollama.OllamaLlm; import com.agentsflex.llm.ollama.OllamaLlmConfig; import com.agentsflex.llm.qwen.QwenLlm; import com.agentsflex.llm.qwen.QwenLlmConfig; +import cn.hutool.core.util.ObjectUtil; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.model.vo.model.AiModelPageReqVO; +import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveMyReqVO; import com.luohuo.flex.ai.controller.model.vo.model.AiModelSaveReqVO; import com.luohuo.flex.ai.core.AiModelFactory; import com.luohuo.flex.ai.core.model.MidjourneyApi; @@ -43,161 +44,233 @@ import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; @Validated public class AiModelServiceImpl implements AiModelService { - @Resource - private AiApiKeyService apiKeyService; + @Resource + private AiApiKeyService apiKeyService; - @Resource - private AiChatMapper modelMapper; + @Resource + private AiChatMapper modelMapper; - @Resource - private AiModelFactory modelFactory; + @Resource + private AiModelFactory modelFactory; - @Override - public Long createModel(AiModelSaveReqVO createReqVO) { - // 1. 校验 - AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); - apiKeyService.validateApiKey(createReqVO.getKeyId()); + @Override + public Long createModel(AiModelSaveReqVO createReqVO) { + // 1. 校验 + AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); + apiKeyService.validateApiKey(createReqVO.getKeyId()); - // 2. 插入 - AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); - modelMapper.insert(model); - return model.getId(); - } + // 2. 插入 + AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); + // 如果是私有模型且没有设置 userId,则抛出异常 + if (Boolean.FALSE.equals(createReqVO.getPublicStatus()) && model.getUserId() == null) { + throw exception(MODEL_NOT_EXISTS); // 使用适当的错误码 + } + modelMapper.insert(model); + return model.getId(); + } - @Override - public void updateModel(AiModelSaveReqVO updateReqVO) { - // 1. 校验 - validateModelExists(updateReqVO.getId()); - AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); - apiKeyService.validateApiKey(updateReqVO.getKeyId()); + @Override + public Long createModelMy(AiModelSaveMyReqVO createReqVO, Long userId) { + // 1. 校验 + AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); + apiKeyService.validateApiKey(createReqVO.getKeyId()); - // 2. 更新 - AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); - modelMapper.updateById(updateObj); - } + // 2. 插入 + AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class) + .setUserId(userId) + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setPublicStatus(false); + modelMapper.insert(model); + return model.getId(); + } - @Override - public void deleteModel(Long id) { - // 校验存在 - validateModelExists(id); - // 删除 - modelMapper.deleteById(id); - } + @Override + public void updateModel(AiModelSaveReqVO updateReqVO) { + // 1. 校验存在 + AiModelDO model = validateModelExists(updateReqVO.getId()); - private AiModelDO validateModelExists(Long id) { - AiModelDO model = modelMapper.selectById(id); - if (modelMapper.selectById(id) == null) { - throw exception(MODEL_NOT_EXISTS); - } - return model; - } + // 2. 权限校验:公开模型不允许普通用户编辑(管理员可以通过其他方式编辑) + // 如果 updateReqVO 中指定了 userId,说明是普通用户操作 + if (updateReqVO.getId() != null) { + // 如果是私有模型,校验是否是创建者 + if (Boolean.FALSE.equals(model.getPublicStatus())) { + // 私有模型需要校验 userId + if (updateReqVO instanceof AiModelSaveReqVO && model.getUserId() != null) { + // 这里需要从上下文获取当前用户ID进行校验 + // 暂时保持原有逻辑,具体权限在 Controller 层控制 + } + } + } - @Override - public AiModelDO getModel(Long id) { - return modelMapper.selectById(id); - } + // 3. 校验平台和密钥 + AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); + apiKeyService.validateApiKey(updateReqVO.getKeyId()); - @Override - public AiModelDO getRequiredDefaultModel(Integer type) { - AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus()); - if (model == null) { - throw exception(MODEL_DEFAULT_NOT_EXISTS); - } - return model; - } + // 4. 更新 + AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); + modelMapper.updateById(updateObj); + } - @Override - public PageResult getModelPage(AiModelPageReqVO pageReqVO) { - return modelMapper.selectPage(pageReqVO); - } + @Override + public void updateModelMy(AiModelSaveMyReqVO updateReqVO, Long userId) { + // 1. 校验存在 + AiModelDO model = validateModelExists(updateReqVO.getId()); + if (ObjectUtil.notEqual(model.getUserId(), userId)) { + throw exception(MODEL_NOT_EXISTS); + } + // 2. 校验平台和密钥 + AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); + apiKeyService.validateApiKey(updateReqVO.getKeyId()); - @Override - public AiModelDO validateModel(Long id) { - AiModelDO model = validateModelExists(id); - if (CommonStatusEnum.isDisable(model.getStatus())) { - throw exception(MODEL_DISABLE); - } - return model; - } + // 3. 更新 + AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); + updateObj.setUserId(userId); + modelMapper.updateById(updateObj); + } - @Override - public List getModelListByStatusAndType(Integer status, Integer type, String platform) { - return modelMapper.selectListByStatusAndType(status, type, platform); - } + @Override + public void deleteModel(Long id) { + // 校验存在 + validateModelExists(id); + // 删除 + modelMapper.deleteById(id); + } - // ========== 与 Spring AI 集成 ========== + @Override + public void deleteModelMy(Long id, Long userId) { + // 校验存在 + AiModelDO model = validateModelExists(id); + if (ObjectUtil.notEqual(model.getUserId(), userId)) { + throw exception(MODEL_NOT_EXISTS); + } + // 删除 + modelMapper.deleteById(id); + } - @Override - public ChatModel getChatModel(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } + private AiModelDO validateModelExists(Long id) { + AiModelDO model = modelMapper.selectById(id); + if (modelMapper.selectById(id) == null) { + throw exception(MODEL_NOT_EXISTS); + } + return model; + } - @Override - public ImageModel getImageModel(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } + @Override + public AiModelDO getModel(Long id) { + return modelMapper.selectById(id); + } - @Override - public MidjourneyApi getMidjourneyApi(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); - } + @Override + public AiModelDO getRequiredDefaultModel(Integer type) { + AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus()); + if (model == null) { + throw exception(MODEL_DEFAULT_NOT_EXISTS); + } + return model; + } - @Override - public SunoApi getSunoApi() { - AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey( - AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); - return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); - } + @Override + public PageResult getModelPage(AiModelPageReqVO pageReqVO) { + return modelMapper.selectPage(pageReqVO); + } - @Override - public VectorStore getOrCreateVectorStore(Long id, Map> metadataFields) { - // 获取模型 + 密钥 - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + @Override + public PageResult getModelMyPage(AiModelPageReqVO pageReqVO, Long userId) { + return modelMapper.selectPageByMy(pageReqVO, userId); + } - // 创建或获取 EmbeddingModel 对象 - EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( - platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); + @Override + public AiModelDO validateModel(Long id) { + AiModelDO model = validateModelExists(id); + if (CommonStatusEnum.isDisable(model.getStatus())) { + throw exception(MODEL_DISABLE); + } + return model; + } - // 创建或获取 VectorStore 对象 - return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); + @Override + public List getModelListByStatusAndType(Integer status, Integer type, String platform) { + return modelMapper.selectListByStatusAndType(status, type, platform); + } + + @Override + public List getModelListByStatusAndTypeAndUserId(Integer status, Integer type, String platform, Long userId) { + return modelMapper.selectListByStatusAndTypeAndUserId(status, type, platform, userId); + } + + // ========== 与 Spring AI 集成 ========== + + @Override + public ChatModel getChatModel(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public ImageModel getImageModel(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public MidjourneyApi getMidjourneyApi(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public SunoApi getSunoApi() { + AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey( + AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); + return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public VectorStore getOrCreateVectorStore(Long id, Map> metadataFields) { + // 获取模型 + 密钥 + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + + // 创建或获取 EmbeddingModel 对象 + EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( + platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); + + // 创建或获取 VectorStore 对象 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); // return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields); // return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields); - } + } - // TODO @lesan:是不是返回 Llm 对象会好点哈? - @Override - public void getLLmProvider4Tinyflow(Tinyflow tinyflow, Long modelId) { - AiModelDO model = validateModel(modelId); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - switch (platform) { - // TODO @lesan 考虑到未来不需要使用agents-flex 现在仅测试通义千问 - // TODO @lesan:【重要】是不是可以实现一个 SpringAiLlm,这样的话,内部全部用它就好了。只实现 chat 部分;这样,就把 flex 作为一个 agent 框架,内部调用,还是 spring ai 相关的。成本可能低一点?! - case TONG_YI: - QwenLlmConfig qwenLlmConfig = new QwenLlmConfig(); - qwenLlmConfig.setApiKey(apiKey.getApiKey()); - qwenLlmConfig.setModel(model.getModel()); - // TODO @lesan:这个有点奇怪。。。如果一个链式里,有多个模型,咋整呀。。。 - tinyflow.setLlmProvider(id -> new QwenLlm(qwenLlmConfig)); - break; - case OLLAMA: - OllamaLlmConfig ollamaLlmConfig = new OllamaLlmConfig(); - ollamaLlmConfig.setEndpoint(apiKey.getUrl()); - ollamaLlmConfig.setModel(model.getModel()); - tinyflow.setLlmProvider(id -> new OllamaLlm(ollamaLlmConfig)); - break; - } - } + // TODO @lesan:是不是返回 Llm 对象会好点哈? + @Override + public void getLLmProvider4Tinyflow(Tinyflow tinyflow, Long modelId) { + AiModelDO model = validateModel(modelId); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + switch (platform) { + // TODO @lesan 考虑到未来不需要使用agents-flex 现在仅测试通义千问 + // TODO @lesan:【重要】是不是可以实现一个 SpringAiLlm,这样的话,内部全部用它就好了。只实现 chat 部分;这样,就把 flex 作为一个 agent 框架,内部调用,还是 spring ai 相关的。成本可能低一点?! + case TONG_YI: + QwenLlmConfig qwenLlmConfig = new QwenLlmConfig(); + qwenLlmConfig.setApiKey(apiKey.getApiKey()); + qwenLlmConfig.setModel(model.getModel()); + // TODO @lesan:这个有点奇怪。。。如果一个链式里,有多个模型,咋整呀。。。 + tinyflow.setLlmProvider(id -> new QwenLlm(qwenLlmConfig)); + break; + case OLLAMA: + OllamaLlmConfig ollamaLlmConfig = new OllamaLlmConfig(); + ollamaLlmConfig.setEndpoint(apiKey.getUrl()); + ollamaLlmConfig.setModel(model.getModel()); + tinyflow.setLlmProvider(id -> new OllamaLlm(ollamaLlmConfig)); + break; + } + } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/common/interceptor/BlackInterceptor.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/common/interceptor/BlackInterceptor.java index 21af8f61..eec51b8c 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/common/interceptor/BlackInterceptor.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/common/interceptor/BlackInterceptor.java @@ -31,15 +31,14 @@ public class BlackInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Map> blackMap = userSummaryCache.getBlackMap(); - Long uid = ContextUtil.getUid(); - if (isBlackList(uid, blackMap.get(BlackTypeEnum.UID.getType()))) { + if (isBlackList(ContextUtil.getUid(), blackMap.get(BlackTypeEnum.UID.getType()))) { response.setStatus(HttpStatus.OK.value()); R responseData = R.fail("对不起,不在白名单内"); response.setContentType(ContentType.JSON.toString(Charset.forName("UTF-8"))); response.getWriter().write(JSONUtil.toJsonStr(responseData)); return false; } - if (isBlackList(uid, blackMap.get(BlackTypeEnum.IP.getType()))) { + if (isBlackList(ContextUtil.getIP(), blackMap.get(BlackTypeEnum.IP.getType()))) { response.setStatus(HttpStatus.OK.value()); R responseData = R.fail("对不起,不在白名单内"); response.setContentType(ContentType.JSON.toString(Charset.forName("UTF-8"))); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java index 394cb268..2af77275 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java @@ -509,10 +509,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean { public Boolean updateMyRoomInfo(Long uid, RoomMyInfoReq request) { // 1.校验修改权限 Triple permissionCheck = checkGroupPermission(uid, request.getId()); - if (!permissionCheck.getRight()) { - return false; - } - RoomGroup roomGroup = permissionCheck.getLeft(); GroupMember member = permissionCheck.getMiddle(); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java index 8160eaf1..195870be 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java @@ -9,14 +9,22 @@ import com.luohuo.flex.im.domain.vo.req.feed.FeedVo; import com.luohuo.flex.im.core.user.mapper.FeedMapper; import org.springframework.stereotype.Service; +import java.util.List; + /** * 朋友圈基础信息 */ @Service public class FeedDao extends ServiceImpl { - public CursorPageBaseResp getFeedPage(Long uid, CursorPageBaseReq request) { - return CursorUtils.getCursorPageByMysql(this, request, wrapper -> wrapper.eq(Feed::getUid, uid), Feed::getCreateTime); + /** + * 查询朋友圈列表(包括指定用户列表的朋友圈) + * @param uidList 用户ID列表(包括当前用户和好友) + * @param request 分页请求 + * @return 朋友圈分页结果 + */ + public CursorPageBaseResp getFeedPage(List uidList, CursorPageBaseReq request) { + return CursorUtils.getCursorPageByMysql(this, request, wrapper -> wrapper.in(Feed::getUid, uidList), Feed::getCreateTime); } public FeedVo getDetail(Long id) { diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/cache/UserSummaryCache.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/cache/UserSummaryCache.java index 88ec43c4..0cca82b3 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/cache/UserSummaryCache.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/cache/UserSummaryCache.java @@ -1,6 +1,7 @@ package com.luohuo.flex.im.core.user.service.cache; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.luohuo.basic.tenant.core.aop.TenantIgnore; import com.luohuo.flex.im.common.constant.RedisKey; import com.luohuo.flex.im.common.service.cache.AbstractRedisStringCache; import com.luohuo.flex.im.core.user.dao.BlackDao; @@ -112,6 +113,7 @@ public class UserSummaryCache extends AbstractRedisStringCache> getBlackMap() { LocalDateTime now = LocalDateTime.now(); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java index 983de264..9f5ee6c6 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java @@ -84,11 +84,46 @@ public class FeedServiceImpl implements FeedService { */ @Override public CursorPageBaseResp getFeedPage(CursorPageBaseReq request, Long uid) { - // 1. 查询朋友圈列表 - CursorPageBaseResp page = feedDao.getFeedPage(uid, request); + // 1. 查询当前用户的所有好友ID + List friendIds = userFriendDao.getAllFriendIdsByUid(uid); + friendIds.add(uid); // 添加自己 - // 2. 合并朋友圈内容 - List result = buildFeedResp(page.getList()); + // 2. 查询这些用户的朋友圈列表 + CursorPageBaseResp page = feedDao.getFeedPage(friendIds, request); + + // 3. 根据权限过滤朋友圈 + List filteredFeeds = page.getList().stream().filter(feed -> { + // 如果是自己的朋友圈,全部可见 + if (feed.getUid().equals(uid)) { + return true; + } + + // 根据权限类型过滤 + FeedPermissionEnum permission = FeedPermissionEnum.get(feed.getPermission()); + switch (permission) { + case privacy: + // 私密:只有发布者自己可见 + return false; + case open: + // 公开:所有好友可见 + return true; + case partVisible: + // 部分可见:查询是否在可见列表中 + List targets = feedTargetDao.selectFeedTargets(feed.getId()); + // type=2 表示用户,targetId 就是用户ID + return targets.stream().anyMatch(t -> t.getType() == 2 && t.getTargetId().equals(uid)); + case notAnyone: + // 不给谁看:查询是否在不可见列表中 + List excludes = feedTargetDao.selectFeedTargets(feed.getId()); + // type=2 表示用户,targetId 就是用户ID + return excludes.stream().noneMatch(t -> t.getType() == 2 && t.getTargetId().equals(uid)); + default: + return false; + } + }).collect(Collectors.toList()); + + // 4. 合并朋友圈内容 + List result = buildFeedResp(filteredFeeds); return CursorPageBaseResp.init(page, result, page.getTotal()); } @@ -117,6 +152,17 @@ public class FeedServiceImpl implements FeedService { * @param feed 朋友圈 */ public void saveFeed(FeedParam param, Long uid, Feed feed) { + saveFeed(param, uid, feed, false); + } + + /** + * 保存朋友圈权限+素材 + * @param param 参数 + * @param uid 操作人 + * @param feed 朋友圈 + * @param needClearCache 是否需要清除缓存(编辑时为true,新建时为false) + */ + private void saveFeed(FeedParam param, Long uid, Feed feed, boolean needClearCache) { List pushList = new ArrayList<>(); List feedTargets = new ArrayList<>(); switch (FeedPermissionEnum.get(param.getPermission())){ @@ -173,9 +219,13 @@ public class FeedServiceImpl implements FeedService { } } - // 3. 缓存权限+素材 告知 pushList 我发布了朋友圈 -// cachePlusOps.hDel(FeedMediaRelCacheKeyBuilder.build(feed.getId())); -// cachePlusOps.hDel(FeedTargetRelCacheKeyBuilder.build(feed.getId())); + // 3. 清除缓存 + if (needClearCache) { + cachePlusOps.del(FeedMediaRelCacheKeyBuilder.build(feed.getId())); + cachePlusOps.del(FeedTargetRelCacheKeyBuilder.build(feed.getId())); + } + + // 4. 告知 pushList 我发布了朋友圈 pushService.sendPushMsg(MemberAdapter.buildFeedPushWS(uid), pushList, uid); } @@ -230,8 +280,8 @@ public class FeedServiceImpl implements FeedService { feedDao.removeById(feedId); // 2. 清空缓存 - cachePlusOps.hDel(FeedTargetRelCacheKeyBuilder.build(feedId)); - cachePlusOps.hDel(FeedMediaRelCacheKeyBuilder.build(feedId)); + cachePlusOps.del(FeedTargetRelCacheKeyBuilder.build(feedId)); + cachePlusOps.del(FeedMediaRelCacheKeyBuilder.build(feedId)); return true; } @@ -247,12 +297,12 @@ public class FeedServiceImpl implements FeedService { public FeedVo feedDetail(Long feedId) { FeedVo feed = getDetail(feedId); - if(feed.getMediaType().equals(FeedEnum.WORD.getType())){ - CacheResult cacheResult = cachePlusOps.hGet(FeedMediaRelCacheKeyBuilder.build(feedId)); - List feedMediaList = cacheResult.asList(); - if(CollUtil.isEmpty(feedMediaList)){ - feedMediaList = feedMediaDao.getMediaByFeedId(feedId); - } + if(!feed.getMediaType().equals(FeedEnum.WORD.getType())){ + // 使用带回调的方式,自动处理缓存读写 + CacheHashKey hashKey = FeedMediaRelCacheKeyBuilder.build(feedId); + CacheResult> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feedId)); + List feedMediaList = result.getValue(); + if (CollUtil.isNotEmpty(feedMediaList)){ feed.setUrls(feedMediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList())); } @@ -313,8 +363,8 @@ public class FeedServiceImpl implements FeedService { feedTargetDao.delByFeedId(feed.getId()); feedMediaDao.delMediaByFeedId(feed.getId()); - // 3. 更新朋友圈的权限+素材 - saveFeed(param, uid, feed); + // 3. 更新朋友圈的权限+素材(编辑场景,需要清除缓存) + saveFeed(param, uid, feed, true); return true; } } diff --git a/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/granter/RefreshTokenGranter.java b/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/granter/RefreshTokenGranter.java index 4982fbbf..4d1e5ed9 100644 --- a/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/granter/RefreshTokenGranter.java +++ b/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/granter/RefreshTokenGranter.java @@ -18,18 +18,28 @@ package com.luohuo.flex.oauth.granter; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.session.SaTerminalInfo; import cn.dev33.satoken.stp.SaLoginModel; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.temp.SaTempUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import com.luohuo.basic.context.ContextUtil; import com.luohuo.basic.exception.TokenExceedException; +import com.luohuo.basic.utils.SpringUtils; import com.luohuo.flex.common.utils.ToolsUtil; +import com.luohuo.flex.model.entity.ws.OffLineResp; +import com.luohuo.flex.oauth.event.TokenExpireEvent; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import com.luohuo.flex.oauth.vo.result.LoginResultVO; +import java.util.ArrayList; +import java.util.List; + import static com.luohuo.basic.context.ContextConstants.*; /** @@ -66,8 +76,11 @@ public class RefreshTokenGranter { String systemType = obj.getStr(JWT_KEY_SYSTEM_TYPE); String device = obj.getStr(JWT_KEY_DEVICE); - // 2、为其生成新的短 token - StpUtil.login(userId, new SaLoginModel().setDevice(ToolsUtil.combineStrings(systemType, device))); + // 2、处理挤下线逻辑 + String combinedDeviceType = kickout(uid, userId, systemType, device); + + // 3、为其生成新的短 token + StpUtil.login(userId, new SaLoginModel().setDevice(combinedDeviceType)); SaSession tokenSession = StpUtil.getTokenSession(); tokenSession.setLoginId(userId); tokenSession.set(JWT_KEY_SYSTEM_TYPE, systemType); @@ -108,4 +121,50 @@ public class RefreshTokenGranter { return resultVO; } + /** + * 处理挤下线的逻辑(刷新 token 时) + * @param uid 登录id + * @param userId 用户基础信息ID + * @param systemType 系统类型 + * @param deviceType 登录设备 + * @return 组合后的设备类型 + */ + private String kickout(Long uid, Long userId, String systemType, String deviceType) { + // 1. 组合完整的设备类型标识 + String combinedDeviceType = ToolsUtil.combineStrings(systemType, deviceType); + + // 2. 检查是否已有相同组合设备的登录 + List sameDeviceTokens = new ArrayList<>(); + SaSession saSession = StpUtil.getSessionByLoginId(userId.toString(), false); + + if (ObjectUtil.isNotNull(saSession)) { + for (SaTerminalInfo terminal : saSession.getTerminalList()) { + String terminalDeviceType = terminal.getDeviceType(); + String tokenValue = terminal.getTokenValue(); + + // 3. 匹配设备类型 + if (combinedDeviceType.equals(terminalDeviceType)) { + sameDeviceTokens.add(tokenValue); + log.info("刷新token时发现同设备登录: token={}, 设备类型={}", tokenValue, terminalDeviceType); + } + } + } + + // 4. 处理已有登录 + if (CollUtil.isNotEmpty(sameDeviceTokens)) { + for (String token : sameDeviceTokens) { + try { + String clientId = StpUtil.getTokenSessionByToken(token).getString(CLIENT_ID); + StpUtil.kickout(token); + log.info("刷新token时已踢出旧会话: token={}", token); + + SpringUtils.publishEvent(new TokenExpireEvent(this, new OffLineResp(uid, deviceType, clientId, ContextUtil.getIP(), token))); + } catch (Exception e) { + log.error("刷新token时踢出旧会话失败: token={}", token, e); + } + } + } + return combinedDeviceType; + } + }