fix: 对接朋友圈、ai模块

This commit is contained in:
乾乾
2025-10-31 19:02:50 +08:00
committed by Dawn
parent 71f7471d86
commit a6faaf100e
38 changed files with 1023 additions and 553 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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<Long> createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) {
public R<AiChatConversationDO> 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<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationMy(id, ContextUtil.getUid());
public R<Boolean> deleteChatConversationMy(@RequestBody AiDelReqVO reqVO) {
chatConversationService.deleteChatConversationMy(reqVO, ContextUtil.getUid());
return success(true);
}

View File

@@ -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<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
@RequestParam("conversationId") Long conversationId) {
public R<List<AiChatMessageRespVO>> 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<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
chatMessageService.deleteChatMessageByConversationId(conversationId, ContextUtil.getUid());
public R<Boolean> deleteChatMessageByConversationId(@RequestBody AiDelReqVO reqVOS) {
chatMessageService.deleteChatMessageByConversationId(reqVOS, ContextUtil.getUid());
return success(true);
}

View File

@@ -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<Long> conversationIdList;
}

View File

@@ -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<Long> createApiKey(@Valid @RequestBody AiApiKeySaveReqVO createReqVO) {
return success(apiKeyService.createApiKey(createReqVO));
return success(apiKeyService.createApiKey(createReqVO, ContextUtil.getUid()));
}
@PutMapping("/update")
@Operation(summary = "更新 API 密钥")
public R<Boolean> 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<Boolean> 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<PageResult<AiApiKeyRespVO>> getApiKeyPage(@Valid AiApiKeyPageReqVO pageReqVO) {
PageResult<AiApiKeyDO> pageResult = apiKeyService.getApiKeyPage(pageReqVO);
PageResult<AiApiKeyDO> pageResult = apiKeyService.getApiKeyPage(pageReqVO, ContextUtil.getUid());
return success(BeanUtils.toBean(pageResult, AiApiKeyRespVO.class));
}

View File

@@ -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<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
PageResult<AiChatRoleDO> 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<AiChatRoleRespVO> 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<Long> createChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO createReqVO) {
return success(chatRoleService.createChatRoleMy(createReqVO, ContextUtil.getUid()));
}
@PutMapping("/update-my")
@Operation(summary = "更新【我的】聊天角色")
public R<Boolean> 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<Boolean> deleteChatRoleMy(@RequestParam("id") Long id) {
chatRoleService.deleteChatRoleMy(id, ContextUtil.getUid());
return success(true);
}
@GetMapping("/category-list")
@Operation(summary = "获得聊天角色的分类列表")
public R<List<String>> getChatRoleCategoryList() {
return success(chatRoleService.getChatRoleCategoryList());
}
// ========== 角色管理 ==========
@PostMapping("/create")
@Operation(summary = "创建聊天角色")
public R<Long> 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<Boolean> 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<Boolean> 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<PageResult<AiChatRoleRespVO>> getChatRolePage(@Valid AiChatRolePageReqVO pageReqVO) {
PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRolePage(pageReqVO);
PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, ContextUtil.getUid());
return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
}
@GetMapping("/category-list")
@Operation(summary = "获得聊天角色的分类列表")
public R<List<Map<String, String>>> getChatRoleCategoryList() {
return success(chatRoleService.getChatRoleCategoryList());
}
}

View File

@@ -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<Long> createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) {
return success(modelService.createModel(createReqVO));
}
@PostMapping("/create")
@Operation(summary = "创建模型")
public R<Long> 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<Boolean> updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) {
modelService.updateModel(updateReqVO);
return success(true);
}
@PutMapping("/update")
@Operation(summary = "更新模型")
public R<Boolean> 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<Boolean> 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<AiModelRespVO> 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<Boolean> deleteModel(@RequestParam("id") Long id) {
AiModelDO model = modelService.getModel(id);
if (model == null) {
return success(false);
}
@GetMapping("/page")
@Operation(summary = "获得模型分页")
public R<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
PageResult<AiModelDO> 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<List<AiModelRespVO>> getModelSimpleList(
@RequestParam("type") Integer type,
@RequestParam(value = "platform", required = false) String platform) {
List<AiModelDO> 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<AiModelRespVO> getModel(@RequestParam("id") Long id) {
AiModelDO model = modelService.getModel(id);
return success(BeanUtils.toBean(model, AiModelRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得模型分页(包含系统公开模型和用户私有模型)")
public R<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
PageResult<AiModelDO> 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<List<AiModelRespVO>> getModelSimpleList(@RequestParam("type") Integer type, @RequestParam(value = "platform", required = false) String platform) {
List<AiModelDO> 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())));
}
}

View File

@@ -17,4 +17,7 @@ public class AiApiKeyPageReqVO extends PageParam {
@Schema(description = "状态", example = "1")
private Integer status;
@Schema(description = "是否公开", example = "true")
private Boolean publicStatus;
}

View File

@@ -25,4 +25,5 @@ public class AiApiKeyRespVO {
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
private Boolean publicStatus;
}

View File

@@ -31,4 +31,7 @@ public class AiApiKeySaveReqVO {
@NotNull(message = "状态不能为空")
private Integer status;
@Schema(description = "是否公开", example = "false")
private Boolean publicStatus;
}

View File

@@ -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<Long> toolIds;
@Schema(description = "模型id", example = "1")
private Long modelId;
}

View File

@@ -17,4 +17,7 @@ public class AiModelPageReqVO extends PageParam {
@Schema(description = "模型平台", example = "OpenAI")
private String platform;
@Schema(description = "是否公开", example = "true")
private Boolean publicStatus;
}

View File

@@ -47,4 +47,7 @@ public class AiModelRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "是否公开", example = "false")
private Boolean publicStatus;
}

View File

@@ -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;
}

View File

@@ -50,4 +50,7 @@ public class AiModelSaveReqVO {
@Schema(description = "上下文的最大 Message 数量", example = "8192")
private Integer maxContexts;
@Schema(description = "是否公开", example = "false")
private Boolean publicStatus;
}

View File

@@ -1 +0,0 @@
package com.luohuo.flex.ai.controller;

View File

@@ -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;
}

View File

@@ -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;
// ========== 对话配置 ==========
/**

View File

@@ -16,20 +16,48 @@ import org.springframework.stereotype.Repository;
@Repository
public interface AiApiKeyMapper extends BaseMapperX<AiApiKeyDO> {
default PageResult<AiApiKeyDO> selectPage(AiApiKeyPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiApiKeyDO>()
.likeIfPresent(AiApiKeyDO::getName, reqVO.getName())
.eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform())
.eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus())
.orderByDesc(AiApiKeyDO::getId));
}
default PageResult<AiApiKeyDO> selectPage(AiApiKeyPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiApiKeyDO>()
.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<AiApiKeyDO>()
.eq("platform", platform)
.eq("status", status)
.limitN(1)
.orderByAsc("id"));
}
default PageResult<AiApiKeyDO> selectPageByMy(AiApiKeyPageReqVO reqVO, Long userId) {
LambdaQueryWrapperX<AiApiKeyDO> wrapper = new LambdaQueryWrapperX<AiApiKeyDO>()
.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<AiApiKeyDO>()
.eq("platform", platform)
.eq("status", status)
.limitN(1)
.orderByAsc("id"));
}
}

View File

@@ -17,29 +17,74 @@ import java.util.List;
@Repository
public interface AiChatMapper extends BaseMapperX<AiModelDO> {
default AiModelDO selectFirstByStatus(Integer type, Integer status) {
return selectOne(new QueryWrapperX<AiModelDO>()
.eq("type", type)
.eq("status", status)
.limitN(1)
.orderByAsc("sort"));
}
default AiModelDO selectFirstByStatus(Integer type, Integer status) {
return selectOne(new QueryWrapperX<AiModelDO>()
.eq("type", type)
.eq("status", status)
.limitN(1)
.orderByAsc("sort"));
}
default PageResult<AiModelDO> selectPage(AiModelPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiModelDO>()
.likeIfPresent(AiModelDO::getName, reqVO.getName())
.eqIfPresent(AiModelDO::getModel, reqVO.getModel())
.eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform())
.orderByAsc(AiModelDO::getSort));
}
default PageResult<AiModelDO> selectPage(AiModelPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiModelDO>()
.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<AiModelDO> selectListByStatusAndType(Integer status, Integer type,
@Nullable String platform) {
return selectList(new LambdaQueryWrapperX<AiModelDO>()
.eq(AiModelDO::getStatus, status)
.eq(AiModelDO::getType, type)
.eqIfPresent(AiModelDO::getPlatform, platform)
.orderByAsc(AiModelDO::getSort));
}
default PageResult<AiModelDO> selectPageByMy(AiModelPageReqVO reqVO, Long userId) {
LambdaQueryWrapperX<AiModelDO> wrapper = new LambdaQueryWrapperX<AiModelDO>()
.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<AiModelDO> selectListByStatusAndType(Integer status, Integer type,
@Nullable String platform) {
return selectList(new LambdaQueryWrapperX<AiModelDO>()
.eq(AiModelDO::getStatus, status)
.eq(AiModelDO::getType, type)
.eqIfPresent(AiModelDO::getPlatform, platform)
.orderByAsc(AiModelDO::getSort));
}
default List<AiModelDO> selectListByStatusAndTypeAndUserId(Integer status, Integer type,
@Nullable String platform, Long userId) {
return selectList(new LambdaQueryWrapperX<AiModelDO>()
.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));
}
}

View File

@@ -17,37 +17,51 @@ import java.util.List;
@Repository
public interface AiChatRoleMapper extends BaseMapperX<AiChatRoleDO> {
default PageResult<AiChatRoleDO> selectPage(AiChatRolePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiChatRoleDO>()
.likeIfPresent(AiChatRoleDO::getName, reqVO.getName())
.eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory())
.eqIfPresent(AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus())
.orderByAsc(AiChatRoleDO::getSort));
}
default PageResult<AiChatRoleDO> selectPage(AiChatRolePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiChatRoleDO>()
.likeIfPresent(AiChatRoleDO::getName, reqVO.getName())
.eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory())
.eqIfPresent(AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus())
.orderByAsc(AiChatRoleDO::getSort));
}
default PageResult<AiChatRoleDO> selectPageByMy(AiChatRolePageReqVO reqVO, Long userId) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiChatRoleDO>()
.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<AiChatRoleDO> selectPageByMy(AiChatRolePageReqVO reqVO, Long userId) {
LambdaQueryWrapperX<AiChatRoleDO> wrapper = new LambdaQueryWrapperX<AiChatRoleDO>()
.likeIfPresent(AiChatRoleDO::getName, reqVO.getName())
.eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory());
default List<AiChatRoleDO> selectListGroupByCategory(Integer status) {
return selectList(new LambdaQueryWrapperX<AiChatRoleDO>()
.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<AiChatRoleDO> selectListByName(String name) {
return selectList(new LambdaQueryWrapperX<AiChatRoleDO>()
.likeIfPresent(AiChatRoleDO::getName, name)
.orderByAsc(AiChatRoleDO::getSort));
}
wrapper.orderByAsc(AiChatRoleDO::getSort);
return selectPage(reqVO, wrapper);
}
}
default List<AiChatRoleDO> selectListGroupByCategory(Integer status) {
return selectList(new LambdaQueryWrapperX<AiChatRoleDO>()
.select(AiChatRoleDO::getCategory)
.eq(AiChatRoleDO::getStatus, status)
.groupBy(AiChatRoleDO::getCategory));
}
default List<AiChatRoleDO> selectListByName(String name) {
return selectList(new LambdaQueryWrapperX<AiChatRoleDO>()
.likeIfPresent(AiChatRoleDO::getName, name)
.orderByAsc(AiChatRoleDO::getSort));
}
}

View File

@@ -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);
/**
* 【管理员】删除聊天对话

View File

@@ -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

View File

@@ -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);
/**
* 【管理员】删除消息

View File

@@ -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<AiChatMessageDO> 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<AiChatMessageDO> 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

View File

@@ -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<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO);
PageResult<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO, Long userId);
/**
* 获得 API 密钥列表

View File

@@ -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<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO) {
return apiKeyMapper.selectPage(pageReqVO);
}
@Override
public void deleteApiKey(Long id, Long userId) {
// 校验存在
AiApiKeyDO apiKey = validateApiKeyExists(id);
@Override
public List<AiApiKeyDO> 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<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO, Long userId) {
return apiKeyMapper.selectPageByMy(pageReqVO, userId);
}
@Override
public List<AiApiKeyDO> 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;
}
}

View File

@@ -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<String> getChatRoleCategoryList();
List<Map<String, String>> getChatRoleCategoryList();
/**
* 根据名字获得聊天角色

View File

@@ -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<String> getChatRoleCategoryList() {
List<AiChatRoleDO> list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus());
return convertList(list, AiChatRoleDO::getCategory,
role -> role != null && StrUtil.isNotBlank(role.getCategory()));
}
@Override
public List<Map<String, String>> getChatRoleCategoryList() {
return CATEGORY_OPTIONS;
}
public static final List<Map<String, String>> CATEGORY_OPTIONS = Arrays.asList(
createOption("AI助手", "AI助手"),
createOption("写作", "写作"),
createOption("编程开发", "编程开发"),
createOption("学习教育", "学习教育"),
createOption("生活娱乐", "生活娱乐"),
createOption("商务办公", "商务办公"),
createOption("创意设计", "创意设计"),
createOption("数据分析", "数据分析"),
createOption("翻译", "翻译"),
createOption("其他", "其他")
);
private static Map<String, String> createOption(String label, String value) {
Map<String, String> option = new HashMap<>();
option.put("label", label);
option.put("value", value);
return option;
}
@Override
public List<AiChatRoleDO> getChatRoleListByName(String name) {

View File

@@ -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<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO);
/**
* 获得【我的】模型分页
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return 模型分页
*/
PageResult<AiModelDO> getModelMyPage(AiModelPageReqVO pageReqVO, Long userId);
/**
* 校验模型是否可使用
*
@@ -92,6 +127,20 @@ public interface AiModelService {
List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type,
@Nullable String platform);
/**
* 获得模型列表(用户可见)
*
* 包含:系统级别的公开模型 + 用户自己的私有模型
*
* @param status 状态
* @param type 类型
* @param platform 平台,允许空
* @param userId 用户编号
* @return 模型列表
*/
List<AiModelDO> getModelListByStatusAndTypeAndUserId(Integer status, Integer type,
@Nullable String platform, Long userId);
// ========== 与 Spring AI 集成 ==========
/**

View File

@@ -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<AiModelDO> 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<AiModelDO> 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<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO) {
return modelMapper.selectPage(pageReqVO);
}
@Override
public VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields) {
// 获取模型 + 密钥
AiModelDO model = validateModel(id);
AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
@Override
public PageResult<AiModelDO> 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<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) {
return modelMapper.selectListByStatusAndType(status, type, platform);
}
@Override
public List<AiModelDO> 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<String, Class<?>> 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;
}
}
}

View File

@@ -31,15 +31,14 @@ public class BlackInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<Integer, Set<String>> 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<Object> 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<Object> responseData = R.fail("对不起,不在白名单内");
response.setContentType(ContentType.JSON.toString(Charset.forName("UTF-8")));

View File

@@ -509,10 +509,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
public Boolean updateMyRoomInfo(Long uid, RoomMyInfoReq request) {
// 1.校验修改权限
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, request.getId());
if (!permissionCheck.getRight()) {
return false;
}
RoomGroup roomGroup = permissionCheck.getLeft();
GroupMember member = permissionCheck.getMiddle();

View File

@@ -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<FeedMapper, Feed> {
public CursorPageBaseResp<Feed> 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<Feed> getFeedPage(List<Long> uidList, CursorPageBaseReq request) {
return CursorUtils.getCursorPageByMysql(this, request, wrapper -> wrapper.in(Feed::getUid, uidList), Feed::getCreateTime);
}
public FeedVo getDetail(Long id) {

View File

@@ -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<Long, SummeryInfo
return null;
}
@TenantIgnore
@Cacheable(cacheNames = "luohuo:user", key = "'blackList'")
public Map<Integer, Set<String>> getBlackMap() {
LocalDateTime now = LocalDateTime.now();

View File

@@ -84,11 +84,46 @@ public class FeedServiceImpl implements FeedService {
*/
@Override
public CursorPageBaseResp<FeedVo> getFeedPage(CursorPageBaseReq request, Long uid) {
// 1. 查询朋友圈列表
CursorPageBaseResp<Feed> page = feedDao.getFeedPage(uid, request);
// 1. 查询当前用户的所有好友ID
List<Long> friendIds = userFriendDao.getAllFriendIdsByUid(uid);
friendIds.add(uid); // 添加自己
// 2. 合并朋友圈内容
List<FeedVo> result = buildFeedResp(page.getList());
// 2. 查询这些用户的朋友圈列表
CursorPageBaseResp<Feed> page = feedDao.getFeedPage(friendIds, request);
// 3. 根据权限过滤朋友圈
List<Feed> 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<FeedTarget> targets = feedTargetDao.selectFeedTargets(feed.getId());
// type=2 表示用户targetId 就是用户ID
return targets.stream().anyMatch(t -> t.getType() == 2 && t.getTargetId().equals(uid));
case notAnyone:
// 不给谁看:查询是否在不可见列表中
List<FeedTarget> 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<FeedVo> 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<Long> pushList = new ArrayList<>();
List<FeedTarget> 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<Object> cacheResult = cachePlusOps.hGet(FeedMediaRelCacheKeyBuilder.build(feedId));
List<FeedMedia> feedMediaList = cacheResult.asList();
if(CollUtil.isEmpty(feedMediaList)){
feedMediaList = feedMediaDao.getMediaByFeedId(feedId);
}
if(!feed.getMediaType().equals(FeedEnum.WORD.getType())){
// 使用带回调的方式,自动处理缓存读写
CacheHashKey hashKey = FeedMediaRelCacheKeyBuilder.build(feedId);
CacheResult<List<FeedMedia>> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feedId));
List<FeedMedia> 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;
}
}

View File

@@ -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<String> 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;
}
}