diff --git a/docs/install/sql/luohuo_dev.sql b/docs/install/sql/luohuo_dev.sql index 4cc2ab30..4d2edf2f 100644 --- a/docs/install/sql/luohuo_dev.sql +++ b/docs/install/sql/luohuo_dev.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 31/10/2025 19:00:36 + Date: 04/11/2025 19:45:03 */ SET NAMES utf8mb4; @@ -37,12 +37,13 @@ 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 = 90154862069761 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91865840302593 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_api_key -- ---------------------------- 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); +INSERT INTO `ai_api_key` VALUES (91865840302592, 10937855681024, b'0', 'Hula', 'sk-dmyowzmrlwjgmfogkaynoqpqfuaodyvcwmqbbxbugmauofci', 'Other', 'https://api.siliconflow.cn/v1/chat/completions', 0, NULL, '2025-11-04 12:02:04', NULL, '2025-11-04 12:02:04', b'0', 1); -- ---------------------------- -- Table structure for ai_chat_conversation @@ -68,14 +69,62 @@ 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 = 90190392010241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91963672443393 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_conversation -- ---------------------------- 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); +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-11-01 21:17:17', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90544185752064, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-10-31 20:30:16', NULL, '2025-11-01 07:54:13', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90639270623744, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 02:48:07', NULL, '2025-11-01 07:54:09', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711748196864, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:06', NULL, '2025-11-01 07:38:22', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711752391168, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:07', NULL, '2025-11-01 07:38:21', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711756585472, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:08', NULL, '2025-11-01 07:38:20', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711756585473, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:09', NULL, '2025-11-01 07:38:19', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711760779776, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:10', NULL, '2025-11-01 07:38:18', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90711769168384, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:36:11', NULL, '2025-11-01 07:38:16', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712415091200, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:38:45', NULL, '2025-11-01 07:38:47', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712759024128, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:40:08', NULL, '2025-11-01 07:42:25', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712763218432, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:40:08', NULL, '2025-11-01 07:42:24', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712763218433, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:40:08', NULL, '2025-11-01 07:42:23', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712767412736, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:40:09', NULL, '2025-11-01 07:42:22', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90712771607040, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:40:10', NULL, '2025-11-01 07:42:21', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90713224591872, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:41:59', NULL, '2025-11-01 07:42:20', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90714143144448, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:45:38', NULL, '2025-11-01 07:45:39', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90715393047040, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:50:35', NULL, '2025-11-01 07:50:38', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90716315793920, 11225442329600, 90190358455808, '小试牛刀', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 07:54:15', NULL, '2025-11-01 08:11:49', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90719184697856, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 08:05:40', NULL, '2025-11-01 08:11:47', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90719507659264, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 08:06:57', NULL, '2025-11-01 08:11:46', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (90720778533376, 11225442329600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 08:12:00', NULL, '2025-11-01 08:12:00', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (90918409943552, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-01 21:17:19', NULL, '2025-11-02 10:56:01', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91009791244800, 10937855681024, 90190358455808, '新的聊天91009791244800', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 03:20:26', NULL, '2025-11-02 03:21:17', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91009925462528, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 03:20:58', NULL, '2025-11-02 03:21:03', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91085058030080, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 08:19:30', NULL, '2025-11-02 10:56:55', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91124627094016, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 10:56:45', NULL, '2025-11-03 17:10:25', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91124790671872, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 10:57:23', NULL, '2025-11-03 17:10:21', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91255690705408, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 19:37:32', NULL, '2025-11-03 17:10:17', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91278583216640, 91277941125632, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 21:08:31', NULL, '2025-11-02 21:08:31', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91278864235008, 91277941125632, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-02 21:09:37', NULL, '2025-11-02 21:09:37', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91392118831616, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 04:39:39', NULL, '2025-11-03 17:10:15', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91546326612480, 91542639456768, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 14:52:25', NULL, '2025-11-03 14:52:25', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91547207416320, 91539938324992, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 14:55:55', NULL, '2025-11-03 14:55:55', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91581109975552, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 17:10:38', NULL, '2025-11-04 13:10:59', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91582871583232, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 17:17:39', NULL, '2025-11-04 13:11:00', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91647820380672, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-03 21:35:44', NULL, '2025-11-04 13:11:01', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91866154875392, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 12:03:19', NULL, '2025-11-04 13:11:02', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91883204721152, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 13:11:04', NULL, '2025-11-04 14:58:33', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91883380881920, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 13:11:46', NULL, '2025-11-04 13:11:46', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91883619957248, 91880733913600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 13:12:42', NULL, '2025-11-04 13:12:42', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91883624151552, 91880733913600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 13:12:44', NULL, '2025-11-04 13:12:44', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91883645123072, 91880733913600, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 13:12:49', NULL, '2025-11-04 13:12:49', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91902066506240, 91901021762048, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 14:26:01', NULL, '2025-11-04 18:26:58', b'1', 1); +INSERT INTO `ai_chat_conversation` VALUES (91910266370560, 10937855681024, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 14:58:35', NULL, '2025-11-04 14:58:35', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91932869474816, 44100975922176, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 16:28:24', NULL, '2025-11-04 16:28:24', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91948023495168, 86483537072128, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 17:28:38', NULL, '2025-11-04 17:28:38', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91962976188928, 91901021762048, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 18:28:03', NULL, '2025-11-04 18:28:03', b'0', 1); +INSERT INTO `ai_chat_conversation` VALUES (91963672443392, 91901021762048, 90190358455808, '测试HuLa', 90158209124864, 'moonshot-v1-128k', b'0', NULL, '你是一个vue专家', 0.8, 4096, 10, NULL, '2025-11-04 18:30:49', NULL, '2025-11-04 18:30:49', b'0', 1); -- ---------------------------- -- Table structure for ai_chat_message @@ -100,7 +149,7 @@ CREATE TABLE `ai_chat_message` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 90190480090626 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91948065438210 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_message @@ -131,13 +180,14 @@ 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 = 90190358455809 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 90544886200833 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_role -- ---------------------------- 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); +INSERT INTO `ai_chat_role` VALUES (90544886200832, NULL, 90158209124864, '敲你母牛🐔', 'https://cdn.hulaspark.com/avatar/k2439646234/8421975045ef869aa5d841f7c4a050a4.webp', 'AI助手', 1, '精通编程语言', 'vue高手', NULL, NULL, b'1', 0, NULL, '2025-10-31 20:33:03', NULL, '2025-10-31 20:33:03', b'0', 1); -- ---------------------------- -- Table structure for ai_image @@ -348,12 +398,13 @@ CREATE TABLE `ai_model` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 91866029046273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 模型表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_model -- ---------------------------- 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); +INSERT INTO `ai_model` VALUES (91866029046272, 10937855681024, 91865840302592, '测试', '测试1', 'OpenAI', 1, 0, 0, b'0', 0.8, 4096, 10, NULL, '2025-11-04 12:02:48', NULL, '2025-11-04 12:02:48', b'0', 1); -- ---------------------------- -- Table structure for ai_music @@ -2232,7 +2283,7 @@ 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-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); +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\": \"113.89.34.18\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"113.89.34.18\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', NULL, 0, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-11-04 19:34:05', 1, '2025-03-27 04:23:08', NULL, '2025-11-04 19:41:35', 0, 1); -- ---------------------------- -- Table structure for def_user_application @@ -2317,7 +2368,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, '腾讯邮件', 624, 57, '2025-10-31 17:41:02', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); +INSERT INTO `extend_interface_log` VALUES (655249535051914248, 244881451621810192, '腾讯邮件', 777, 57, '2025-11-04 19:11:50', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); -- ---------------------------- -- Table structure for extend_interface_logging @@ -2531,7 +2582,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 = 921 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 931 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/docs/install/sql/luohuo_im_01.sql b/docs/install/sql/luohuo_im_01.sql index 8424d31c..88b8168e 100644 --- a/docs/install/sql/luohuo_im_01.sql +++ b/docs/install/sql/luohuo_im_01.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 31/10/2025 19:00:29 + Date: 04/11/2025 19:45:09 */ SET NAMES utf8mb4; @@ -74,7 +74,7 @@ CREATE TABLE `im_announcements` ( `is_del` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 1, PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 89028498137601 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_as_ci COMMENT = '聊天公告表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91601993052673 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_as_ci COMMENT = '聊天公告表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_announcements @@ -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 = 69082079592702 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 69082079594846 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_contact @@ -183,8 +183,53 @@ 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_comment +-- ---------------------------- +DROP TABLE IF EXISTS `im_feed_comment`; +CREATE TABLE `im_feed_comment` ( + `id` bigint NOT NULL, + `feed_id` bigint NOT NULL COMMENT '朋友圈id', + `uid` bigint NOT NULL COMMENT '评论人uid', + `reply_comment_id` bigint NULL DEFAULT NULL COMMENT '回复的评论ID(如果是回复评论,则有值;如果是直接评论朋友圈,则为null)', + `reply_uid` bigint NULL DEFAULT NULL COMMENT '被回复人的uid(如果是回复评论,则有值)', + `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL COMMENT '评论内容', + `tenant_id` bigint NOT NULL DEFAULT 1, + `create_by` bigint NOT NULL DEFAULT 0 COMMENT '创建者', + `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + `update_by` bigint NOT NULL DEFAULT 0 COMMENT '更新者', + `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '更新时间', + `is_del` tinyint NOT NULL DEFAULT 0 COMMENT '逻辑删除', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_feed_id`(`feed_id` ASC) USING BTREE, + INDEX `idx_uid`(`uid` ASC) USING BTREE, + INDEX `idx_create_time`(`create_time` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_as_ci COMMENT = '朋友圈评论表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of im_feed_comment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for im_feed_like +-- ---------------------------- +DROP TABLE IF EXISTS `im_feed_like`; +CREATE TABLE `im_feed_like` ( + `id` bigint NOT NULL, + `feed_id` bigint NOT NULL COMMENT '朋友圈id', + `uid` bigint NOT NULL COMMENT '点赞人uid', + `tenant_id` bigint NOT NULL DEFAULT 1, + `create_by` bigint NOT NULL DEFAULT 0 COMMENT '创建者', + `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_feed_uid`(`feed_id` ASC, `uid` ASC) USING BTREE, + INDEX `idx_uid`(`uid` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_as_ci COMMENT = '朋友圈点赞表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of im_feed_like +-- ---------------------------- -- ---------------------------- -- Table structure for im_feed_media @@ -279,7 +324,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 = 90516414902273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_group_member @@ -342,7 +387,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 = 90516414902274 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121160 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message @@ -372,7 +417,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 = 90188940422657 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91948392231425 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message_mark @@ -404,7 +449,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 = 90516389736451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91941953974787 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_notice @@ -454,12 +499,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 = 90505799118849 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121155 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-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); +INSERT INTO `im_room` VALUES (1, 1, 1, '2025-11-04 19:12:14.299', 91974099121159, NULL, '2024-07-10 11:17:15.521', '2025-11-04 11:12:14.333', 1, 1, NULL, 0); -- ---------------------------- -- Table structure for im_room_friend @@ -484,7 +529,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 = 90505799118850 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121156 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room_friend @@ -513,7 +558,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 = 90207575715331 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群聊房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91897532101123 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群聊房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room_group @@ -596,13 +641,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 = 90501692895233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121153 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-31 14:03:50.763', '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\": \"113.89.34.18\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"113.89.34.18\", \"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-11-04 12:54:01.895', 'k.2439646234', 0, NULL, 0, '2025-09-20 21:35:31.415', 99978, 0, 1); -- ---------------------------- -- Table structure for im_user_apply @@ -631,7 +676,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 = 90516389736449 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91941953974785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_apply @@ -658,7 +703,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 = 90501697089540 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121163 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_backpack @@ -701,7 +746,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 = 90111324826625 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91254163616257 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_emoji @@ -734,7 +779,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 = 90505799118852 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121158 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_friend @@ -912,7 +957,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 = 90516414902275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 91974099121162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of secure_invoke_record @@ -931,7 +976,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 = 217 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 219 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java index 68494d4f..9912bd0c 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/AiApiKeyController.java @@ -1,6 +1,7 @@ package com.luohuo.flex.ai.controller.model; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyBalanceRespVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyPageReqVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyRespVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeySaveReqVO; @@ -76,4 +77,11 @@ public class AiApiKeyController { return success(convertList(list, key -> new AiModelRespVO().setId(key.getId()).setName(key.getName()))); } + @GetMapping("/balance") + @Operation(summary = "查询 API 密钥余额") + @Parameter(name = "id", description = "API密钥编号", required = true, example = "1024") + public R getApiKeyBalance(@RequestParam("id") Long id) { + return success(apiKeyService.getApiKeyBalance(id, ContextUtil.getUid())); + } + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java new file mode 100644 index 00000000..f163f717 --- /dev/null +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java @@ -0,0 +1,70 @@ +package com.luohuo.flex.ai.controller.model.vo.apikey; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.List; + +/** + * AI API 密钥余额查询响应 VO + * + * @author fansili + */ +@Schema(description = "管理后台 - AI API 密钥余额查询 Response VO") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiApiKeyBalanceRespVO { + + @Schema(description = "API Key ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538") + private Long id; + + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Moonshot") + private String platform; + + @Schema(description = "是否支持余额查询", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean supported; + + @Schema(description = "是否查询成功", example = "true") + private Boolean success; + + @Schema(description = "错误信息", example = "API Key无效") + private String errorMessage; + + @Schema(description = "余额信息列表") + private List balanceInfos; + + @Schema(description = "总余额(所有币种的总和,统一转换为人民币)", example = "100.50") + private BigDecimal totalBalance; + + /** + * 余额信息 + */ + @Schema(description = "余额信息") + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BalanceInfo { + + @Schema(description = "币种", example = "CNY") + private String currency; + + @Schema(description = "总余额", example = "110.00") + private BigDecimal totalBalance; + + @Schema(description = "赠送余额", example = "10.00") + private BigDecimal grantedBalance; + + @Schema(description = "充值余额", example = "100.00") + private BigDecimal toppedUpBalance; + + @Schema(description = "是否可用", example = "true") + private Boolean available; + } +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java index 79ae095e..d15be63a 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyService.java @@ -1,6 +1,7 @@ package com.luohuo.flex.ai.service.model; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyBalanceRespVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyPageReqVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeySaveReqVO; import com.luohuo.flex.ai.dal.model.AiApiKeyDO; @@ -80,4 +81,13 @@ public interface AiApiKeyService { */ AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status); + /** + * 查询 API 密钥余额 + * + * @param id API 密钥编号 + * @param userId 用户编号 + * @return 余额信息 + */ + AiApiKeyBalanceRespVO getApiKeyBalance(Long id, Long userId); + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java index 40b85ff0..537c7679 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java @@ -1,17 +1,27 @@ package com.luohuo.flex.ai.service.model; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import com.luohuo.flex.ai.common.pojo.PageResult; +import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyBalanceRespVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeyPageReqVO; import com.luohuo.flex.ai.controller.model.vo.apikey.AiApiKeySaveReqVO; import com.luohuo.flex.ai.dal.model.AiApiKeyDO; +import com.luohuo.flex.ai.enums.AiPlatformEnum; import com.luohuo.flex.ai.enums.CommonStatusEnum; import com.luohuo.flex.ai.mapper.model.AiApiKeyMapper; import com.luohuo.flex.ai.utils.BeanUtils; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.API_KEY_DISABLE; @@ -23,6 +33,7 @@ import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; * * @author 芋道源码 */ +@Slf4j @Service @Validated public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -135,4 +146,224 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { return apiKey; } + @Override + public AiApiKeyBalanceRespVO getApiKeyBalance(Long id, Long userId) { + // 1. 校验 API Key 是否存在 + AiApiKeyDO apiKey = validateApiKeyExists(id); + + // 2. 权限校验 + if (Boolean.FALSE.equals(apiKey.getPublicStatus())) { + // 私有密钥,只有创建者可以查询 + if (ObjectUtil.notEqual(apiKey.getUserId(), userId)) { + throw exception(API_KEY_NOT_EXISTS); + } + } + + // 3. 根据平台查询余额 + AiPlatformEnum platformEnum; + try { + platformEnum = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + } catch (IllegalArgumentException e) { + return AiApiKeyBalanceRespVO.builder() + .id(id) + .platform(apiKey.getPlatform()) + .supported(false) + .success(false) + .errorMessage("不支持的平台: " + apiKey.getPlatform()) + .build(); + } + + return queryBalanceByPlatform(apiKey, platformEnum); + } + + /** + * 根据平台查询余额 + */ + private AiApiKeyBalanceRespVO queryBalanceByPlatform(AiApiKeyDO apiKey, AiPlatformEnum platform) { + try { + switch (platform) { + case MOONSHOT: + return queryMoonshotBalance(apiKey); + case DEEP_SEEK: + return queryDeepSeekBalance(apiKey); + case SILICON_FLOW: + return querySiliconFlowBalance(apiKey); + default: + return AiApiKeyBalanceRespVO.builder() + .id(apiKey.getId()) + .platform(apiKey.getPlatform()) + .supported(false) + .success(false) + .errorMessage("该平台暂不支持余额查询") + .build(); + } + } catch (Exception e) { + log.error("查询API Key余额失败, platform={}, error={}", platform.getPlatform(), e.getMessage(), e); + return AiApiKeyBalanceRespVO.builder() + .id(apiKey.getId()) + .platform(apiKey.getPlatform()) + .supported(true) + .success(false) + .errorMessage("查询失败: " + e.getMessage()) + .build(); + } + } + + /** + * 查询月之暗面(Moonshot/Kimi)余额 + * API文档: https://platform.moonshot.cn/docs/api/balance + */ + private AiApiKeyBalanceRespVO queryMoonshotBalance(AiApiKeyDO apiKey) { + String baseUrl = StrUtil.isNotBlank(apiKey.getUrl()) ? apiKey.getUrl() : "https://api.moonshot.cn"; + String url = baseUrl + "/v1/user/balance"; + + HttpResponse response = HttpRequest.get(url) + .header("Authorization", "Bearer " + apiKey.getApiKey()) + .header("Content-Type", "application/json") + .timeout(10000) + .execute(); + + if (!response.isOk()) { + throw new RuntimeException("HTTP请求失败: " + response.getStatus()); + } + + JSONObject jsonResponse = JSONUtil.parseObj(response.body()); + + // 解析响应 + List balanceInfos = new ArrayList<>(); + BigDecimal totalBalance = BigDecimal.ZERO; + + if (jsonResponse.containsKey("data")) { + JSONObject data = jsonResponse.getJSONObject("data"); + if (data.containsKey("available_balance")) { + BigDecimal balance = data.getBigDecimal("available_balance"); + balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() + .currency("CNY") + .totalBalance(balance) + .available(true) + .build()); + totalBalance = balance; + } + } + + return AiApiKeyBalanceRespVO.builder() + .id(apiKey.getId()) + .platform(apiKey.getPlatform()) + .supported(true) + .success(true) + .balanceInfos(balanceInfos) + .totalBalance(totalBalance) + .build(); + } + + /** + * 查询DeepSeek余额 + * API文档: https://api-docs.deepseek.com/api/get-user-balance + */ + private AiApiKeyBalanceRespVO queryDeepSeekBalance(AiApiKeyDO apiKey) { + String baseUrl = StrUtil.isNotBlank(apiKey.getUrl()) ? apiKey.getUrl() : "https://api.deepseek.com"; + String url = baseUrl + "/user/balance"; + + HttpResponse response = HttpRequest.get(url) + .header("Authorization", "Bearer " + apiKey.getApiKey()) + .header("Accept", "application/json") + .timeout(10000) + .execute(); + + if (!response.isOk()) { + throw new RuntimeException("HTTP请求失败: " + response.getStatus()); + } + + JSONObject jsonResponse = JSONUtil.parseObj(response.body()); + + // 解析响应 + List balanceInfos = new ArrayList<>(); + BigDecimal totalBalance = BigDecimal.ZERO; + + if (jsonResponse.getBool("is_available", false) && jsonResponse.containsKey("balance_infos")) { + for (Object item : jsonResponse.getJSONArray("balance_infos")) { + JSONObject balanceInfo = (JSONObject) item; + String currency = balanceInfo.getStr("currency"); + BigDecimal total = new BigDecimal(balanceInfo.getStr("total_balance")); + BigDecimal granted = balanceInfo.containsKey("granted_balance") + ? new BigDecimal(balanceInfo.getStr("granted_balance")) : BigDecimal.ZERO; + BigDecimal toppedUp = balanceInfo.containsKey("topped_up_balance") + ? new BigDecimal(balanceInfo.getStr("topped_up_balance")) : BigDecimal.ZERO; + + balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() + .currency(currency) + .totalBalance(total) + .grantedBalance(granted) + .toppedUpBalance(toppedUp) + .available(true) + .build()); + + // 简单累加(实际应该考虑汇率转换) + totalBalance = totalBalance.add(total); + } + } + + return AiApiKeyBalanceRespVO.builder() + .id(apiKey.getId()) + .platform(apiKey.getPlatform()) + .supported(true) + .success(true) + .balanceInfos(balanceInfos) + .totalBalance(totalBalance) + .build(); + } + + /** + * 查询硅基流动余额 + * API文档: https://docs.siliconflow.cn/cn/api-reference/userinfo/get-user-info + */ + private AiApiKeyBalanceRespVO querySiliconFlowBalance(AiApiKeyDO apiKey) { + String baseUrl = StrUtil.isNotBlank(apiKey.getUrl()) ? apiKey.getUrl() : "https://api.siliconflow.cn"; + String url = baseUrl + "/v1/user/info"; + + HttpResponse response = HttpRequest.get(url) + .header("Authorization", "Bearer " + apiKey.getApiKey()) + .header("Content-Type", "application/json") + .timeout(10000) + .execute(); + + if (!response.isOk()) { + throw new RuntimeException("HTTP请求失败: " + response.getStatus()); + } + + JSONObject jsonResponse = JSONUtil.parseObj(response.body()); + + // 解析响应 + List balanceInfos = new ArrayList<>(); + BigDecimal totalBalance = BigDecimal.ZERO; + + if (jsonResponse.getInt("code") == 20000 && jsonResponse.containsKey("data")) { + JSONObject data = jsonResponse.getJSONObject("data"); + + // 硅基流动返回的余额字段 + BigDecimal balance = data.getBigDecimal("balance", BigDecimal.ZERO); + BigDecimal chargeBalance = data.getBigDecimal("chargeBalance", BigDecimal.ZERO); + BigDecimal total = data.getBigDecimal("totalBalance", BigDecimal.ZERO); + + balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() + .currency("CNY") + .totalBalance(total) + .grantedBalance(balance.subtract(chargeBalance).max(BigDecimal.ZERO)) + .toppedUpBalance(chargeBalance) + .available("normal".equals(data.getStr("status"))) + .build()); + + totalBalance = total; + } + + return AiApiKeyBalanceRespVO.builder() + .id(apiKey.getId()) + .platform(apiKey.getPlatform()) + .supported(true) + .success(true) + .balanceInfos(balanceInfos) + .totalBalance(totalBalance) + .build(); + } + } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java index 2af77275..8c0136d6 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/chat/service/impl/RoomAppServiceImpl.java @@ -489,7 +489,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean { // 4. 异步清理缓存 CompletableFuture.runAsync(() -> { - roomGroupCache.delete(roomGroup.getId()); + roomGroupCache.delete(roomGroup.getRoomId()); roomGroupCache.evictGroup(roomGroup.getAccount()); }); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedCommentDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedCommentDao.java new file mode 100644 index 00000000..52698ee0 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedCommentDao.java @@ -0,0 +1,66 @@ +package com.luohuo.flex.im.core.user.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.luohuo.flex.im.common.utils.CursorUtils; +import com.luohuo.flex.im.core.user.mapper.FeedCommentMapper; +import com.luohuo.flex.im.domain.entity.FeedComment; +import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; +import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; + +/** + * 朋友圈评论 Dao + */ +@Service +public class FeedCommentDao extends ServiceImpl { + + /** + * 获取朋友圈的评论数量 + */ + public Integer getCommentCount(Long feedId) { + return Math.toIntExact(lambdaQuery() + .eq(FeedComment::getFeedId, feedId) + .count()); + } + + /** + * 游标分页查询朋友圈评论(升序排列,最新评论在最后) + */ + public CursorPageBaseResp getCommentPage(Long feedId, CursorPageBaseReq request) { + // 使用 CursorUtils 进行游标分页(默认降序) + CursorPageBaseResp result = CursorUtils.getCursorPageByMysql( + this, + request, + wrapper -> wrapper.eq(FeedComment::getFeedId, feedId), + FeedComment::getCreateTime + ); + + // 反向排序列表,使最新评论在最后 + Collections.reverse(result.getList()); + + return result; + } + + /** + * 获取某条朋友圈的所有评论(不分页,用于缓存) + * 注意:MyBatis Plus 会自动过滤已删除的记录(通过 @TableLogic 注解) + */ + public List getCommentListByFeedId(Long feedId) { + return lambdaQuery() + .eq(FeedComment::getFeedId, feedId) + .orderByAsc(FeedComment::getCreateTime) + .list(); + } + + /** + * 删除朋友圈的所有评论 + */ + public boolean delByFeedId(Long feedId) { + return remove(new LambdaQueryWrapper() + .eq(FeedComment::getFeedId, feedId)); + } +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java index 195870be..d97367d8 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedDao.java @@ -19,6 +19,7 @@ public class FeedDao extends ServiceImpl { /** * 查询朋友圈列表(包括指定用户列表的朋友圈) + * * @param uidList 用户ID列表(包括当前用户和好友) * @param request 分页请求 * @return 朋友圈分页结果 diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedLikeDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedLikeDao.java new file mode 100644 index 00000000..c2477219 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedLikeDao.java @@ -0,0 +1,56 @@ +package com.luohuo.flex.im.core.user.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.luohuo.flex.im.core.user.mapper.FeedLikeMapper; +import com.luohuo.flex.im.domain.entity.FeedLike; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 朋友圈点赞 Dao + */ +@Service +public class FeedLikeDao extends ServiceImpl { + + /** + * 获取用户对某条朋友圈的点赞记录 + */ + public FeedLike get(Long uid, Long feedId) { + return lambdaQuery() + .eq(FeedLike::getUid, uid) + .eq(FeedLike::getFeedId, feedId) + .one(); + } + + /** + * 获取朋友圈的点赞数量 + */ + public Integer getLikeCount(Long feedId) { + return Math.toIntExact(lambdaQuery() + .eq(FeedLike::getFeedId, feedId) + .count()); + } + + /** + * 获取某条朋友圈的所有点赞用户ID列表 + */ + public List getLikeUidList(Long feedId) { + return lambdaQuery() + .eq(FeedLike::getFeedId, feedId) + .select(FeedLike::getUid) + .list() + .stream() + .map(FeedLike::getUid) + .toList(); + } + + /** + * 删除朋友圈的所有点赞 + */ + public boolean delByFeedId(Long feedId) { + return remove(new LambdaQueryWrapper() + .eq(FeedLike::getFeedId, feedId)); + } +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedMediaDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedMediaDao.java index 0118fc82..a821c082 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedMediaDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedMediaDao.java @@ -17,11 +17,12 @@ public class FeedMediaDao extends ServiceImpl { /** * 批量添加朋友圈的资源的数据 + * * @param feedId 朋友圈id * @param images 素材地址 - * @param type 0 纯文字 1 图片 2 视频 + * @param type 0 纯文字 1 图片 2 视频 */ - public List batchSaveMedia(Long feedId, List images, Integer type){ + public List batchSaveMedia(Long feedId, List images, Integer type) { List feedMediaList = new ArrayList<>(); for (int i = 0; i < images.size(); i++) { String url = images.get(i); @@ -37,6 +38,7 @@ public class FeedMediaDao extends ServiceImpl { /** * 删除朋友圈的资源等消息 + * * @param feedId * @return */ @@ -46,6 +48,7 @@ public class FeedMediaDao extends ServiceImpl { /** * 通过id获取到朋友圈资源信息 + * * @param feedId * @return */ diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedTargetDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedTargetDao.java index 7449ce7d..01e81d3c 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedTargetDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/FeedTargetDao.java @@ -14,6 +14,7 @@ public class FeedTargetDao extends ServiceImpl { /** * 删除朋友圈和标签的管理 + * * @param feedId * @return */ diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/ItemConfigDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/ItemConfigDao.java index db70d5c2..5444d540 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/ItemConfigDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/ItemConfigDao.java @@ -17,9 +17,9 @@ import java.util.List; @Service public class ItemConfigDao extends ServiceImpl { - public List getByType(Integer type) { - return lambdaQuery() - .eq(ItemConfig::getType, type) - .list(); - } + public List getByType(Integer type) { + return lambdaQuery() + .eq(ItemConfig::getType, type) + .list(); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/NoticeDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/NoticeDao.java index c8fbfebe..f500ba0c 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/NoticeDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/NoticeDao.java @@ -36,7 +36,8 @@ public class NoticeDao extends ServiceImpl { /** * 查询当前用户的通知 - * @param uid 登录用户 + * + * @param uid 登录用户 * @param onlyUnread 通知状态 * @return */ @@ -53,7 +54,7 @@ public class NoticeDao extends ServiceImpl { wsNotice.setUid(uid); for (FriendUnreadDto friendUnreadDto : unReadCountByTypeMap) { - if(friendUnreadDto.getType().equals(RoomTypeEnum.FRIEND.getType())){ + if (friendUnreadDto.getType().equals(RoomTypeEnum.FRIEND.getType())) { wsNotice.setUnReadCount4Friend(friendUnreadDto.getCount()); } else { wsNotice.setUnReadCount4Group(friendUnreadDto.getCount()); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserApplyDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserApplyDao.java index 1432bc52..085a5ba4 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserApplyDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserApplyDao.java @@ -25,44 +25,43 @@ import static com.luohuo.flex.im.domain.enums.NoticeStatusEnum.ACCEPTED; @Service public class UserApplyDao extends ServiceImpl { - /** - * - * @param uid uid - * @param targetUid 目标 UID - * @param initiator 方法调用方是否是申请记录发起方 - * @return {@link UserApply } - */ - public UserApply getFriendApproving(Long uid, Long targetUid, boolean initiator) { - return lambdaQuery().eq(UserApply::getUid, uid) - .eq(UserApply::getTargetId, targetUid) - .eq(UserApply::getStatus, NoticeStatusEnum.UNTREATED.getStatus()) - .eq(UserApply::getType, RoomTypeEnum.FRIEND.getType()) - .notIn(initiator,UserApply::getDeleted, ApplyDeletedEnum.applyDeleted()) - .notIn(!initiator,UserApply::getDeleted, ApplyDeletedEnum.targetDeleted()) - .one(); - } + /** + * @param uid uid + * @param targetUid 目标 UID + * @param initiator 方法调用方是否是申请记录发起方 + * @return {@link UserApply } + */ + public UserApply getFriendApproving(Long uid, Long targetUid, boolean initiator) { + return lambdaQuery().eq(UserApply::getUid, uid) + .eq(UserApply::getTargetId, targetUid) + .eq(UserApply::getStatus, NoticeStatusEnum.UNTREATED.getStatus()) + .eq(UserApply::getType, RoomTypeEnum.FRIEND.getType()) + .notIn(initiator, UserApply::getDeleted, ApplyDeletedEnum.applyDeleted()) + .notIn(!initiator, UserApply::getDeleted, ApplyDeletedEnum.targetDeleted()) + .one(); + } - public void agree(Long applyId) { - lambdaUpdate() - .eq(UserApply::getId, applyId) - .set(UserApply::getStatus, ACCEPTED.getStatus()) - .set(UserApply::getUpdateTime, LocalDateTime.now()) - .update(); - } + public void agree(Long applyId) { + lambdaUpdate() + .eq(UserApply::getId, applyId) + .set(UserApply::getStatus, ACCEPTED.getStatus()) + .set(UserApply::getUpdateTime, LocalDateTime.now()) + .update(); + } - public void updateStatus(Long applyId, NoticeStatusEnum statusEnum) { - lambdaUpdate().set(UserApply::getStatus, statusEnum.getStatus()) - .set(UserApply::getUpdateTime, LocalDateTime.now()) - .eq(UserApply::getId,applyId) - .update(); - } + public void updateStatus(Long applyId, NoticeStatusEnum statusEnum) { + lambdaUpdate().set(UserApply::getStatus, statusEnum.getStatus()) + .set(UserApply::getUpdateTime, LocalDateTime.now()) + .eq(UserApply::getId, applyId) + .update(); + } - public void deleteApprove(Long applyId, ApplyDeletedEnum deletedEnum) { - lambdaUpdate().set(UserApply::getDeleted, deletedEnum.getCode()) - .set(UserApply::getUpdateTime, LocalDateTime.now()) - .eq(UserApply::getId, applyId) - .update(); - } + public void deleteApprove(Long applyId, ApplyDeletedEnum deletedEnum) { + lambdaUpdate().set(UserApply::getDeleted, deletedEnum.getCode()) + .set(UserApply::getUpdateTime, LocalDateTime.now()) + .eq(UserApply::getId, applyId) + .update(); + } public List getExistingUsers(Long roomId, HashSet uidList) { return lambdaQuery() diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserBackpackDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserBackpackDao.java index d1078a3d..4ffe61d1 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserBackpackDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserBackpackDao.java @@ -19,57 +19,57 @@ import java.util.List; */ @Service public class UserBackpackDao extends ServiceImpl { - private final ItemConfigDao itemConfigDao; + private final ItemConfigDao itemConfigDao; - public UserBackpackDao(ItemConfigDao itemConfigDao) { - this.itemConfigDao = itemConfigDao; - } + public UserBackpackDao(ItemConfigDao itemConfigDao) { + this.itemConfigDao = itemConfigDao; + } - public Integer getCountByValidItemId(Long uid, Long itemId) { - return Math.toIntExact(lambdaQuery().eq(UserBackpack::getUid, uid) - .eq(UserBackpack::getItemId, itemId) - .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) - .count()); - } + public Integer getCountByValidItemId(Long uid, Long itemId) { + return Math.toIntExact(lambdaQuery().eq(UserBackpack::getUid, uid) + .eq(UserBackpack::getItemId, itemId) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .count()); + } - public UserBackpack getFirstValidItem(Long uid, Long itemId) { - LambdaQueryWrapper wrapper = new QueryWrapper().lambda() - .eq(UserBackpack::getUid, uid) - .eq(UserBackpack::getItemId, itemId) - .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) - .last("limit 1"); - return getOne(wrapper); - } + public UserBackpack getFirstValidItem(Long uid, Long itemId) { + LambdaQueryWrapper wrapper = new QueryWrapper().lambda() + .eq(UserBackpack::getUid, uid) + .eq(UserBackpack::getItemId, itemId) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .last("limit 1"); + return getOne(wrapper); + } - public boolean invalidItem(Long id) { - UserBackpack update = new UserBackpack(); - update.setId(id); - update.setStatus(YesOrNoEnum.YES.getStatus()); - return updateById(update); - } + public boolean invalidItem(Long id) { + UserBackpack update = new UserBackpack(); + update.setId(id); + update.setStatus(YesOrNoEnum.YES.getStatus()); + return updateById(update); + } - public List getByItemIds(Long uid, List itemIds) { - return lambdaQuery().eq(UserBackpack::getUid, uid) - .in(UserBackpack::getItemId, itemIds) - .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) - .list(); - } + public List getByItemIds(Long uid, List itemIds) { + return lambdaQuery().eq(UserBackpack::getUid, uid) + .in(UserBackpack::getItemId, itemIds) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .list(); + } - public List getByItemIds(List uids, List itemIds) { - return lambdaQuery().in(UserBackpack::getUid, uids) - .in(UserBackpack::getItemId, itemIds) - .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) - .list(); - } + public List getByItemIds(List uids, List itemIds) { + return lambdaQuery().in(UserBackpack::getUid, uids) + .in(UserBackpack::getItemId, itemIds) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .list(); + } - public UserBackpack getByIdp(String idempotent) { - return lambdaQuery().eq(UserBackpack::getIdempotent, idempotent).one(); - } + public UserBackpack getByIdp(String idempotent) { + return lambdaQuery().eq(UserBackpack::getIdempotent, idempotent).one(); + } - public long countByUidAndItemId(Long uid, Long itemId) { - return baseMapper.selectCount(new LambdaQueryWrapper() - .eq(UserBackpack::getUid, uid) - .eq(UserBackpack::getItemId, itemId) - ); - } + public long countByUidAndItemId(Long uid, Long itemId) { + return baseMapper.selectCount(new LambdaQueryWrapper() + .eq(UserBackpack::getUid, uid) + .eq(UserBackpack::getItemId, itemId) + ); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserDao.java index b20413ea..48b5bcf5 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserDao.java @@ -22,57 +22,57 @@ import java.util.Set; @Service public class UserDao extends ServiceImpl { - public User getByOpenId(String openId) { - LambdaQueryWrapper wrapper = new QueryWrapper().lambda().eq(User::getOpenId, openId); - return getOne(wrapper); - } + public User getByOpenId(String openId) { + LambdaQueryWrapper wrapper = new QueryWrapper().lambda().eq(User::getOpenId, openId); + return getOne(wrapper); + } - public void wearingBadge(Long uid, Long badgeId) { - User update = new User(); - update.setId(uid); - update.setItemId(badgeId); - updateById(update); - } + public void wearingBadge(Long uid, Long badgeId) { + User update = new User(); + update.setId(uid); + update.setItemId(badgeId); + updateById(update); + } public List getByDefUserId(List defUserIdLIst) { return lambdaQuery().in(User::getUserId, defUserIdLIst).list(); } - public User getByEmail(String email) { - return lambdaQuery().eq(User::getEmail, email).one(); - } + public User getByEmail(String email) { + return lambdaQuery().eq(User::getEmail, email).one(); + } - public List getMemberList() { - return lambdaQuery() - .eq(User::getState, NormalOrNoEnum.NORMAL.getStatus()) - //最近活跃的1000个人,可以用lastOptTime字段,但是该字段没索引,updateTime可平替 - .orderByDesc(User::getLastOptTime) - //毕竟是大群聊,人数需要做个限制 - .last("limit 1000") - .select(User::getId, User::getName, User::getAvatar, User::getAccount) - .list(); + public List getMemberList() { + return lambdaQuery() + .eq(User::getState, NormalOrNoEnum.NORMAL.getStatus()) + //最近活跃的1000个人,可以用lastOptTime字段,但是该字段没索引,updateTime可平替 + .orderByDesc(User::getLastOptTime) + //毕竟是大群聊,人数需要做个限制 + .last("limit 1000") + .select(User::getId, User::getName, User::getAvatar, User::getAccount) + .list(); - } + } - public int changeUserState(Long uid, Long userStateId) { - return baseMapper.changeUserState(uid, userStateId); - } + public int changeUserState(Long uid, Long userStateId) { + return baseMapper.changeUserState(uid, userStateId); + } - public List getFriend(String key) { - return baseMapper.getFriend("%" + key + "%"); - } + public List getFriend(String key) { + return baseMapper.getFriend("%" + key + "%"); + } - public Boolean existsByEmailAndIdNot(Long uid, String email) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(User::getEmail, email); + public Boolean existsByEmailAndIdNot(Long uid, String email) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(User::getEmail, email); - if(uid != null){ - wrapper.ne(User::getId, uid); - } - return baseMapper.selectCount(wrapper) > 0; - } + if (uid != null) { + wrapper.ne(User::getId, uid); + } + return baseMapper.selectCount(wrapper) > 0; + } - public List getByIds(Set uidSet) { - return baseMapper.selectBatchIds(uidSet); - } + public List getByIds(Set uidSet) { + return baseMapper.selectBatchIds(uidSet); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserEmojiDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserEmojiDao.java index 5c4a8fc1..ee53169b 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserEmojiDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserEmojiDao.java @@ -17,11 +17,11 @@ import java.util.List; @Service public class UserEmojiDao extends ServiceImpl { - public List listByUid(Long uid) { - return lambdaQuery().eq(UserEmoji::getUid, uid).list(); - } + public List listByUid(Long uid) { + return lambdaQuery().eq(UserEmoji::getUid, uid).list(); + } - public int countByUid(Long uid) { - return Math.toIntExact(lambdaQuery().eq(UserEmoji::getUid, uid).count()); - } + public int countByUid(Long uid) { + return Math.toIntExact(lambdaQuery().eq(UserEmoji::getUid, uid).count()); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserFriendDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserFriendDao.java index a538239b..8d7368a8 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserFriendDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserFriendDao.java @@ -55,6 +55,7 @@ public class UserFriendDao extends ServiceImpl { /** * 查询他不看我的好友 + * * @param uid 操作人 * @return */ @@ -70,6 +71,7 @@ public class UserFriendDao extends ServiceImpl { /** * 查询不让他看我的好友 + * * @param uid 操作人 * @return */ @@ -85,6 +87,7 @@ public class UserFriendDao extends ServiceImpl { /** * 查询我不看他的好友 + * * @param uid 操作人 * @return */ @@ -100,6 +103,7 @@ public class UserFriendDao extends ServiceImpl { /** * 查询仅聊天的好友 + * * @param uid 操作人 * @return */ @@ -123,10 +127,11 @@ public class UserFriendDao extends ServiceImpl { /** * TODO 这里也要重构 * 获取到当前登录人员的所有的好友的信息 + * * @return */ @Cacheable(cacheNames = "luohuo:friend", key = "'list:'+#uid") - public List getAllFriendIdsByUid(Long uid){ + public List getAllFriendIdsByUid(Long uid) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.select(UserFriend::getFriendUid) .eq(UserFriend::getUid, uid); @@ -135,6 +140,7 @@ public class UserFriendDao extends ServiceImpl { /** * 根据房间号+自己的id 定位好友关系 + * * @param roomId * @param uid * @return @@ -156,8 +162,9 @@ public class UserFriendDao extends ServiceImpl { /** * 当好友关系变更时清除缓存 + * * @param roomId 房间ID - * @param uid 用户ID + * @param uid 用户ID */ @CacheEvict(cacheNames = "luohuo:userFriend", key = "'room:'+#roomId+':uid:'+#uid") public void evictFriendCache(Long roomId, Long uid) { @@ -165,6 +172,7 @@ public class UserFriendDao extends ServiceImpl { /** * 查询所有好友房间 + * * @param roomIdList 房间id */ public List getAllRoomInfo(List roomIdList) { diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserPrivacyDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserPrivacyDao.java index 4f65236f..b2d9cbbc 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserPrivacyDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserPrivacyDao.java @@ -9,38 +9,38 @@ import org.springframework.transaction.annotation.Transactional; @Service public class UserPrivacyDao extends ServiceImpl { - - /** - * 获取用户隐私设置 - */ - public UserPrivacy getByUid(Long uid) { - return lambdaQuery().eq(UserPrivacy::getUid, uid).one(); - } - - /** - * 初始化用户隐私设置 - */ - @Transactional - public boolean initUserPrivacy(Long uid) { - UserPrivacy privacy = new UserPrivacy(); - privacy.setUid(uid); - privacy.setIsPrivate(false); - privacy.setAllowTempSession(true); - privacy.setSearchableByPhone(true); - privacy.setSearchableByAccount(true); - privacy.setSearchableByUsername(true); - privacy.setShowOnlineStatus(true); - privacy.setAllowAddFriend(true); - privacy.setAllowGroupInvite(true); - privacy.setHideProfile(false); - return save(privacy); - } - - /** - * 更新隐私设置 - */ - @Transactional - public boolean updatePrivacy(Long uid, PrivacySettingReq req) { + + /** + * 获取用户隐私设置 + */ + public UserPrivacy getByUid(Long uid) { + return lambdaQuery().eq(UserPrivacy::getUid, uid).one(); + } + + /** + * 初始化用户隐私设置 + */ + @Transactional + public boolean initUserPrivacy(Long uid) { + UserPrivacy privacy = new UserPrivacy(); + privacy.setUid(uid); + privacy.setIsPrivate(false); + privacy.setAllowTempSession(true); + privacy.setSearchableByPhone(true); + privacy.setSearchableByAccount(true); + privacy.setSearchableByUsername(true); + privacy.setShowOnlineStatus(true); + privacy.setAllowAddFriend(true); + privacy.setAllowGroupInvite(true); + privacy.setHideProfile(false); + return save(privacy); + } + + /** + * 更新隐私设置 + */ + @Transactional + public boolean updatePrivacy(Long uid, PrivacySettingReq req) { return lambdaUpdate() .eq(UserPrivacy::getUid, uid) .set(req.getIsPrivate() != null, UserPrivacy::getIsPrivate, req.getIsPrivate()) @@ -53,7 +53,7 @@ public class UserPrivacyDao extends ServiceImpl .set(req.getAllowGroupInvite() != null, UserPrivacy::getAllowGroupInvite, req.getAllowGroupInvite()) .set(req.getHideProfile() != null, UserPrivacy::getHideProfile, req.getHideProfile()) .update(); - } + } public Boolean checkAllowTempSession(Long uid) { return baseMapper.checkAllowTempSession(uid); diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserRoleDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserRoleDao.java index 51fb91a0..2180bfc8 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserRoleDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserRoleDao.java @@ -18,8 +18,8 @@ import java.util.stream.Collectors; @Service public class UserRoleDao extends ServiceImpl { - public Set listByUid(Long uid) { - return lambdaQuery() - .eq(UserRole::getUid, uid).select(UserRole::getUid).list().stream().map(UserRole::getUid).collect(Collectors.toSet()); - } + public Set listByUid(Long uid) { + return lambdaQuery() + .eq(UserRole::getUid, uid).select(UserRole::getUid).list().stream().map(UserRole::getUid).collect(Collectors.toSet()); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserStateDao.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserStateDao.java index 2e670856..e275fb7c 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserStateDao.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/dao/UserStateDao.java @@ -19,7 +19,7 @@ import java.util.List; public class UserStateDao extends ServiceImpl { @Cacheable(cacheNames = "luohuo:user", key = "'state'") - public List list(){ + public List list() { return lambdaQuery() .list(); } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedCommentMapper.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedCommentMapper.java new file mode 100644 index 00000000..f6bca81a --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedCommentMapper.java @@ -0,0 +1,12 @@ +package com.luohuo.flex.im.core.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.luohuo.flex.im.domain.entity.FeedComment; +import org.springframework.stereotype.Repository; + +/** + * 朋友圈评论 Mapper 接口 + */ +@Repository +public interface FeedCommentMapper extends BaseMapper { +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedLikeMapper.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedLikeMapper.java new file mode 100644 index 00000000..0dafc0cc --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/mapper/FeedLikeMapper.java @@ -0,0 +1,13 @@ +package com.luohuo.flex.im.core.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.luohuo.flex.im.domain.entity.FeedLike; +import org.springframework.stereotype.Repository; + +/** + * 朋友圈点赞 Mapper 接口 + */ +@Repository +public interface FeedLikeMapper extends BaseMapper { + +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedCommentService.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedCommentService.java new file mode 100644 index 00000000..7e116f42 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedCommentService.java @@ -0,0 +1,53 @@ +package com.luohuo.flex.im.core.user.service; + +import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; +import com.luohuo.flex.im.domain.vo.req.feed.FeedCommentReq; +import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedCommentVo; + +import java.util.List; + +/** + * 朋友圈评论服务 + */ +public interface FeedCommentService { + + /** + * 发表评论 + * @param uid 操作人 + * @param req 评论请求 + * @return 是否成功 + */ + Boolean addComment(Long uid, FeedCommentReq req); + + /** + * 删除评论 + * @param uid 操作人 + * @param commentId 评论ID + * @return 是否成功 + */ + Boolean delComment(Long uid, Long commentId); + + /** + * 分页查询朋友圈评论 + * @param feedId 朋友圈ID + * @param request 分页请求 + * @return 评论分页列表 + */ + CursorPageBaseResp getCommentPage(Long feedId, CursorPageBaseReq request); + + /** + * 获取朋友圈的评论列表(不分页,用于展示前几条) + * @param feedId 朋友圈ID + * @param limit 限制数量 + * @return 评论列表 + */ + List getCommentList(Long feedId, Integer limit); + + /** + * 获取朋友圈的评论数量 + * @param feedId 朋友圈ID + * @return 评论数量 + */ + Integer getCommentCount(Long feedId); +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedLikeService.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedLikeService.java new file mode 100644 index 00000000..c5ddee49 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedLikeService.java @@ -0,0 +1,42 @@ +package com.luohuo.flex.im.core.user.service; + +import com.luohuo.flex.im.domain.vo.resp.feed.FeedLikeVo; + +import java.util.List; + +/** + * 朋友圈点赞服务 + */ +public interface FeedLikeService { + + /** + * 点赞或取消点赞 + * @param uid 操作人 + * @param feedId 朋友圈ID + * @param actType 操作类型 1-点赞 2-取消点赞 + * @return 是否成功 + */ + Boolean setLike(Long uid, Long feedId, Integer actType); + + /** + * 获取朋友圈的点赞列表 + * @param feedId 朋友圈ID + * @return 点赞用户列表 + */ + List getLikeList(Long feedId); + + /** + * 获取朋友圈的点赞数量 + * @param feedId 朋友圈ID + * @return 点赞数量 + */ + Integer getLikeCount(Long feedId); + + /** + * 判断用户是否已点赞 + * @param uid 用户ID + * @param feedId 朋友圈ID + * @return 是否已点赞 + */ + Boolean hasLiked(Long uid, Long feedId); +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedNotifyService.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedNotifyService.java new file mode 100644 index 00000000..78b74874 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedNotifyService.java @@ -0,0 +1,40 @@ +package com.luohuo.flex.im.core.user.service; + +import com.luohuo.flex.im.domain.entity.FeedComment; +import com.luohuo.flex.im.domain.entity.FeedLike; + +/** + * 朋友圈通知服务 + */ +public interface FeedNotifyService { + + /** + * 发送点赞通知 + * 通知逻辑: + * 1. 获取朋友圈发布人的所有好友 + * 2. 过滤出与该朋友圈有互动关系的好友(已点赞或已评论) + * 3. 发送 WebSocket 通知给这些好友 + * + * @param feedLike 点赞记录 + */ + void notifyFeedLike(FeedLike feedLike); + + /** + * 发送评论通知 + * 通知逻辑: + * 1. 获取朋友圈发布人的所有好友 + * 2. 过滤出与该朋友圈有互动关系的好友(已点赞或已评论) + * 3. 发送 WebSocket 通知给这些好友 + * + * @param feedComment 评论记录 + */ + void notifyFeedComment(FeedComment feedComment); + + /** + * 取消点赞通知 + * + * @param feedId 朋友圈ID + * @param uid 点赞人UID + */ + void notifyFeedUnlike(Long feedId, Long uid); +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedService.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedService.java index 10e69eb2..9adf309a 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedService.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/FeedService.java @@ -39,10 +39,11 @@ public interface FeedService { /** * 查看朋友圈 - * @param feedId + * @param feedId 朋友圈ID + * @param uid 当前用户ID * @return */ - FeedVo feedDetail(Long feedId); + FeedVo feedDetail(Long feedId, Long uid); /** * 获取朋友圈的可见权限 diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedCommentServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedCommentServiceImpl.java new file mode 100644 index 00000000..9f837cbc --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedCommentServiceImpl.java @@ -0,0 +1,202 @@ +package com.luohuo.flex.im.core.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import com.luohuo.basic.cache.redis2.CacheResult; +import com.luohuo.basic.cache.repository.CachePlusOps; +import com.luohuo.basic.exception.BizException; +import com.luohuo.basic.model.cache.CacheHashKey; +import com.luohuo.flex.common.cache.common.FeedCommentCacheKeyBuilder; +import com.luohuo.flex.im.core.user.dao.FeedCommentDao; +import com.luohuo.flex.im.core.user.dao.FeedDao; +import com.luohuo.flex.im.core.user.service.FeedCommentService; +import com.luohuo.flex.im.core.user.service.FeedNotifyService; +import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache; +import com.luohuo.flex.im.domain.dto.SummeryInfoDTO; +import com.luohuo.flex.im.domain.entity.Feed; +import com.luohuo.flex.im.domain.entity.FeedComment; +import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; +import com.luohuo.flex.im.domain.vo.req.feed.FeedCommentReq; +import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedCommentVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 朋友圈评论服务实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FeedCommentServiceImpl implements FeedCommentService { + + private final FeedCommentDao feedCommentDao; + private final FeedDao feedDao; + private final CachePlusOps cachePlusOps; + private final UserSummaryCache userSummaryCache; + private final FeedNotifyService feedNotifyService; + + @Override + @Transactional + public Boolean addComment(Long uid, FeedCommentReq req) { + // 1. 校验朋友圈是否存在 + Feed feed = feedDao.getById(req.getFeedId()); + if (Objects.isNull(feed)) { + throw new BizException("朋友圈不存在"); + } + + // 2. 如果是回复评论,校验被回复的评论是否存在 + if (Objects.nonNull(req.getReplyCommentId())) { + FeedComment replyComment = feedCommentDao.getById(req.getReplyCommentId()); + if (Objects.isNull(replyComment)) { + throw new BizException("被回复的评论不存在"); + } + } + + // 3. 创建评论 + FeedComment feedComment = FeedComment.builder() + .feedId(req.getFeedId()) + .uid(uid) + .replyCommentId(req.getReplyCommentId()) + .replyUid(req.getReplyUid()) + .content(req.getContent()) + .build(); + feedCommentDao.save(feedComment); + + // 4. 发送评论通知 + feedNotifyService.notifyFeedComment(feedComment); + + // 5. 清除缓存 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + cachePlusOps.del(FeedCommentCacheKeyBuilder.build(req.getFeedId())); + } + }); + + return true; + } + + @Override + @Transactional + public Boolean delComment(Long uid, Long commentId) { + // 1. 查询评论是否存在 + FeedComment comment = feedCommentDao.getById(commentId); + if (Objects.isNull(comment)) { + throw new BizException("评论不存在"); + } + + // 2. 校验权限 + if (!comment.getUid().equals(uid)) { + throw new BizException("无权删除该评论"); + } + + // 3. 逻辑删除评论 + feedCommentDao.removeById(commentId); + + // 4. 清除缓存(在事务提交后执行,确保数据库已更新) + Long feedId = comment.getFeedId(); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + cachePlusOps.del(FeedCommentCacheKeyBuilder.build(feedId)); + } + }); + + return true; + } + + @Override + public CursorPageBaseResp getCommentPage(Long feedId, CursorPageBaseReq request) { + // 1. 分页查询评论 + CursorPageBaseResp commentPage = feedCommentDao.getCommentPage(feedId, request); + + // 2. 组装用户信息 + List commentVoList = buildCommentVoList(commentPage.getList()); + + // 3. 返回结果 + CursorPageBaseResp result = new CursorPageBaseResp<>(); + result.setList(commentVoList); + result.setCursor(commentPage.getCursor()); + result.setIsLast(commentPage.getIsLast()); + return result; + } + + @Override + public List getCommentList(Long feedId, Integer limit) { + // 1. 从缓存获取评论列表 + CacheHashKey hashKey = FeedCommentCacheKeyBuilder.build(feedId); + CacheResult> result = cachePlusOps.get(hashKey, t -> feedCommentDao.getCommentListByFeedId(feedId)); + List commentList = result.getValue(); + + if (CollUtil.isEmpty(commentList)) { + return new ArrayList<>(); + } + + // 2. 限制数量 + if (Objects.nonNull(limit) && commentList.size() > limit) { + commentList = commentList.subList(0, limit); + } + + // 3. 组装用户信息 + return buildCommentVoList(commentList); + } + + @Override + public Integer getCommentCount(Long feedId) { + return feedCommentDao.getCommentCount(feedId); + } + + /** + * 组装评论VO列表(包含用户信息) + */ + private List buildCommentVoList(List commentList) { + if (CollUtil.isEmpty(commentList)) { + return new ArrayList<>(); + } + + // 1. 收集所有需要查询的用户ID + Set uidSet = new HashSet<>(); + commentList.forEach(comment -> { + uidSet.add(comment.getUid()); + if (Objects.nonNull(comment.getReplyUid())) { + uidSet.add(comment.getReplyUid()); + } + }); + + // 2. 批量获取用户信息 + Map userInfoMap = userSummaryCache.getBatch(new ArrayList<>(uidSet)); + + // 3. 组装返回结果 + return commentList.stream() + .map(comment -> { + FeedCommentVo vo = new FeedCommentVo(); + BeanUtil.copyProperties(comment, vo); + + // 设置评论人信息 + SummeryInfoDTO userInfo = userInfoMap.get(comment.getUid()); + if (Objects.nonNull(userInfo)) { + vo.setUserName(userInfo.getName()); + vo.setUserAvatar(userInfo.getAvatar()); + } + + // 设置被回复人信息 + if (Objects.nonNull(comment.getReplyUid())) { + SummeryInfoDTO replyUserInfo = userInfoMap.get(comment.getReplyUid()); + if (Objects.nonNull(replyUserInfo)) { + vo.setReplyUserName(replyUserInfo.getName()); + } + } + + return vo; + }) + .collect(Collectors.toList()); + } +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedLikeServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedLikeServiceImpl.java new file mode 100644 index 00000000..253aa11e --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedLikeServiceImpl.java @@ -0,0 +1,139 @@ +package com.luohuo.flex.im.core.user.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.luohuo.basic.cache.redis2.CacheResult; +import com.luohuo.basic.cache.repository.CachePlusOps; +import com.luohuo.basic.exception.BizException; +import com.luohuo.basic.model.cache.CacheHashKey; +import com.luohuo.flex.common.cache.common.FeedLikeCacheKeyBuilder; +import com.luohuo.flex.im.core.user.dao.FeedDao; +import com.luohuo.flex.im.core.user.dao.FeedLikeDao; +import com.luohuo.flex.im.core.user.service.FeedLikeService; +import com.luohuo.flex.im.core.user.service.FeedNotifyService; +import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache; +import com.luohuo.flex.im.domain.dto.SummeryInfoDTO; +import com.luohuo.flex.im.domain.entity.Feed; +import com.luohuo.flex.im.domain.entity.FeedLike; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedLikeVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 朋友圈点赞服务实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FeedLikeServiceImpl implements FeedLikeService { + + private final FeedLikeDao feedLikeDao; + private final FeedDao feedDao; + private final CachePlusOps cachePlusOps; + private final UserSummaryCache userSummaryCache; + private final FeedNotifyService feedNotifyService; + + @Override + @Transactional + public Boolean setLike(Long uid, Long feedId, Integer actType) { + // 1. 校验朋友圈是否存在 + Feed feed = feedDao.getById(feedId); + if (Objects.isNull(feed)) { + throw new BizException("朋友圈不存在"); + } + + // 2. 查询是否已经点赞过 + FeedLike oldLike = feedLikeDao.get(uid, feedId); + + // 3. 根据操作类型处理 + if (actType == 1) { + // 点赞 + if (Objects.isNull(oldLike)) { + // 新增点赞记录 + FeedLike feedLike = FeedLike.builder() + .feedId(feedId) + .uid(uid) + .build(); + feedLikeDao.save(feedLike); + + // 发送点赞通知 + feedNotifyService.notifyFeedLike(feedLike); + } + // 如果已经点赞过,则不做任何操作(幂等性) + } else if (actType == 2) { + // 取消点赞 + if (Objects.isNull(oldLike)) { + // 没有点赞记录,直接返回 + return true; + } + feedLikeDao.removeById(oldLike.getId()); + + // 发送取消点赞通知 + feedNotifyService.notifyFeedUnlike(feedId, uid); + } else { + throw new BizException("操作类型错误"); + } + + // 4. 清除缓存(在事务提交后执行,确保数据库已更新) + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + cachePlusOps.del(FeedLikeCacheKeyBuilder.build(feedId)); + log.info("✅ 点赞缓存已清除,朋友圈ID: {}", feedId); + } + }); + + return true; + } + + @Override + public List getLikeList(Long feedId) { + // 1. 从缓存获取点赞用户ID列表 + CacheHashKey hashKey = FeedLikeCacheKeyBuilder.build(feedId); + CacheResult> result = cachePlusOps.get(hashKey, t -> feedLikeDao.getLikeUidList(feedId)); + List likeUidList = result.getValue(); + + if (CollUtil.isEmpty(likeUidList)) { + return new ArrayList<>(); + } + + // 2. 批量获取用户信息 + Map userInfoMap = userSummaryCache.getBatch(likeUidList); + + // 3. 组装返回结果 + return likeUidList.stream() + .map(uid -> { + SummeryInfoDTO userInfo = userInfoMap.get(uid); + if (Objects.isNull(userInfo)) { + return null; + } + return FeedLikeVo.builder() + .uid(uid) + .userName(userInfo.getName()) + .userAvatar(userInfo.getAvatar()) + .build(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public Integer getLikeCount(Long feedId) { + return feedLikeDao.getLikeCount(feedId); + } + + @Override + public Boolean hasLiked(Long uid, Long feedId) { + FeedLike feedLike = feedLikeDao.get(uid, feedId); + return Objects.nonNull(feedLike); + } +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedNotifyServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedNotifyServiceImpl.java new file mode 100644 index 00000000..7add5e62 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedNotifyServiceImpl.java @@ -0,0 +1,160 @@ +package com.luohuo.flex.im.core.user.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.luohuo.flex.im.core.user.dao.FeedCommentDao; +import com.luohuo.flex.im.core.user.dao.FeedDao; +import com.luohuo.flex.im.core.user.dao.FeedLikeDao; +import com.luohuo.flex.im.core.user.dao.UserFriendDao; +import com.luohuo.flex.im.core.user.service.FeedNotifyService; +import com.luohuo.flex.im.domain.entity.Feed; +import com.luohuo.flex.im.domain.entity.FeedComment; +import com.luohuo.flex.im.domain.entity.FeedLike; +import com.luohuo.flex.model.entity.WSRespTypeEnum; +import com.luohuo.flex.model.entity.WsBaseResp; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 朋友圈通知服务实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FeedNotifyServiceImpl implements FeedNotifyService { + + private final FeedLikeDao feedLikeDao; + private final FeedCommentDao feedCommentDao; + private final FeedDao feedDao; + private final UserFriendDao userFriendDao; + private final PushService pushService; + + @Override + public void notifyFeedLike(FeedLike feedLike) { + try { + // 1. 获取跟朋友圈有关系的好友 + Set interactiveUidSet = getInteractiveUid(feedLike.getFeedId(), feedLike.getUid()); + if (interactiveUidSet == null) return; + + // 2. 构建通知消息 - 点赞:uid、feedId(无 comment 字段) + Map data = new HashMap<>(); + data.put("uid", feedLike.getUid()); + data.put("feedId", feedLike.getFeedId()); + + sendFeedNotify(data, interactiveUidSet, feedLike.getUid(), "点赞"); + } catch (Exception e) { + log.error("❌ 发送点赞通知失败", e); + } + } + + @Nullable + private Set getInteractiveUid(Long feedId, Long operatorUid) { + // 1. 获取朋友圈信息 + Feed feed = feedDao.getById(feedId); + if (feed == null) { + return null; + } + + // 2. 获取朋友圈发布人的所有好友 + List friendUidList = userFriendDao.getAllFriendIdsByUid(feed.getUid()); + if (CollUtil.isEmpty(friendUidList)) { + return null; + } + + // 3. 获取与该朋友圈有互动关系的好友(已点赞或已评论) + Set interactiveUidSet = getInteractiveUidSet(feedId, friendUidList); + + // 4. 添加发朋友圈的人 + interactiveUidSet.add(feed.getUid()); + + // 5. 添加操作人自己(这样操作人可以在其他设备上实时看到自己的操作) + interactiveUidSet.add(operatorUid); + + if (CollUtil.isEmpty(interactiveUidSet)) { + return null; + } + return interactiveUidSet; + } + + @Override + public void notifyFeedComment(FeedComment feedComment) { + try { + // 1. 获取朋友圈信息 + Set interactiveUidSet = getInteractiveUid(feedComment.getFeedId(), feedComment.getUid()); + if (interactiveUidSet == null) return; + + // 2. 构建通知消息 - 评论:uid、feedId、comment、replyCommentId + Map data = new HashMap<>(); + data.put("replyCommentId", feedComment.getReplyCommentId()); + data.put("uid", feedComment.getUid()); + data.put("feedId", feedComment.getFeedId()); + data.put("comment", feedComment.getContent()); + + sendFeedNotify(data, interactiveUidSet, feedComment.getUid(), "评论"); + } catch (Exception e) { + log.error("❌ 发送评论通知失败", e); + } + } + + /** + * 发送朋友圈通知(统一处理点赞和评论) + * 前端通过判断 comment 字段是否存在来区分点赞还是评论 + */ + private void sendFeedNotify(Map data, Set interactiveUidSet, Long operatorUid, String type) { + WsBaseResp> resp = new WsBaseResp<>(); + resp.setType(WSRespTypeEnum.FEED_NOTIFY.getType()); + resp.setData(data); + + pushService.sendPushMsg(resp, new ArrayList<>(interactiveUidSet), operatorUid); + + Long feedId = (Long) data.get("feedId"); + log.info("✅ {}通知已发送,朋友圈ID: {},操作人: {},通知人数: {}", type, feedId, operatorUid, interactiveUidSet.size()); + } + + @Override + public void notifyFeedUnlike(Long feedId, Long uid) { + try { + // 1. 获取跟朋友圈有关系的好友 + Set interactiveUidSet = getInteractiveUid(feedId, uid); + if (interactiveUidSet == null) return; + + // 2. 构建通知消息 - 取消点赞:uid、feedId、isUnlike=true(无 comment 字段) + Map data = new HashMap<>(); + data.put("uid", uid); + data.put("feedId", feedId); + data.put("isUnlike", true); // 标记为取消点赞 + + sendFeedNotify(data, interactiveUidSet, uid, "取消点赞"); + } catch (Exception e) { + log.error("❌ 发送取消点赞通知失败", e); + } + } + + /** + * 获取与该朋友圈有互动关系的好友集合 + * 互动关系包括:已点赞或已评论 + */ + private Set getInteractiveUidSet(Long feedId, List friendUidList) { + Set interactiveUidSet = new HashSet<>(); + + // 1. 获取所有点赞人 + List likeUidList = feedLikeDao.getLikeUidList(feedId); + interactiveUidSet.addAll(likeUidList); + + // 2. 获取所有评论人 + List commentList = feedCommentDao.getCommentListByFeedId(feedId); + Set commentUidSet = commentList.stream() + .map(FeedComment::getUid) + .collect(Collectors.toSet()); + interactiveUidSet.addAll(commentUidSet); + + // 3. 过滤出是好友的用户 + interactiveUidSet.retainAll(friendUidList); + + return interactiveUidSet; + } +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java index 9f5ee6c6..7e181be3 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/user/service/impl/FeedServiceImpl.java @@ -7,12 +7,17 @@ import com.luohuo.basic.cache.redis2.CacheResult; import com.luohuo.basic.cache.repository.CachePlusOps; import com.luohuo.basic.exception.BizException; import com.luohuo.basic.model.cache.CacheHashKey; +import com.luohuo.flex.common.cache.common.FeedCommentCacheKeyBuilder; +import com.luohuo.flex.common.cache.common.FeedLikeCacheKeyBuilder; import com.luohuo.flex.common.cache.common.FeedMediaRelCacheKeyBuilder; import com.luohuo.flex.common.cache.common.FeedTargetRelCacheKeyBuilder; +import com.luohuo.flex.im.domain.entity.FeedLike; import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp; import com.luohuo.flex.im.core.chat.service.adapter.MemberAdapter; +import com.luohuo.flex.im.core.user.dao.FeedCommentDao; import com.luohuo.flex.im.core.user.dao.FeedDao; +import com.luohuo.flex.im.core.user.dao.FeedLikeDao; import com.luohuo.flex.im.core.user.dao.FeedMediaDao; import com.luohuo.flex.im.core.user.dao.FeedTargetDao; import com.luohuo.flex.im.core.user.dao.UserFriendDao; @@ -24,18 +29,19 @@ import com.luohuo.flex.im.domain.enums.FeedPermissionEnum; import com.luohuo.flex.im.domain.vo.req.feed.FeedParam; import com.luohuo.flex.im.domain.vo.req.feed.FeedPermission; import com.luohuo.flex.im.domain.vo.req.feed.FeedVo; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedLikeVo; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedCommentVo; +import com.luohuo.flex.im.domain.dto.SummeryInfoDTO; import com.luohuo.flex.im.core.user.service.FeedService; +import com.luohuo.flex.im.core.user.service.FeedCommentService; import com.luohuo.flex.im.core.user.service.UserTargetRelService; +import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -53,17 +59,54 @@ public class FeedServiceImpl implements FeedService { private final UserFriendDao userFriendDao; private final CachePlusOps cachePlusOps; private final FeedTargetDao feedTargetDao; + private final FeedCommentDao feedCommentDao; + private final FeedCommentService feedCommentService; + private final FeedLikeDao feedLikeDao; + private final UserSummaryCache userSummaryCache; /** * @param feedList 朋友圈基础内容 + * @param currentUid 当前用户ID * @return */ - private List buildFeedResp(List feedList) { + private List buildFeedResp(List feedList, Long currentUid) { + return buildFeedResp(feedList, false, currentUid); + } + + /** + * @param feedList 朋友圈基础内容 + * @param currentUid 当前用户ID(用于判断是否已点赞) + * @return + */ + private List buildFeedResp(List feedList, boolean isDetail, Long currentUid) { List feedVos = new ArrayList<>(); + + // 批量获取所有发布者的用户信息 + Set userIds = feedList.stream().map(Feed::getUid).collect(Collectors.toSet()); + Map userInfoMap = userSummaryCache.getBatch(new ArrayList<>(userIds)); + + // 批量获取当前用户对所有朋友圈的点赞状态 + Map likeStatusMap = new HashMap<>(); + if (CollUtil.isNotEmpty(feedList)) { + List feedIds = feedList.stream().map(Feed::getId).collect(Collectors.toList()); + for (Long feedId : feedIds) { + FeedLike like = feedLikeDao.get(currentUid, feedId); + likeStatusMap.put(feedId, Objects.nonNull(like)); + } + } + for (Feed feed : feedList) { FeedVo feedVo = new FeedVo(); BeanUtil.copyProperties(feed, feedVo); + // 添加发布者信息 + SummeryInfoDTO userInfo = userInfoMap.get(feed.getUid()); + if (ObjectUtil.isNotNull(userInfo)) { + feedVo.setUserName(userInfo.getName()); + feedVo.setUserAvatar(userInfo.getAvatar()); + } + + // 添加媒体信息 if(!feed.getMediaType().equals(FeedEnum.WORD.getType())){ CacheHashKey hashKey = FeedMediaRelCacheKeyBuilder.build(feed.getId()); CacheResult> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feed.getId())); @@ -72,6 +115,46 @@ public class FeedServiceImpl implements FeedService { feedVo.setUrls(mediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList())); } } + + // 添加点赞信息 + Integer likeCount = feedLikeDao.getLikeCount(feed.getId()); + feedVo.setLikeCount(likeCount); + + // 设置当前用户是否已点赞 + feedVo.setHasLiked(likeStatusMap.getOrDefault(feed.getId(), false)); + + // 获取点赞列表(返回全部,前端按高度显示) + List likeUidList = feedLikeDao.getLikeUidList(feed.getId()); + + if (CollUtil.isNotEmpty(likeUidList)) { + Map likeUserInfoMap = userSummaryCache.getBatch(likeUidList); + List likeList = likeUidList.stream() + .map(uid -> { + SummeryInfoDTO likeUserInfo = likeUserInfoMap.get(uid); + if (ObjectUtil.isNull(likeUserInfo)) { + return null; + } + return FeedLikeVo.builder() + .uid(uid) + .userName(likeUserInfo.getName()) + .userAvatar(likeUserInfo.getAvatar()) + .build(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + feedVo.setLikeList(likeList); + } + + // 添加评论数量 + Integer commentCount = feedCommentDao.getCommentCount(feed.getId()); + feedVo.setCommentCount(commentCount); + + // 获取评论列表(返回全部,前端按高度显示) + List commentList = feedCommentService.getCommentList(feed.getId(), null); + if (CollUtil.isNotEmpty(commentList)) { + feedVo.setCommentList(commentList); + } + feedVos.add(feedVo); } return feedVos; @@ -123,7 +206,7 @@ public class FeedServiceImpl implements FeedService { }).collect(Collectors.toList()); // 4. 合并朋友圈内容 - List result = buildFeedResp(filteredFeeds); + List result = buildFeedResp(filteredFeeds, uid); return CursorPageBaseResp.init(page, result, page.getTotal()); } @@ -274,14 +357,18 @@ public class FeedServiceImpl implements FeedService { */ @Transactional public Boolean delFeed(Long feedId){ - // 1. 首先将朋友圈素材、权限删除 + // 1. 首先将朋友圈素材、权限、评论、点赞删除 feedMediaDao.delMediaByFeedId(feedId); feedTargetDao.delByFeedId(feedId); + feedCommentDao.delByFeedId(feedId); + feedLikeDao.delByFeedId(feedId); feedDao.removeById(feedId); // 2. 清空缓存 cachePlusOps.del(FeedTargetRelCacheKeyBuilder.build(feedId)); cachePlusOps.del(FeedMediaRelCacheKeyBuilder.build(feedId)); + cachePlusOps.del(FeedCommentCacheKeyBuilder.build(feedId)); + cachePlusOps.del(FeedLikeCacheKeyBuilder.build(feedId)); return true; } @@ -291,24 +378,19 @@ public class FeedServiceImpl implements FeedService { /** * 查看朋友圈 - * @param feedId + * @param feedId 朋友圈ID + * @param uid 当前用户ID * @return */ - public FeedVo feedDetail(Long feedId) { - FeedVo feed = getDetail(feedId); - - if(!feed.getMediaType().equals(FeedEnum.WORD.getType())){ - // 使用带回调的方式,自动处理缓存读写 - CacheHashKey hashKey = FeedMediaRelCacheKeyBuilder.build(feedId); - CacheResult> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feedId)); - List feedMediaList = result.getValue(); - - if (CollUtil.isNotEmpty(feedMediaList)){ - feed.setUrls(feedMediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList())); - } + public FeedVo feedDetail(Long feedId, Long uid) { + Feed feed = feedDao.getById(feedId); + if (ObjectUtil.isNull(feed)) { + return null; } - return feed; + // 使用 buildFeedResp 方法构建详情页的响应(isDetail=true,显示全部点赞) + List feedVos = buildFeedResp(List.of(feed), true, uid); + return feedVos.isEmpty() ? null : feedVos.get(0); } /** @@ -317,7 +399,7 @@ public class FeedServiceImpl implements FeedService { * @return */ public FeedPermission getFeedPermission(Long uid, Long feedId) { - FeedVo feedVo = feedDetail(feedId); + FeedVo feedVo = feedDetail(feedId, uid); if(ObjectUtil.isNull(feedVo)){ throw new RuntimeException("请选择朋友圈!"); } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/service/tenant/impl/EmailServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/service/tenant/impl/EmailServiceImpl.java index d196bece..6a870138 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/service/tenant/impl/EmailServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/service/tenant/impl/EmailServiceImpl.java @@ -23,7 +23,7 @@ import com.luohuo.flex.im.service.tenant.EmailService; import java.time.LocalDateTime; /** - * 邮箱服务 TODO 整合实现 + * 邮箱服务 */ @Slf4j @Service diff --git a/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/user/FeedController.java b/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/user/FeedController.java index 6da03da9..b71f1a80 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/user/FeedController.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/user/FeedController.java @@ -1,15 +1,23 @@ package com.luohuo.flex.im.controller.user; +import com.luohuo.flex.im.domain.vo.req.feed.FeedCommentPageReq; +import com.luohuo.flex.im.domain.vo.req.feed.FeedCommentReq; +import com.luohuo.flex.im.domain.vo.req.feed.FeedLikeReq; import com.luohuo.flex.im.domain.vo.req.feed.FeedReq; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedCommentVo; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedLikeVo; import io.swagger.v3.oas.annotations.tags.Tag; import com.luohuo.basic.base.R; import com.luohuo.basic.context.ContextUtil; +import com.luohuo.basic.exception.BizException; import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp; import com.luohuo.flex.model.vo.query.OperParam; import com.luohuo.flex.im.domain.vo.req.feed.FeedParam; import com.luohuo.flex.im.domain.vo.req.feed.FeedPermission; import com.luohuo.flex.im.domain.vo.req.feed.FeedVo; +import com.luohuo.flex.im.core.user.service.FeedCommentService; +import com.luohuo.flex.im.core.user.service.FeedLikeService; import com.luohuo.flex.im.core.user.service.FeedService; import io.swagger.v3.oas.annotations.Operation; import jakarta.annotation.Resource; @@ -20,8 +28,12 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.Map; + /** * 发布朋友圈、编辑朋友圈、设置谁可见、谁不可见、仅聊天、不看他、不让他看我 */ @@ -33,6 +45,12 @@ public class FeedController { @Resource private FeedService feedService; + @Resource + private FeedCommentService feedCommentService; + + @Resource + private FeedLikeService feedLikeService; + @PostMapping("list") @Operation(summary = "朋友圈列表") public R> list(@Valid @RequestBody CursorPageBaseReq request) { @@ -66,6 +84,72 @@ public class FeedController { @GetMapping("detail") @Operation(summary = "用户查看详情") public R feedDetail(FeedReq feedReq) { - return R.success(feedService.feedDetail(feedReq.getFeedId())); + return R.success(feedService.feedDetail(feedReq.getFeedId(), ContextUtil.getUid())); + } + + // ==================== 评论相关接口 ==================== + + @PostMapping("comment/add") + @Operation(summary = "发表评论") + public R addComment(@Valid @RequestBody FeedCommentReq req) { + return R.success(feedCommentService.addComment(ContextUtil.getUid(), req)); + } + + @PostMapping("comment/delete") + @Operation(summary = "删除评论") + public R delComment(@RequestParam(value = "commentId", required = false) Long commentId, + @RequestBody(required = false) Map body) { + // 支持两种方式:URL参数或请求体 + Long id = commentId != null ? commentId : (body != null ? body.get("commentId") : null); + if (id == null) { + throw new BizException("缺少必须的[Long]类型的参数[commentId]"); + } + return R.success(feedCommentService.delComment(ContextUtil.getUid(), id)); + } + + @PostMapping("comment/list") + @Operation(summary = "分页查询评论列表") + public R> getCommentPage(@Valid @RequestBody FeedCommentPageReq req) { + CursorPageBaseReq pageReq = new CursorPageBaseReq(); + pageReq.setCursor(req.getCursor()); + pageReq.setPageSize(req.getPageSize()); + return R.success(feedCommentService.getCommentPage(req.getFeedId(), pageReq)); + } + + @GetMapping("comment/count") + @Operation(summary = "获取评论数量") + public R getCommentCount(@RequestParam Long feedId) { + return R.success(feedCommentService.getCommentCount(feedId)); + } + + @GetMapping("comment/all") + @Operation(summary = "获取所有评论列表(不分页)") + public R> getAllComments(@RequestParam Long feedId) { + return R.success(feedCommentService.getCommentList(feedId, null)); + } + + // ==================== 点赞相关接口 ==================== + @PostMapping("like/toggle") + @Operation(summary = "点赞或取消点赞") + public R toggleLike(@Valid @RequestBody FeedLikeReq req) { + return R.success(feedLikeService.setLike(ContextUtil.getUid(), req.getFeedId(), req.getActType())); + } + + @GetMapping("like/list") + @Operation(summary = "获取点赞用户列表") + public R> getLikeList(@RequestParam Long feedId) { + return R.success(feedLikeService.getLikeList(feedId)); + } + + @GetMapping("like/count") + @Operation(summary = "获取点赞数量") + public R getLikeCount(@RequestParam Long feedId) { + return R.success(feedLikeService.getLikeCount(feedId)); + } + + @GetMapping("like/hasLiked") + @Operation(summary = "判断是否已点赞") + public R hasLiked(@RequestParam Long feedId) { + return R.success(feedLikeService.hasLiked(ContextUtil.getUid(), feedId)); } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedComment.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedComment.java new file mode 100644 index 00000000..0241c288 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedComment.java @@ -0,0 +1,60 @@ +package com.luohuo.flex.im.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.luohuo.basic.base.entity.SuperEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 朋友圈评论表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("im_feed_comment") +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FeedComment extends SuperEntity { + + private static final long serialVersionUID = 1L; + + /** + * 朋友圈ID + */ + @Schema(description = "朋友圈ID") + @TableField("feed_id") + private Long feedId; + + /** + * 评论人uid + */ + @Schema(description = "评论人uid") + @TableField("uid") + private Long uid; + + /** + * 回复的评论ID(如果是回复评论,则有值;如果是直接评论朋友圈,则为null) + */ + @Schema(description = "回复的评论ID") + @TableField("reply_comment_id") + private Long replyCommentId; + + /** + * 被回复人的uid(如果是回复评论,则有值) + */ + @Schema(description = "被回复人的uid") + @TableField("reply_uid") + private Long replyUid; + + /** + * 评论内容 + */ + @Schema(description = "评论内容") + @TableField("content") + private String content; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedLike.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedLike.java new file mode 100644 index 00000000..5f60b600 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/entity/FeedLike.java @@ -0,0 +1,61 @@ +package com.luohuo.flex.im.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 朋友圈点赞表 + */ +@Data +@TableName("im_feed_like") +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FeedLike implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 朋友圈ID + */ + @Schema(description = "朋友圈ID") + @TableField("feed_id") + private Long feedId; + + /** + * 点赞人uid + */ + @Schema(description = "点赞人uid") + @TableField("uid") + private Long uid; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + /** + * 创建者 + */ + @Schema(description = "创建者") + @TableField("create_by") + private Long createBy; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentPageReq.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentPageReq.java new file mode 100644 index 00000000..cd91ddbf --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentPageReq.java @@ -0,0 +1,23 @@ +package com.luohuo.flex.im.domain.vo.req.feed; + +import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 朋友圈评论分页请求 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +public class FeedCommentPageReq extends CursorPageBaseReq { + + @NotNull(message = "朋友圈ID不能为空") + @Schema(description = "朋友圈ID") + private Long feedId; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentReq.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentReq.java new file mode 100644 index 00000000..a58f8225 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedCommentReq.java @@ -0,0 +1,34 @@ +package com.luohuo.flex.im.domain.vo.req.feed; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 朋友圈评论请求 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FeedCommentReq { + + @NotNull(message = "朋友圈ID不能为空") + @Schema(description = "朋友圈ID") + private Long feedId; + + @Schema(description = "回复的评论ID(如果是回复评论,则有值;如果是直接评论朋友圈,则为null)") + private Long replyCommentId; + + @Schema(description = "被回复人的uid(如果是回复评论,则有值)") + private Long replyUid; + + @NotNull(message = "评论内容不能为空") + @Size(min = 1, max = 500, message = "评论内容必须在1到500个字符之间") + @Schema(description = "评论内容") + private String content; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedLikeReq.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedLikeReq.java new file mode 100644 index 00000000..b600a9ca --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedLikeReq.java @@ -0,0 +1,26 @@ +package com.luohuo.flex.im.domain.vo.req.feed; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 朋友圈点赞请求 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FeedLikeReq { + + @NotNull(message = "朋友圈ID不能为空") + @Schema(description = "朋友圈ID") + private Long feedId; + + @NotNull(message = "操作类型不能为空") + @Schema(description = "操作类型 1-点赞 2-取消点赞") + private Integer actType; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedVo.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedVo.java index 85d3b8c7..e54dca57 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedVo.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/req/feed/FeedVo.java @@ -1,6 +1,8 @@ package com.luohuo.flex.im.domain.vo.req.feed; import com.luohuo.flex.im.domain.entity.Feed; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedLikeVo; +import com.luohuo.flex.im.domain.vo.resp.feed.FeedCommentVo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -14,4 +16,25 @@ public class FeedVo extends Feed { @Schema(description = "发布的内容的url") private List urls; + + @Schema(description = "点赞数量") + private Integer likeCount; + + @Schema(description = "评论数量") + private Integer commentCount; + + @Schema(description = "当前用户是否已点赞") + private Boolean hasLiked; + + @Schema(description = "点赞用户列表(列表页显示前3个,详情页显示全部)") + private List likeList; + + @Schema(description = "评论列表(列表页显示前3条,详情页显示全部)") + private List commentList; + + @Schema(description = "发布人昵称") + private String userName; + + @Schema(description = "发布人头像") + private String userAvatar; } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedCommentVo.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedCommentVo.java new file mode 100644 index 00000000..f494aa97 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedCommentVo.java @@ -0,0 +1,23 @@ +package com.luohuo.flex.im.domain.vo.resp.feed; + +import com.luohuo.flex.im.domain.entity.FeedComment; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 朋友圈评论响应VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class FeedCommentVo extends FeedComment { + + @Schema(description = "评论人昵称") + private String userName; + + @Schema(description = "评论人头像") + private String userAvatar; + + @Schema(description = "被回复人昵称") + private String replyUserName; +} diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedLikeVo.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedLikeVo.java new file mode 100644 index 00000000..b5f3a84b --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/feed/FeedLikeVo.java @@ -0,0 +1,26 @@ +package com.luohuo.flex.im.domain.vo.resp.feed; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 朋友圈点赞响应VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FeedLikeVo { + + @Schema(description = "用户ID") + private Long uid; + + @Schema(description = "用户昵称") + private String userName; + + @Schema(description = "用户头像") + private String userAvatar; +} diff --git a/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/CacheKeyTable.java b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/CacheKeyTable.java index ac349596..fb60e25a 100644 --- a/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/CacheKeyTable.java +++ b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/CacheKeyTable.java @@ -264,6 +264,16 @@ public interface CacheKeyTable { */ String FEED_TARGET = "feedTarget"; + /** + * 朋友圈评论 + */ + String FEED_COMMENT = "feedComment"; + + /** + * 朋友圈点赞 + */ + String FEED_LIKE = "feedLike"; + /** * 会话信息 */ diff --git a/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedCommentCacheKeyBuilder.java b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedCommentCacheKeyBuilder.java new file mode 100644 index 00000000..f5e76de6 --- /dev/null +++ b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedCommentCacheKeyBuilder.java @@ -0,0 +1,54 @@ +package com.luohuo.flex.common.cache.common; + +import com.luohuo.basic.base.entity.SuperEntity; +import com.luohuo.basic.model.cache.CacheHashKey; +import com.luohuo.basic.model.cache.CacheKeyBuilder; +import com.luohuo.flex.common.cache.CacheKeyModular; +import com.luohuo.flex.common.cache.CacheKeyTable; + +import java.time.Duration; + +/** + * 朋友圈评论缓存 朋友圈ID -> 评论列表 + * @author 乾乾 + */ +public class FeedCommentCacheKeyBuilder implements CacheKeyBuilder { + public static CacheHashKey build(Long feedId) { + return new FeedCommentCacheKeyBuilder().hashKey(feedId); + } + + @Override + public String getTenant() { + return null; + } + + @Override + public String getTable() { + return CacheKeyTable.Chat.FEED_COMMENT; + } + + @Override + public String getPrefix() { + return CacheKeyModular.PREFIX; + } + + @Override + public String getModular() { + return CacheKeyModular.CHAT; + } + + @Override + public String getField() { + return SuperEntity.ID_FIELD; + } + + @Override + public ValueType getValueType() { + return ValueType.obj; + } + + @Override + public Duration getExpire() { + return Duration.ofDays(7L); + } +} diff --git a/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedLikeCacheKeyBuilder.java b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedLikeCacheKeyBuilder.java new file mode 100644 index 00000000..01cf37dd --- /dev/null +++ b/luohuo-cloud/luohuo-public/luohuo-common/src/main/java/com/luohuo/flex/common/cache/common/FeedLikeCacheKeyBuilder.java @@ -0,0 +1,54 @@ +package com.luohuo.flex.common.cache.common; + +import com.luohuo.basic.base.entity.SuperEntity; +import com.luohuo.basic.model.cache.CacheHashKey; +import com.luohuo.basic.model.cache.CacheKeyBuilder; +import com.luohuo.flex.common.cache.CacheKeyModular; +import com.luohuo.flex.common.cache.CacheKeyTable; + +import java.time.Duration; + +/** + * 朋友圈点赞缓存 朋友圈ID -> 点赞用户ID列表 + * @author 乾乾 + */ +public class FeedLikeCacheKeyBuilder implements CacheKeyBuilder { + public static CacheHashKey build(Long feedId) { + return new FeedLikeCacheKeyBuilder().hashKey(feedId); + } + + @Override + public String getTenant() { + return null; + } + + @Override + public String getTable() { + return CacheKeyTable.Chat.FEED_LIKE; + } + + @Override + public String getPrefix() { + return CacheKeyModular.PREFIX; + } + + @Override + public String getModular() { + return CacheKeyModular.CHAT; + } + + @Override + public String getField() { + return SuperEntity.ID_FIELD; + } + + @Override + public ValueType getValueType() { + return ValueType.obj; + } + + @Override + public Duration getExpire() { + return Duration.ofDays(7L); + } +} diff --git a/luohuo-cloud/luohuo-public/luohuo-model/src/main/java/com/luohuo/flex/model/entity/WSRespTypeEnum.java b/luohuo-cloud/luohuo-public/luohuo-model/src/main/java/com/luohuo/flex/model/entity/WSRespTypeEnum.java index be5bef7e..8182a17e 100644 --- a/luohuo-cloud/luohuo-public/luohuo-model/src/main/java/com/luohuo/flex/model/entity/WSRespTypeEnum.java +++ b/luohuo-cloud/luohuo-public/luohuo-model/src/main/java/com/luohuo/flex/model/entity/WSRespTypeEnum.java @@ -39,6 +39,7 @@ public enum WSRespTypeEnum { ROOM_GROUP_NOTICE_READ_MSG("roomGroupNoticeReadMsg", "群公告已读", null), FEED_SEND_MSG("feedSendMsg", "朋友圈发布", null), + FEED_NOTIFY("feedNotify", "朋友圈通知(点赞/评论)", null), ROOM_NOTIFICATION("roomNotification", "会话消息接收类型改变", null), SHIELD("shield", "你已屏蔽好友的消息", null), UNBLOCK("unblock", "你已解除屏蔽好友的消息", null), diff --git a/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/config/MySaTokenContextRegister.java b/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/config/MySaTokenContextRegister.java index 4ca39d0d..58bc68ec 100644 --- a/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/config/MySaTokenContextRegister.java +++ b/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/config/MySaTokenContextRegister.java @@ -1,5 +1,6 @@ package com.luohuo.flex.satoken.config; +import cn.dev33.satoken.context.SaTokenContext; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -8,6 +9,7 @@ import org.springframework.context.annotation.Configuration; import com.luohuo.basic.constant.Constants; import com.luohuo.flex.common.properties.IgnoreProperties; import com.luohuo.flex.common.properties.SystemProperties; +import com.luohuo.flex.satoken.spring.MySaTokenContextForSpringInJakartaServlet; /** * 注册 Sa-Token 框架所需要的 Bean @@ -15,8 +17,19 @@ import com.luohuo.flex.common.properties.SystemProperties; * @since 2024/9/18 14:38 */ @Slf4j +@Configuration public class MySaTokenContextRegister { + /** + * 注册 SaTokenContext Bean,用于 Spring Boot 3 Jakarta Servlet 环境 + * 这个 Bean 必须被注册,否则 SaToken 会报"上下文尚未初始化"的错误 + */ + @Bean + public SaTokenContext saTokenContext() { + log.info("注册 SaTokenContext 实现类:MySaTokenContextForSpringInJakartaServlet"); + return new MySaTokenContextForSpringInJakartaServlet(); + } + @Configuration @ConditionalOnProperty(prefix = Constants.PROJECT_PREFIX + ".webmvc", name = "header", havingValue = "true", matchIfMissing = true) public static class InnerConfig { diff --git a/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/spring/MySaTokenContextForSpringInJakartaServlet.java b/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/spring/MySaTokenContextForSpringInJakartaServlet.java index 8772968a..938afed0 100644 --- a/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/spring/MySaTokenContextForSpringInJakartaServlet.java +++ b/luohuo-cloud/luohuo-public/luohuo-sa-token-ext/src/main/java/com/luohuo/flex/satoken/spring/MySaTokenContextForSpringInJakartaServlet.java @@ -1,76 +1,84 @@ -///* -// * Copyright 2020-2099 sa-token.cc -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -//package com.luohuo.flex.satoken.spring; -// -//import cn.dev33.satoken.context.SaTokenContext; -//import cn.dev33.satoken.context.model.SaRequest; -//import cn.dev33.satoken.context.model.SaResponse; -//import cn.dev33.satoken.context.model.SaStorage; -//import cn.dev33.satoken.servlet.model.SaRequestForServlet; -//import cn.dev33.satoken.servlet.model.SaResponseForServlet; -//import cn.dev33.satoken.servlet.model.SaStorageForServlet; -//import cn.dev33.satoken.spring.SpringMVCUtil; -//import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil; -// -///** -// * Sa-Token 上下文处理器 [ SpringBoot3 Jakarta Servlet 版 ],在 SpringBoot3 中使用 Sa-Token 时,必须注入此实现类,否则会出现上下文无效异常 -// * -// * @author click33 -// * @since 1.34.0 -// */ -//public class MySaTokenContextForSpringInJakartaServlet implements SaTokenContext { -// -// /** -// * 获取当前请求的 Request 包装对象 -// */ -// @Override -// public SaRequest getRequest() { -// return new MySaRequestForServlet(SpringMVCUtil.getRequest()); -// } -// -// /** -// * 获取当前请求的 Response 包装对象 -// */ -// @Override -// public SaResponse getResponse() { -// return new SaResponseForServlet(SpringMVCUtil.getResponse()); -// } -// -// /** -// * 获取当前请求的 Storage 包装对象 -// */ -// @Override -// public SaStorage getStorage() { -// return new SaStorageForServlet(SpringMVCUtil.getRequest()); -// } -// -// /** -// * 判断:指定路由匹配符是否可以匹配成功指定路径 -// */ -// @Override -// public boolean matchPath(String pattern, String path) { -// return SaPathPatternParserUtil.match(pattern, path); -// } -// -// /** -// * 判断:在本次请求中,此上下文是否可用。 -// */ -// @Override -// public boolean isValid() { -// return SpringMVCUtil.isWeb(); -// } -// -//} +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luohuo.flex.satoken.spring; + +import cn.dev33.satoken.context.SaTokenContext; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.context.model.SaResponse; +import cn.dev33.satoken.context.model.SaStorage; +import cn.dev33.satoken.context.model.SaTokenContextModelBox; +import cn.dev33.satoken.servlet.model.SaRequestForServlet; +import cn.dev33.satoken.servlet.model.SaResponseForServlet; +import cn.dev33.satoken.servlet.model.SaStorageForServlet; +import cn.dev33.satoken.spring.SpringMVCUtil; + +/** + * Sa-Token 上下文处理器 [ SpringBoot3 Jakarta Servlet 版 ],在 SpringBoot3 中使用 Sa-Token 时,必须注入此实现类,否则会出现上下文无效异常 + * + * @author click33 + * @since 1.34.0 + */ +public class MySaTokenContextForSpringInJakartaServlet implements SaTokenContext { + + /** + * 获取当前请求的 Request 包装对象 + */ + @Override + public SaRequest getRequest() { + return new SaRequestForServlet(SpringMVCUtil.getRequest()); + } + + /** + * 获取当前请求的 Response 包装对象 + */ + @Override + public SaResponse getResponse() { + return new SaResponseForServlet(SpringMVCUtil.getResponse()); + } + + /** + * 获取当前请求的 Storage 包装对象 + */ + @Override + public SaStorage getStorage() { + return new SaStorageForServlet(SpringMVCUtil.getRequest()); + } + + @Override + public void setContext(SaRequest saRequest, SaResponse saResponse, SaStorage saStorage) { + // Servlet 环境中,上下文通过 ThreadLocal 自动管理,无需手动设置 + } + + @Override + public void clearContext() { + // Servlet 环境中,上下文通过 ThreadLocal 自动管理,无需手动清除 + } + + /** + * 判断:在本次请求中,此上下文是否可用。 + */ + @Override + public boolean isValid() { + return SpringMVCUtil.isWeb(); + } + + @Override + public SaTokenContextModelBox getModelBox() { + // 返回当前请求的上下文模型盒子 + return new SaTokenContextModelBox(getRequest(), getResponse(), getStorage()); + } + +} diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/pom.xml b/luohuo-cloud/luohuo-support/luohuo-boot-server/pom.xml index e24b9381..dd1b28b4 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/pom.xml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/pom.xml @@ -77,12 +77,6 @@ com.luohuo.basic luohuo-all - - - com.luohuo.basic - luohuo-cloud-starter - - cn.dev33 @@ -116,6 +110,11 @@ spring-cloud-starter-bootstrap + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + org.springframework.boot spring-boot-starter-test diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/java/com/luohuo/flex/base/config/BootWebConfiguration.java b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/java/com/luohuo/flex/base/config/BootWebConfiguration.java index 360098b9..4e7b7ad2 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/java/com/luohuo/flex/base/config/BootWebConfiguration.java +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/java/com/luohuo/flex/base/config/BootWebConfiguration.java @@ -16,11 +16,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.luohuo.basic.boot.config.BaseConfig; import com.luohuo.basic.constant.Constants; import com.luohuo.basic.log.event.SysLogListener; +import cn.dev33.satoken.filter.SaTokenContextFilterForJakartaServlet; import com.luohuo.flex.base.interceptor.AuthenticationSaInterceptor; import com.luohuo.flex.base.interceptor.TokenContextFilter; import com.luohuo.flex.common.properties.IgnoreProperties; import com.luohuo.flex.common.properties.SystemProperties; import com.luohuo.flex.oauth.facade.LogFacade; +import jakarta.servlet.DispatcherType; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import java.util.EnumSet; /** * 基础服务-Web配置 @@ -49,6 +53,22 @@ public class BootWebConfiguration extends BaseConfig implements WebMvcConfigurer return new AuthenticationSaInterceptor(ignoreProperties, defResourceFacade); } + /** + * 注册 SaToken 上下文过滤器,用于初始化 SaToken 上下文 + * 这个过滤器必须在所有其他过滤器之前执行,以确保 SaToken 上下文被正确初始化 + */ + @Bean + public FilterRegistrationBean saTokenContextFilterForJakartaServlet() { + FilterRegistrationBean bean = new FilterRegistrationBean<>(new SaTokenContextFilterForJakartaServlet()); + // 配置 Filter 拦截的 URL 模式 + bean.addUrlPatterns("/*"); + // 设置 Filter 的执行顺序,数值越小越先执行 + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + bean.setAsyncSupported(true); + bean.setDispatcherTypes(EnumSet.of(DispatcherType.ASYNC, DispatcherType.REQUEST)); + return bean; + } + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/"); diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/application.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/application.yml index 2abff512..62c09e99 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/application.yml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/application.yml @@ -265,6 +265,8 @@ spring: - classpath:config/${spring.profiles.active}/rocketmq.yml - classpath:config/${spring.profiles.active}/redis.yml - classpath:config/${spring.profiles.active}/mysql.yml + - classpath:config/${spring.profiles.active}/base-server.yml + - classpath:config/${spring.profiles.active}/ai-server.yml mvc: pathmatch: # 升级springboot2.6.6后临时处理,防止swagger报错 diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/ai-server.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/ai-server.yml new file mode 100644 index 00000000..56e8d3ac --- /dev/null +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/ai-server.yml @@ -0,0 +1,49 @@ +spring: + autoconfigure: + exclude: + - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建 + - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建 + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + ai: + vectorstore: # 向量存储 + redis: + initialize-schema: true + index: knowledge_index # Redis 中向量索引的名称:用于存储和检索向量数据的索引标识符,所有相关的向量搜索操作都会基于这个索引进行 + prefix: "knowledge_segment:" # Redis 中存储向量数据的键名前缀:这个前缀会添加到每个存储在 Redis 中的向量数据键名前,每个 document 都是一个 hash 结构 + qdrant: + initialize-schema: true + collection-name: knowledge_segment # Qdrant 中向量集合的名称:用于存储向量数据的集合标识符,所有相关的向量操作都会在这个集合中进行 + host: 127.0.0.1 + port: 6334 + milvus: + initialize-schema: true + database-name: default # Milvus 中数据库的名称 + collection-name: knowledge_segment # Milvus 中集合的名称:用于存储向量数据的集合标识符,所有相关的向量操作都会在这个集合中进行 + client: + host: 127.0.0.1 + port: 19530 + qianfan: # 文心一言 + api-key: x0cuLZ7XsaTCU08vuJWO87Lg + secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK + zhipuai: # 智谱 AI + api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs + openai: # OpenAI 官方 + api-key: sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17 + base-url: https://api.gptsapi.net + azure: # OpenAI 微软 + openai: + endpoint: https://eastusprejade.openai.azure.com + api-key: xxx + ollama: + base-url: http://127.0.0.1:11434 + chat: + model: llama3 + stabilityai: + api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx + dashscope: # 通义千问 + api-key: sk-71800982914041848008480000000000 + minimax: # Minimax:https://www.minimaxi.com/ + api-key: xxxx + moonshot: # 月之暗灭(KIMI) + api-key: sk-abc \ No newline at end of file diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/base-server.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/base-server.yml new file mode 100644 index 00000000..4b59328a --- /dev/null +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/base-server.yml @@ -0,0 +1,72 @@ +luohuo: + file: + storageType: LOCAL # FAST_DFS LOCAL MIN_IO ALI_OSS HUAWEI_OSS QINIU_OSS + delFile: false + suffix: doc,docx,gif,jpeg,jpg,pdf,png,rar,zip,xls,xlsx,txt,file,bin,egp,egpx,apk,xml,rpx,rpg,html,htm,wps,xbid,db + publicBucket: + - public + local: + storagePath: /Users/luohuo/data/projects/uploadfile/file/ # 文件存储路径 ( 某些版本的 window 需要改成 D:\\data\\projects\\uploadfile\\file\\ ) + urlPrefix: http://127.0.0.1/file/ # 文件访问 (部署nginx后,配置nginx的ip,并配置nginx静态代理storage-path地址的静态资源) + innerUrlPrefix: null # 内网的url前缀 + fastDfs: + urlPrefix: https://fastdfs.hula.com/ + ali: + # 请填写自己的阿里云存储配置 + urlPrefix: "https://luohuo-admin-cloud.oss-cn-beijing.aliyuncs.com/" + bucket: "luohuo-admin-cloud" + endpoint: "oss-cn-beijing.aliyuncs.com" + accessKeyId: "填写你的id" + accessKeySecret: "填写你的秘钥" + minIo: + endpoint: "https://static.luohuo.top/" + accessKey: "luohuo" + secretKey: "123." + bucket: "dev" + huawei: + uriPrefix: https://123-dev.obs.cn-southwest-2.myhuaweicloud.com/ + endpoint: "obs.cn-southwest-2.myhuaweicloud.com" + accessKey: "123" + secretKey: "123" + bucket: "123-dev" + location: "cn-123-2" + qiNiu: + domain: cdn.hula + useHttps: true + zone: "z0" + accessKey: "1" + secretKey: "2" + bucket: "luohuo_admin_cloud" + +#FAST_DFS配置 +fdfs: + soTimeout: 1500 + connectTimeout: 600 + thumb-image: + width: 150 + height: 150 + tracker-list: + - 39.108.109.234:22122 + pool: + #从池中借出的对象的最大数目 + max-total: 153 + max-wait-millis: 102 + jmx-name-base: 1 + jmx-name-prefix: 1 +# 这里也需要配置 +spring: + mail: + # 网易邮箱SMTP服务器 + host: smtp.163.com + # SSL端口 + port: 465 + # 发件人邮箱 + username: nongyehon1g919@163.com + # 邮箱SMTP授权码 + password: YZ7ju1X6W11WSbx2e + properties: + mail: + smtp: + ssl: + enable: true + auth: true \ No newline at end of file diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/common.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/common.yml index e5d9b9ff..ff756691 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/common.yml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/common.yml @@ -1,14 +1,26 @@ +server: + port: 18760 + # 优雅停机 + shutdown: GRACEFUL + servlet: + encoding: + enabled: true + charset: UTF-8 + force: true + # undertow: + # threads: + # io: 4 # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 + # worker: 80 # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 + # buffer-size: 2048 # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 , 每块buffer的空间大小,越小的空间被利用越充分 + # direct-buffers: true # 是否分配的直接内存 luohuo: - version: 2.6.5 - generator: - outputDir: /Users/luohuo/gitlab/luohuo-boot-pro-column - frontOutputDir: /Users/luohuo/gitlab/luohuo-web-pro - frontSoyOutputDir: /Users/luohuo/gitlab/luohuo-web-pro-soybean - frontVben5OutputDir: /Users/luohuo/github/luohuo-web-vben-max/apps/web-antd - # 作者 - author: qianqian - # 默认项目 - projectType: BOOT + version: 3.0.0 + thread: + multiplier: 2 # 线程乘数 + min: 4 # 最小线程数 + retry: + max-count: 3 # 最大重试次数 + delay-seconds: 3 # 多久时候开始重试 # 微信扫码配置 wx: mp: @@ -18,21 +30,257 @@ luohuo: secret: 9c215f8d078422805218221144b9095b # 公众号的appsecret token: nongyehong aesKey: 7iNecFLZ8nbtCaMntHYEruSHP1Mk8FmGvrkHwnJzwug # 接口配置里的EncodingAESKey值 -spring: - mail: - host: smtp.qq.com # 邮箱SMTP服务器 - port: 465 # SSL端口 - username: xxxxxxxxxxxxxxx # 发件人邮箱 - password: xxxxxxxx # 邮箱SMTP授权码 - properties: - mail: - smtp: - auth: true - starttls: - enable: true - ssl: - enable: false - protocols: TLSv1.2 # 加密协议 + feign: + # xxl-job-admin 的地址 + job-server: http://127.0.0.1:8767 + webmvc: + undertow: true + header: true + scan: + enabled: true + basePackage: com.luohuo + ignore: + # 是否启用网关的 uri权限鉴权 (设置为false,则后台不校验访问权限) + authEnabled: true + # 前端校验按钮 是否区分大小写 + caseSensitive: false + # 系统没有配置某个URI时,是否允许访问 + notConfigUriAllow: false + anyone: # 请求中 需要携带Tenant 且 需要携带Token(需要登录),但不需要验证uri权限 + ALL: + - /*/anyone/** + - /anyone/** + anyUser: # 请求中 需要携带Tenant,但 不需要携带Token(不需要登录) 和 不需要验证uri权限 + ALL: + - /*/anyUser/** + - /anyUser/** + anyTenant: # 请求中 不需要携带Tenant 且 不需要携带Token(不需要登录) 和 不需要验证uri权限 + ALL: + - /*/anyTenant/** + - /anyTenant/** + system: + # 登录时否验证密码有效性 (常用于开发环境快速登录) + verifyPassword: true + # 登录时否验证验证码有效性 (常用于开发环境快速登录) + verifyCaptcha: true + # 默认用户密码 + defPwd: '123456' + # 密码最大输错次数 小于0不限制 + maxPasswordErrorNum: 10 + # 密码错误锁定用户时间 除了0表示锁定到今天结束,还支持m、h、d、w、M、y等单位 + passwordErrorLockUserTime: '0' + # 锁定的时间 + lockUserTime: 15 + # 缓存Key前缀 + cachePrefix: luohuo + # oauth 服务扫描枚举类的包路径 + enumPackage: "com.luohuo" + # 演示环境启用 + notAllowWrite: false + notAllowWriteList: + POST: + - /defResource + - /defTenantApplicationRel/cancel + PUT: + - /defResource + - /defResource/moveResource + DELETE: + - /defResource*/** + - /defTenant*/** + - /defApplication*/** + - /defUser*/** + - /defParameter*/** + - /defDict*/** + - /defClient*/** + # swagger 文档通用配置, 主要配置了全局参数、版本号信息、联系人信息 详情看: SwaggerProperties + swagger: + license: Powered By luohuo + licenseUrl: https://github.com/luohuo + termsOfServiceUrl: https://github.com/luohuo + contact: # 联系人信息 + url: https://github.com/luohuo + name: luohuo + email: 1046762075@qq.com + global-operation-parameters: # 全局参数 + - name: Token + description: 用户信息 + modelRef: String + parameterType: header + required: true + # 默认值只是方便本地开发时,少填参数,生产环境请禁用swagger或者禁用默认参数 + defaultValue: "test" + - name: Authorization + description: 客户端信息 + modelRef: String + parameterType: header + required: true + defaultValue: "bGFtcF93ZWI6bGFtcF93ZWJfc2VjcmV0" + - name: ApplicationId + description: 应用ID + modelRef: String + parameterType: header + required: true + defaultValue: "1" + echo: #详情看: EchoProperties + # 是否启用 远程数据 注解AOP注入 + enabled: true + aop-enabled: true + basePackages: + - com.luohuo.basic + - com.luohuo.flex + # 字典类型 和 code 的分隔符 + dictSeparator: '###' + # 多个字典code 之间的的分隔符 + dictItemSeparator: ',' + # 递归最大深度 + maxDepth: 3 + # 本地缓存配置信息 生产慎用 + guavaCache: + enabled: false + maximumSize: 1000 + refreshWriteTime: 2 + refreshThreadPoolSize: 10 + log: # 详情看:OptLogProperties + # 开启记录操作日志 + enabled: true + # 记录到什么地方 DB:mysql LOGGER:日志文件 + type: DB + xss: + # 是否开启 xss 过滤器 详情看:XssProperties + enabled: true + requestBodyEnabled: false + captcha: + # 登录界面的验证码配置 详情看:CaptchaProperties + type: ARITHMETIC + width: 158 + height: 58 + len: 2 + charType: 2 + async: # 全局线程池配置 + corePoolSize: 2 + maxPoolSize: 50 + queueCapacity: 10000 + keepAliveSeconds: 300 + threadNamePrefix: 'luohuo-async-executor-' + tenant: + enable: true + ignoreTables: + - secure_invoke_record + - worker_node + - base_operation_log_ext + - base_operation_log -server: - port: 18760 + +# knife4j 文档通用配置 详情看: Knife4jProperties +knife4j: + enable: true + setting: + language: zh_cn + swagger-model-name: 实体类列表 + # 是否在每个Debug调试栏后显示刷新变量按钮,默认不显示 + enableReloadCacheParameter: true + # 是否开启界面中对某接口的版本控制,如果开启,后端变化后Ui界面会存在小蓝点 + enableVersion: true + # 针对RequestMapping的接口请求类型,在不指定参数类型的情况下,如果不过滤,默认会显示7个类型的接口地址参数,如果开启此配置,默认展示一个Post类型的接口地址 + enableFilterMultipartApis: false + # 是否开启动态参数调试功能 + enableDynamicParameter: true + # 是否显示Footer + enableFooter: false + enableFooterCustom: true + footerCustomContent: Apache License 2.0 | Copyright 2025 [luohuo-cloud](https://github.com/qianqian) + +springdoc: + # 默认是false,需要设置为true + default-flat-param-object: true + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + enabled: ${knife4j.enable} + api-docs: + enabled: ${knife4j.enable} + path: /v3/api-docs + +spring: + mvc: + pathmatch: + # 升级springboot2.6.6后临时处理,防止swagger报错 + matching-strategy: ANT_PATH_MATCHER + lifecycle: + # 优雅停机宽限期时间 + timeout-per-shutdown-phase: 30s + servlet: + # 上传文件最大值 + multipart: + max-file-size: 512MB + max-request-size: 512MB + +management: + endpoints: + web: + base-path: /actuator + exposure: + include: '*' + endpoint: + health: + show-details: ALWAYS + enabled: true + +feign: + httpclient: + enabled: false + okhttp: + enabled: true + hystrix: + enabled: false + sentinel: + enabled: true + client: + config: + default: + # feign client 调用全局超时时间 + connectTimeout: 60000 + readTimeout: 60000 + loggerLevel: FULL + #支持压缩的mime types + compression: # 请求压缩 + request: + enabled: true + mime-types: text/xml,application/xml,application/json + min-request-size: 2048 + response: # 响应压缩 + enabled: true + + +# Sa-Token配置 +sa-token: + # token前缀 + token-prefix: + # token名称 (同时也是cookie名称) + token-name: Token + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + active-timeout: 60 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + # 开启后,logout() 会自动将 Token 加入黑名单 + is-blacklist: true + # 设备类型标识(关键配置) + device: + pc: PC + mobile: MOBILE + + +p6spy: + filter: true + config: + # comma separated list of strings to exclude + exclude: secure_invoke_record \ No newline at end of file diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/mysql.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/mysql.yml index e2a2f090..ab10e8ce 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/mysql.yml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/mysql.yml @@ -1,62 +1,47 @@ -# 相当于 database.yml luohuo: - # validation-query 参数对不同数据库的支持参考:https://www.cnblogs.com/BonnieWss/p/9100402.html - oracle: &db-oracle - db-type: oracle - validation-query: SELECT 'x' FROM DUAL - filters: stat,wall,slf4j # druid不支持使用p6spy打印日志,所以采用druid 的 slf4j 过滤器来打印可执行日志 - username: 'luohuo_none' - password: 'luohuo_none' - driverClassName: oracle.jdbc.driver.OracleDriver - url: jdbc:oracle:thin:@172.26.3.67:1521:helowin mysql: &db-mysql filters: stat,wall db-type: mysql validation-query: SELECT 'x' - username: 'luohuo_dev' + username: 'root' password: '123456' - # 生产使用原生驱动,开发使用p6spy驱动打印日志 driverClassName: com.p6spy.engine.spy.P6SpyDriver - url: jdbc:p6spy:mysql://127.0.0.1:13306/luohuo_dev?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true - dameng: &db-dameng - username: 'SYSDBA' - password: 'SYSdba001' - driverClassName: dm.jdbc.driver.DmDriver - # 主主复制集群的连接 URL,使用服务器IP和端口 - #以下是样例中的默认端口号和数据库名称,请根据实际情况修改 - url: jdbc:dm://192.168.1.139:5236/luohuo_ds_c_defaults - sqlserver: &db-sqlserver - username: 'sa' - password: '1234@abcd' - driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver - url: jdbc:sqlserver://172.26.3.67:1433;DatabaseName=luohuo_none - # driverClassName: com.p6spy.engine.spy.P6SpyDriver - # url: jdbc:p6spy:sqlserver://172.26.3.67:1433;DatabaseName=luohuo_none - db-type: sqlserver - validation-query: SELECT 'x' - filters: stat,wall - init: - separator: GO + url: jdbc:p6spy:mysql://192.168.1.37:13306/luohuo_dev?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true database: # 字段介绍参考 DatabaseProperties - # 4.x 需要写死 column, 其他模式需要使用其他项目,而非改变此参数 multiTenantType: NONE + # 是否启用数据权限 + isDataScope: true # 是否启用 sql性能规范插件 isBlockAttack: false + # 是否启用 sql性能规范插件 + isIllegalSql: false # 是否启用分布式事务 isSeata: false # 生产环境请设置p6spy = false - p6spy: true + p6spy: false + # 单页分页条数限制 + maxLimit: -1 + # 溢出总页数后是否进行处理 + overflow: true + # 生成 countSql 优化掉 join, 现在只支持 left join + optimizeJoin: true # id生成策略 - id-type: CACHE - hutoolId: + id-type: DEFAULT + hutool-id: workerId: 0 dataCenterId: 0 - cache-id: - time-bits: 31 - worker-bits: 22 - seq-bits: 10 - epochStr: '2020-09-15' + default-id: + # 时间位分配(默认41) + time-bits: 41 + # 工作节点位分配(默认13) + worker-bits: 13 + # 序列号位分配(默认9) + seq-bits: 9 + # 起始时间 + epochStr: '2025-02-24' + # size扩容参数 boost-power: 3 + # 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50 padding-factor: 50 # mysql 通用配置 @@ -70,22 +55,17 @@ spring: enable: true # 从这里开始(druid),中间的这段配置用于 luohuo.database.multiTenantType != DATASOURCE 时 <<: *db-mysql - # <<: *db-dameng - # <<: *db-sqlserver - # <<: *db-oracle initialSize: 10 minIdle: 10 maxActive: 200 max-wait: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 - validation-query: SELECT 'x' test-on-borrow: false test-on-return: false - test-while-idle: true + test-while-idle: false time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒 - filters: stat,wall filter: wall: enabled: true @@ -101,14 +81,14 @@ spring: # 从这里结束(druid),中间的这段配置用于 luohuo.database.multiTenantType != DATASOURCE 时 # 以下的2段配置,同时适用于所有模式 - web-stat-filter: # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter + web-stat-filter: # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter enabled: true url-pattern: /* exclusions: "*.js , *.gif ,*.jpg ,*.png ,*.css ,*.ico , /druid/*" session-stat-max-count: 1000 profile-enable: true session-stat-enable: false - stat-view-servlet: #展示Druid的统计信息,StatViewServlet的用途包括:1.提供监控信息展示的html页面2.提供监控信息的JSON API + stat-view-servlet: #展示Druid的统计信息,StatViewServlet的用途包括:1.提供监控信息展示的html页面2.提供监控信息的JSON API enabled: true url-pattern: /druid/* #根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html例如:http://127.0.0.1:9000/druid/index.html reset-enable: true #允许清空统计数据 @@ -118,7 +98,7 @@ spring: mybatis-plus: mapper-locations: - - classpath*:mapper_**/**/*Mapper.xml + - classpath*:mapper**/**/**/*Mapper.xml #实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.luohuo.flex.*.entity;com.luohuo.basic.database.mybatis.typehandler typeEnumsPackage: com.luohuo.flex.*.enumeration diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/redis.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/redis.yml index 149801dc..45ff558c 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/redis.yml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/redis.yml @@ -1,12 +1,12 @@ luohuo: cache: type: REDIS + serializer-type: JACK_SON redis: - ip: 127.0.0.1 + ip: 192.168.1.37 port: 16379 - password: 'mq0000' - database: 7 - + password: luo.123456 + database: 1 spring: cache: type: GENERIC diff --git a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/rocketmq.yml b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/rocketmq.yml index 2c6f100d..b49ed120 100644 --- a/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/rocketmq.yml +++ b/luohuo-cloud/luohuo-support/luohuo-boot-server/src/main/resources/config/dev/rocketmq.yml @@ -1,14 +1,12 @@ -# 相当于 rocketmq.yml luohuo: rocketmq: # 若系统中有除了zipkin之外的地方使用了mq ,则一定不能设置成false enabled: true - ip: 117.72.82.249 + ip: 192.168.1.37 port: 9876 access-key: 'earthearth' secret-key: 'mq000000' - rocketmq: ip: ${luohuo.rocketmq.ip} port: ${luohuo.rocketmq.port} @@ -34,4 +32,4 @@ rocketmq: secret-key: ${luohuo.rocketmq.secret-key} # Secret Key listeners: # 配置某个消费分组,是否监听指定 Topic 。结构为 Map<消费者分组, > 。默认情况下,不配置表示监听。 erbadagang-consumer-group: - topic1: false # 关闭 test-consumer-group 对 topic1 的监听消费 + topic1: false # 关闭 test-consumer-group 对 topic1 的监听消费 \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/AckProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/AckProcessor.java index 42e714c9..bb015e18 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/AckProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/AckProcessor.java @@ -28,19 +28,18 @@ public class AckProcessor implements MessageProcessor { private RocketMQTemplate rocketMQTemplate; @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return WSReqTypeEnum.ACK.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { + public void process(WebSocketSession session, Long uid, WSBaseReq payload) { if(ReactiveContextUtil.getTenantId() == null){ ReactiveContextUtil.setTenantId(DefValConstants.DEF_TENANT_ID); ReactiveContextUtil.setUid(uid); } - AckMessageDTO req = JSONUtil.toBean(JSONUtil.toBean(payload, WSBaseReq.class).getData(), AckMessageDTO.class); + AckMessageDTO req = JSONUtil.toBean(payload.getData(), AckMessageDTO.class); req.setUid(uid); rocketMQTemplate.send(MqConstant.MSG_PUSH_ACK_TOPIC, MessageBuilder.withPayload(req).build()); } diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/DefaultMessageProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/DefaultMessageProcessor.java index 17dc2c2c..b3a72fa8 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/DefaultMessageProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/DefaultMessageProcessor.java @@ -1,6 +1,5 @@ package com.luohuo.flex.ws.websocket.processor; -import cn.hutool.json.JSONUtil; import com.luohuo.flex.model.ws.WSBaseReq; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; @@ -15,13 +14,12 @@ import org.springframework.web.reactive.socket.WebSocketSession; @Component public class DefaultMessageProcessor implements MessageProcessor { @Override - public boolean supports(String payload) { + public boolean supports(WSBaseReq req) { return true; } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public void process(WebSocketSession session, Long uid, WSBaseReq req) { log.warn("未知消息类型: {}", req.getType()); } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java index ac042cb5..11a548ed 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java @@ -1,6 +1,5 @@ package com.luohuo.flex.ws.websocket.processor; -import cn.hutool.json.JSONUtil; import com.luohuo.flex.model.ws.WSBaseReq; import com.luohuo.flex.model.enums.WSReqTypeEnum; import lombok.extern.slf4j.Slf4j; @@ -16,13 +15,12 @@ import org.springframework.web.reactive.socket.WebSocketSession; @Component public class HeartbeatProcessor implements MessageProcessor { @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return WSReqTypeEnum.HEARTBEAT.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { + public void process(WebSocketSession session, Long uid, WSBaseReq req) { log.info("收到用户 {} 的心跳", uid); } } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/LoginProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/LoginProcessor.java index 88b88160..c19761b6 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/LoginProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/LoginProcessor.java @@ -24,15 +24,13 @@ // @Resource // private SessionManager sessionManager; // @Override -// public boolean supports(String payload) { -// WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); +// public boolean supports(WSBaseReq req) { // return WSReqTypeEnum.LOGIN.equals(WSReqTypeEnum.of(req.getType())); // } // // @Override -// public void process(WebSocketSession session, Long uid, String payload) { +// public void process(WebSocketSession session, Long uid, WSBaseReq req) { // try { -// WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); // String qrToken = req.getData().getStr("qrToken"); // // // 1. 验证Token有效性 diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageHandlerChain.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageHandlerChain.java index d4cb2000..c0183427 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageHandlerChain.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageHandlerChain.java @@ -1,5 +1,7 @@ package com.luohuo.flex.ws.websocket.processor; +import cn.hutool.json.JSONUtil; +import com.luohuo.flex.model.ws.WSBaseReq; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.reactive.socket.WebSocketSession; @@ -22,10 +24,11 @@ public class MessageHandlerChain { } public void handleMessage(WebSocketSession session, Long uid, String payload) { + WSBaseReq bean = JSONUtil.toBean(payload, WSBaseReq.class); processors.stream() .filter(p -> { try { - return p.supports(payload); + return p.supports(bean); } catch (Exception e) { log.error("处理器[{}]支持检查异常", p.getClass().getSimpleName(), e); return false; @@ -34,7 +37,7 @@ public class MessageHandlerChain { .findFirst() .ifPresent(p -> { try { - p.process(session, uid, payload); + p.process(session, uid, bean); } catch (Exception e) { log.error("处理器[{}]执行失败", p.getClass().getSimpleName(), e); } diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageProcessor.java index 92623b87..8b67cd7c 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/MessageProcessor.java @@ -1,5 +1,6 @@ package com.luohuo.flex.ws.websocket.processor; +import com.luohuo.flex.model.ws.WSBaseReq; import org.springframework.web.reactive.socket.WebSocketSession; /** @@ -9,15 +10,15 @@ public interface MessageProcessor { /** * 轻量级验证,避免复杂逻辑 * - * @param payload 消息内容 + * @param bean 消息内容 */ - boolean supports(String payload); + boolean supports(WSBaseReq bean); /** * 业务处理需捕获自身异常 * @param session 会话 * @param uid 用户id - * @param payload 消息内容 + * @param bean 消息内容 */ - void process(WebSocketSession session, Long uid, String payload); + void process(WebSocketSession session, Long uid, WSBaseReq bean); } \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/ReadProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/ReadProcessor.java index d2fbec97..a44bb1c6 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/ReadProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/ReadProcessor.java @@ -26,14 +26,13 @@ public class ReadProcessor implements MessageProcessor { private RocketMQTemplate rocketMQTemplate; @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return WSReqTypeEnum.READ.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - ReadMessageDTO req = JSONUtil.toBean(JSONUtil.toBean(payload, WSBaseReq.class).getData(), ReadMessageDTO.class); + public void process(WebSocketSession session, Long uid, WSBaseReq payload) { + ReadMessageDTO req = JSONUtil.toBean(payload.getData(), ReadMessageDTO.class); req.setUid(uid); rocketMQTemplate.send(MqConstant.MSG_PUSH_READ_TOPIC, MessageBuilder.withPayload(req).build()); } diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/MediaControlProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/MediaControlProcessor.java index d361bb40..cabc1cb8 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/MediaControlProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/MediaControlProcessor.java @@ -35,16 +35,14 @@ public class MediaControlProcessor implements MessageProcessor { private final VideoChatService videoService; @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return MEDIA_MUTE_AUDIO.eq(req.getType()) || MEDIA_MUTE_ALL.eq(req.getType()) || MEDIA_MUTE_VIDEO.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq baseReq = JSONUtil.toBean(payload, WSBaseReq.class); + public void process(WebSocketSession session, Long uid, WSBaseReq baseReq) { MediaControlVO control = JSONUtil.toBean(baseReq.getData(), MediaControlVO.class); // 转发媒体控制指令给房间内其他成员 diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/QualityMonitorProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/QualityMonitorProcessor.java index 0e1eeaf8..d1d84901 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/QualityMonitorProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/QualityMonitorProcessor.java @@ -38,16 +38,13 @@ public class QualityMonitorProcessor implements MessageProcessor { private final VideoChatService videoService; private final PushService pushService; - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return NETWORK_REPORT.eq(req.getType()) || SCREEN_SHARING.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq baseReq = JSONUtil.toBean(payload, WSBaseReq.class); - + public void process(WebSocketSession session, Long uid, WSBaseReq baseReq) { switch (WSReqTypeEnum.of(baseReq.getType())) { case NETWORK_REPORT -> handleNetworkReport(uid, baseReq); case SCREEN_SHARING -> handleScreenSharing(uid, baseReq); diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/RoomAdminProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/RoomAdminProcessor.java index f78d6ab3..c1092c55 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/RoomAdminProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/RoomAdminProcessor.java @@ -34,17 +34,14 @@ public class RoomAdminProcessor implements MessageProcessor { private final PushService pushService; private final RoomTimeoutService roomTimeoutService; - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return CLOSE_ROOM.eq(req.getType()) || KICK_USER.eq(req.getType()) || MEDIA_MUTE_ALL.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq baseReq = JSONUtil.toBean(payload, WSBaseReq.class); - + public void process(WebSocketSession session, Long uid, WSBaseReq baseReq) { switch (WSReqTypeEnum.of(baseReq.getType())) { case CLOSE_ROOM -> handleCloseRoom(uid, baseReq); case KICK_USER -> handleKickUser(uid, baseReq); diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoCallProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoCallProcessor.java index 0691e738..d29d5dbb 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoCallProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoCallProcessor.java @@ -53,16 +53,13 @@ public class VideoCallProcessor implements MessageProcessor { private final RoomTimeoutService roomTimeoutService; @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return VIDEO_CALL_REQUEST.eq(req.getType()) || VIDEO_CALL_RESPONSE.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq baseReq = JSONUtil.toBean(payload, WSBaseReq.class); - + public void process(WebSocketSession session, Long uid, WSBaseReq baseReq) { switch (WSReqTypeEnum.of(baseReq.getType())) { case VIDEO_CALL_REQUEST -> handleCallRequest(uid, baseReq); case VIDEO_CALL_RESPONSE -> handleCallResponse(uid, baseReq); diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoProcessor.java index 2afa78ed..8fcfac3e 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/meet/VideoProcessor.java @@ -35,15 +35,13 @@ public class VideoProcessor implements MessageProcessor { private final RoomTimeoutService roomTimeoutService; @Override - public boolean supports(String payload) { - WSBaseReq req = JSONUtil.toBean(payload, WSBaseReq.class); + public boolean supports(WSBaseReq req) { return WEBRTC_SIGNAL.eq(req.getType()) || VIDEO_HEARTBEAT.eq(req.getType()); } @Override - public void process(WebSocketSession session, Long uid, String payload) { - WSBaseReq baseReq = JSONUtil.toBean(payload, WSBaseReq.class); + public void process(WebSocketSession session, Long uid, WSBaseReq baseReq) { // 处理不同情况的会话消息 switch (WSReqTypeEnum.of(baseReq.getType())) { diff --git a/preview/wx.png b/preview/wx.png index c6b19beb..8f0277b0 100644 Binary files a/preview/wx.png and b/preview/wx.png differ