feature: 对接朋友圈评论、点赞功能

This commit is contained in:
乾乾
2025-11-04 19:49:26 +08:00
parent a6faaf100e
commit 716db7ae4b
70 changed files with 2595 additions and 450 deletions

View File

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

View File

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

View File

@@ -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<AiApiKeyBalanceRespVO> getApiKeyBalance(@RequestParam("id") Long id) {
return success(apiKeyService.getApiKeyBalance(id, ContextUtil.getUid()));
}
}

View File

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

View File

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

View File

@@ -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<AiApiKeyBalanceRespVO.BalanceInfo> 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<AiApiKeyBalanceRespVO.BalanceInfo> 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<AiApiKeyBalanceRespVO.BalanceInfo> 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();
}
}

View File

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

View File

@@ -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<FeedCommentMapper, FeedComment> {
/**
* 获取朋友圈的评论数量
*/
public Integer getCommentCount(Long feedId) {
return Math.toIntExact(lambdaQuery()
.eq(FeedComment::getFeedId, feedId)
.count());
}
/**
* 游标分页查询朋友圈评论(升序排列,最新评论在最后)
*/
public CursorPageBaseResp<FeedComment> getCommentPage(Long feedId, CursorPageBaseReq request) {
// 使用 CursorUtils 进行游标分页(默认降序)
CursorPageBaseResp<FeedComment> result = CursorUtils.getCursorPageByMysql(
this,
request,
wrapper -> wrapper.eq(FeedComment::getFeedId, feedId),
FeedComment::getCreateTime
);
// 反向排序列表,使最新评论在最后
Collections.reverse(result.getList());
return result;
}
/**
* 获取某条朋友圈的所有评论(不分页,用于缓存)
* 注意MyBatis Plus 会自动过滤已删除的记录(通过 @TableLogic 注解)
*/
public List<FeedComment> getCommentListByFeedId(Long feedId) {
return lambdaQuery()
.eq(FeedComment::getFeedId, feedId)
.orderByAsc(FeedComment::getCreateTime)
.list();
}
/**
* 删除朋友圈的所有评论
*/
public boolean delByFeedId(Long feedId) {
return remove(new LambdaQueryWrapper<FeedComment>()
.eq(FeedComment::getFeedId, feedId));
}
}

View File

@@ -19,6 +19,7 @@ public class FeedDao extends ServiceImpl<FeedMapper, Feed> {
/**
* 查询朋友圈列表(包括指定用户列表的朋友圈)
*
* @param uidList 用户ID列表包括当前用户和好友
* @param request 分页请求
* @return 朋友圈分页结果

View File

@@ -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<FeedLikeMapper, FeedLike> {
/**
* 获取用户对某条朋友圈的点赞记录
*/
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<Long> 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<FeedLike>()
.eq(FeedLike::getFeedId, feedId));
}
}

View File

@@ -17,11 +17,12 @@ public class FeedMediaDao extends ServiceImpl<FeedMediaMapper, FeedMedia> {
/**
* 批量添加朋友圈的资源的数据
*
* @param feedId 朋友圈id
* @param images 素材地址
* @param type 0 纯文字 1 图片 2 视频
* @param type 0 纯文字 1 图片 2 视频
*/
public List<FeedMedia> batchSaveMedia(Long feedId, List<String> images, Integer type){
public List<FeedMedia> batchSaveMedia(Long feedId, List<String> images, Integer type) {
List<FeedMedia> 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<FeedMediaMapper, FeedMedia> {
/**
* 删除朋友圈的资源等消息
*
* @param feedId
* @return
*/
@@ -46,6 +48,7 @@ public class FeedMediaDao extends ServiceImpl<FeedMediaMapper, FeedMedia> {
/**
* 通过id获取到朋友圈资源信息
*
* @param feedId
* @return
*/

View File

@@ -14,6 +14,7 @@ public class FeedTargetDao extends ServiceImpl<FeedTargetMapper, FeedTarget> {
/**
* 删除朋友圈和标签的管理
*
* @param feedId
* @return
*/

View File

@@ -17,9 +17,9 @@ import java.util.List;
@Service
public class ItemConfigDao extends ServiceImpl<ItemConfigMapper, ItemConfig> {
public List<ItemConfig> getByType(Integer type) {
return lambdaQuery()
.eq(ItemConfig::getType, type)
.list();
}
public List<ItemConfig> getByType(Integer type) {
return lambdaQuery()
.eq(ItemConfig::getType, type)
.list();
}
}

View File

@@ -36,7 +36,8 @@ public class NoticeDao extends ServiceImpl<NoticeMapper, Notice> {
/**
* 查询当前用户的通知
* @param uid 登录用户
*
* @param uid 登录用户
* @param onlyUnread 通知状态
* @return
*/
@@ -53,7 +54,7 @@ public class NoticeDao extends ServiceImpl<NoticeMapper, Notice> {
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());

View File

@@ -25,44 +25,43 @@ import static com.luohuo.flex.im.domain.enums.NoticeStatusEnum.ACCEPTED;
@Service
public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
/**
*
* @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<Long> getExistingUsers(Long roomId, HashSet<Long> uidList) {
return lambdaQuery()

View File

@@ -19,57 +19,57 @@ import java.util.List;
*/
@Service
public class UserBackpackDao extends ServiceImpl<UserBackpackMapper, UserBackpack> {
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<UserBackpack> wrapper = new QueryWrapper<UserBackpack>().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<UserBackpack> wrapper = new QueryWrapper<UserBackpack>().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<UserBackpack> getByItemIds(Long uid, List<Long> itemIds) {
return lambdaQuery().eq(UserBackpack::getUid, uid)
.in(UserBackpack::getItemId, itemIds)
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus())
.list();
}
public List<UserBackpack> getByItemIds(Long uid, List<Long> itemIds) {
return lambdaQuery().eq(UserBackpack::getUid, uid)
.in(UserBackpack::getItemId, itemIds)
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus())
.list();
}
public List<UserBackpack> getByItemIds(List<Long> uids, List<Long> itemIds) {
return lambdaQuery().in(UserBackpack::getUid, uids)
.in(UserBackpack::getItemId, itemIds)
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus())
.list();
}
public List<UserBackpack> getByItemIds(List<Long> uids, List<Long> 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<UserBackpack>()
.eq(UserBackpack::getUid, uid)
.eq(UserBackpack::getItemId, itemId)
);
}
public long countByUidAndItemId(Long uid, Long itemId) {
return baseMapper.selectCount(new LambdaQueryWrapper<UserBackpack>()
.eq(UserBackpack::getUid, uid)
.eq(UserBackpack::getItemId, itemId)
);
}
}

View File

@@ -22,57 +22,57 @@ import java.util.Set;
@Service
public class UserDao extends ServiceImpl<UserMapper, User> {
public User getByOpenId(String openId) {
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda().eq(User::getOpenId, openId);
return getOne(wrapper);
}
public User getByOpenId(String openId) {
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().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<User> getByDefUserId(List<Long> 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<User> 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<User> 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<ChatMemberListResp> getFriend(String key) {
return baseMapper.getFriend("%" + key + "%");
}
public List<ChatMemberListResp> getFriend(String key) {
return baseMapper.getFriend("%" + key + "%");
}
public Boolean existsByEmailAndIdNot(Long uid, String email) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getEmail, email);
public Boolean existsByEmailAndIdNot(Long uid, String email) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.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<User> getByIds(Set<Long> uidSet) {
return baseMapper.selectBatchIds(uidSet);
}
public List<User> getByIds(Set<Long> uidSet) {
return baseMapper.selectBatchIds(uidSet);
}
}

View File

@@ -17,11 +17,11 @@ import java.util.List;
@Service
public class UserEmojiDao extends ServiceImpl<UserEmojiMapper, UserEmoji> {
public List<UserEmoji> listByUid(Long uid) {
return lambdaQuery().eq(UserEmoji::getUid, uid).list();
}
public List<UserEmoji> 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());
}
}

View File

@@ -55,6 +55,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 查询他不看我的好友
*
* @param uid 操作人
* @return
*/
@@ -70,6 +71,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 查询不让他看我的好友
*
* @param uid 操作人
* @return
*/
@@ -85,6 +87,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 查询我不看他的好友
*
* @param uid 操作人
* @return
*/
@@ -100,6 +103,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 查询仅聊天的好友
*
* @param uid 操作人
* @return
*/
@@ -123,10 +127,11 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* TODO 这里也要重构
* 获取到当前登录人员的所有的好友的信息
*
* @return
*/
@Cacheable(cacheNames = "luohuo:friend", key = "'list:'+#uid")
public List<Long> getAllFriendIdsByUid(Long uid){
public List<Long> getAllFriendIdsByUid(Long uid) {
LambdaQueryWrapper<UserFriend> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.select(UserFriend::getFriendUid)
.eq(UserFriend::getUid, uid);
@@ -135,6 +140,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 根据房间号+自己的id 定位好友关系
*
* @param roomId
* @param uid
* @return
@@ -156,8 +162,9 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
/**
* 当好友关系变更时清除缓存
*
* @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<UserFriendMapper, UserFriend> {
/**
* 查询所有好友房间
*
* @param roomIdList 房间id
*/
public List<UserFriend> getAllRoomInfo(List<Long> roomIdList) {

View File

@@ -9,38 +9,38 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class UserPrivacyDao extends ServiceImpl<UserPrivacyMapper, UserPrivacy> {
/**
* 获取用户隐私设置
*/
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<UserPrivacyMapper, UserPrivacy>
.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);

View File

@@ -18,8 +18,8 @@ import java.util.stream.Collectors;
@Service
public class UserRoleDao extends ServiceImpl<UserRoleMapper, UserRole> {
public Set<Long> listByUid(Long uid) {
return lambdaQuery()
.eq(UserRole::getUid, uid).select(UserRole::getUid).list().stream().map(UserRole::getUid).collect(Collectors.toSet());
}
public Set<Long> listByUid(Long uid) {
return lambdaQuery()
.eq(UserRole::getUid, uid).select(UserRole::getUid).list().stream().map(UserRole::getUid).collect(Collectors.toSet());
}
}

View File

@@ -19,7 +19,7 @@ import java.util.List;
public class UserStateDao extends ServiceImpl<UserStateMapper, UserState> {
@Cacheable(cacheNames = "luohuo:user", key = "'state'")
public List<UserState> list(){
public List<UserState> list() {
return lambdaQuery()
.list();
}

View File

@@ -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<FeedComment> {
}

View File

@@ -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<FeedLike> {
}

View File

@@ -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<FeedCommentVo> getCommentPage(Long feedId, CursorPageBaseReq request);
/**
* 获取朋友圈的评论列表(不分页,用于展示前几条)
* @param feedId 朋友圈ID
* @param limit 限制数量
* @return 评论列表
*/
List<FeedCommentVo> getCommentList(Long feedId, Integer limit);
/**
* 获取朋友圈的评论数量
* @param feedId 朋友圈ID
* @return 评论数量
*/
Integer getCommentCount(Long feedId);
}

View File

@@ -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<FeedLikeVo> getLikeList(Long feedId);
/**
* 获取朋友圈的点赞数量
* @param feedId 朋友圈ID
* @return 点赞数量
*/
Integer getLikeCount(Long feedId);
/**
* 判断用户是否已点赞
* @param uid 用户ID
* @param feedId 朋友圈ID
* @return 是否已点赞
*/
Boolean hasLiked(Long uid, Long feedId);
}

View File

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

View File

@@ -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);
/**
* 获取朋友圈的可见权限

View File

@@ -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<FeedCommentVo> getCommentPage(Long feedId, CursorPageBaseReq request) {
// 1. 分页查询评论
CursorPageBaseResp<FeedComment> commentPage = feedCommentDao.getCommentPage(feedId, request);
// 2. 组装用户信息
List<FeedCommentVo> commentVoList = buildCommentVoList(commentPage.getList());
// 3. 返回结果
CursorPageBaseResp<FeedCommentVo> result = new CursorPageBaseResp<>();
result.setList(commentVoList);
result.setCursor(commentPage.getCursor());
result.setIsLast(commentPage.getIsLast());
return result;
}
@Override
public List<FeedCommentVo> getCommentList(Long feedId, Integer limit) {
// 1. 从缓存获取评论列表
CacheHashKey hashKey = FeedCommentCacheKeyBuilder.build(feedId);
CacheResult<List<FeedComment>> result = cachePlusOps.get(hashKey, t -> feedCommentDao.getCommentListByFeedId(feedId));
List<FeedComment> 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<FeedCommentVo> buildCommentVoList(List<FeedComment> commentList) {
if (CollUtil.isEmpty(commentList)) {
return new ArrayList<>();
}
// 1. 收集所有需要查询的用户ID
Set<Long> uidSet = new HashSet<>();
commentList.forEach(comment -> {
uidSet.add(comment.getUid());
if (Objects.nonNull(comment.getReplyUid())) {
uidSet.add(comment.getReplyUid());
}
});
// 2. 批量获取用户信息
Map<Long, SummeryInfoDTO> 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());
}
}

View File

@@ -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<FeedLikeVo> getLikeList(Long feedId) {
// 1. 从缓存获取点赞用户ID列表
CacheHashKey hashKey = FeedLikeCacheKeyBuilder.build(feedId);
CacheResult<List<Long>> result = cachePlusOps.get(hashKey, t -> feedLikeDao.getLikeUidList(feedId));
List<Long> likeUidList = result.getValue();
if (CollUtil.isEmpty(likeUidList)) {
return new ArrayList<>();
}
// 2. 批量获取用户信息
Map<Long, SummeryInfoDTO> 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);
}
}

View File

@@ -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<Long> interactiveUidSet = getInteractiveUid(feedLike.getFeedId(), feedLike.getUid());
if (interactiveUidSet == null) return;
// 2. 构建通知消息 - 点赞uid、feedId无 comment 字段)
Map<String, Object> 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<Long> getInteractiveUid(Long feedId, Long operatorUid) {
// 1. 获取朋友圈信息
Feed feed = feedDao.getById(feedId);
if (feed == null) {
return null;
}
// 2. 获取朋友圈发布人的所有好友
List<Long> friendUidList = userFriendDao.getAllFriendIdsByUid(feed.getUid());
if (CollUtil.isEmpty(friendUidList)) {
return null;
}
// 3. 获取与该朋友圈有互动关系的好友(已点赞或已评论)
Set<Long> 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<Long> interactiveUidSet = getInteractiveUid(feedComment.getFeedId(), feedComment.getUid());
if (interactiveUidSet == null) return;
// 2. 构建通知消息 - 评论uid、feedId、comment、replyCommentId
Map<String, Object> 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<String, Object> data, Set<Long> interactiveUidSet, Long operatorUid, String type) {
WsBaseResp<Map<String, Object>> 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<Long> interactiveUidSet = getInteractiveUid(feedId, uid);
if (interactiveUidSet == null) return;
// 2. 构建通知消息 - 取消点赞uid、feedId、isUnlike=true无 comment 字段)
Map<String, Object> 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<Long> getInteractiveUidSet(Long feedId, List<Long> friendUidList) {
Set<Long> interactiveUidSet = new HashSet<>();
// 1. 获取所有点赞人
List<Long> likeUidList = feedLikeDao.getLikeUidList(feedId);
interactiveUidSet.addAll(likeUidList);
// 2. 获取所有评论人
List<FeedComment> commentList = feedCommentDao.getCommentListByFeedId(feedId);
Set<Long> commentUidSet = commentList.stream()
.map(FeedComment::getUid)
.collect(Collectors.toSet());
interactiveUidSet.addAll(commentUidSet);
// 3. 过滤出是好友的用户
interactiveUidSet.retainAll(friendUidList);
return interactiveUidSet;
}
}

View File

@@ -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<FeedVo> buildFeedResp(List<Feed> feedList) {
private List<FeedVo> buildFeedResp(List<Feed> feedList, Long currentUid) {
return buildFeedResp(feedList, false, currentUid);
}
/**
* @param feedList 朋友圈基础内容
* @param currentUid 当前用户ID用于判断是否已点赞
* @return
*/
private List<FeedVo> buildFeedResp(List<Feed> feedList, boolean isDetail, Long currentUid) {
List<FeedVo> feedVos = new ArrayList<>();
// 批量获取所有发布者的用户信息
Set<Long> userIds = feedList.stream().map(Feed::getUid).collect(Collectors.toSet());
Map<Long, SummeryInfoDTO> userInfoMap = userSummaryCache.getBatch(new ArrayList<>(userIds));
// 批量获取当前用户对所有朋友圈的点赞状态
Map<Long, Boolean> likeStatusMap = new HashMap<>();
if (CollUtil.isNotEmpty(feedList)) {
List<Long> 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<List<FeedMedia>> 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<Long> likeUidList = feedLikeDao.getLikeUidList(feed.getId());
if (CollUtil.isNotEmpty(likeUidList)) {
Map<Long, SummeryInfoDTO> likeUserInfoMap = userSummaryCache.getBatch(likeUidList);
List<FeedLikeVo> 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<FeedCommentVo> 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<FeedVo> result = buildFeedResp(filteredFeeds);
List<FeedVo> 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<List<FeedMedia>> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feedId));
List<FeedMedia> feedMediaList = result.getValue();
if (CollUtil.isNotEmpty(feedMediaList)){
feed.setUrls(feedMediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList()));
}
public FeedVo feedDetail(Long feedId, Long uid) {
Feed feed = feedDao.getById(feedId);
if (ObjectUtil.isNull(feed)) {
return null;
}
return feed;
// 使用 buildFeedResp 方法构建详情页的响应isDetail=true显示全部点赞
List<FeedVo> 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("请选择朋友圈!");
}

View File

@@ -23,7 +23,7 @@ import com.luohuo.flex.im.service.tenant.EmailService;
import java.time.LocalDateTime;
/**
* 邮箱服务 TODO 整合实现
* 邮箱服务
*/
@Slf4j
@Service

View File

@@ -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<CursorPageBaseResp<FeedVo>> list(@Valid @RequestBody CursorPageBaseReq request) {
@@ -66,6 +84,72 @@ public class FeedController {
@GetMapping("detail")
@Operation(summary = "用户查看详情")
public R<FeedVo> 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<Boolean> addComment(@Valid @RequestBody FeedCommentReq req) {
return R.success(feedCommentService.addComment(ContextUtil.getUid(), req));
}
@PostMapping("comment/delete")
@Operation(summary = "删除评论")
public R<Boolean> delComment(@RequestParam(value = "commentId", required = false) Long commentId,
@RequestBody(required = false) Map<String, Long> 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<CursorPageBaseResp<FeedCommentVo>> 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<Integer> getCommentCount(@RequestParam Long feedId) {
return R.success(feedCommentService.getCommentCount(feedId));
}
@GetMapping("comment/all")
@Operation(summary = "获取所有评论列表(不分页)")
public R<List<FeedCommentVo>> getAllComments(@RequestParam Long feedId) {
return R.success(feedCommentService.getCommentList(feedId, null));
}
// ==================== 点赞相关接口 ====================
@PostMapping("like/toggle")
@Operation(summary = "点赞或取消点赞")
public R<Boolean> toggleLike(@Valid @RequestBody FeedLikeReq req) {
return R.success(feedLikeService.setLike(ContextUtil.getUid(), req.getFeedId(), req.getActType()));
}
@GetMapping("like/list")
@Operation(summary = "获取点赞用户列表")
public R<List<FeedLikeVo>> getLikeList(@RequestParam Long feedId) {
return R.success(feedLikeService.getLikeList(feedId));
}
@GetMapping("like/count")
@Operation(summary = "获取点赞数量")
public R<Integer> getLikeCount(@RequestParam Long feedId) {
return R.success(feedLikeService.getLikeCount(feedId));
}
@GetMapping("like/hasLiked")
@Operation(summary = "判断是否已点赞")
public R<Boolean> hasLiked(@RequestParam Long feedId) {
return R.success(feedLikeService.hasLiked(ContextUtil.getUid(), feedId));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String> urls;
@Schema(description = "点赞数量")
private Integer likeCount;
@Schema(description = "评论数量")
private Integer commentCount;
@Schema(description = "当前用户是否已点赞")
private Boolean hasLiked;
@Schema(description = "点赞用户列表列表页显示前3个详情页显示全部")
private List<FeedLikeVo> likeList;
@Schema(description = "评论列表列表页显示前3条详情页显示全部")
private List<FeedCommentVo> commentList;
@Schema(description = "发布人昵称")
private String userName;
@Schema(description = "发布人头像")
private String userAvatar;
}

View File

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

View File

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

View File

@@ -264,6 +264,16 @@ public interface CacheKeyTable {
*/
String FEED_TARGET = "feedTarget";
/**
* 朋友圈评论
*/
String FEED_COMMENT = "feedComment";
/**
* 朋友圈点赞
*/
String FEED_LIKE = "feedLike";
/**
* 会话信息
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -77,12 +77,6 @@
<dependency>
<groupId>com.luohuo.basic</groupId>
<artifactId>luohuo-all</artifactId>
<exclusions>
<exclusion>
<groupId>com.luohuo.basic</groupId>
<artifactId>luohuo-cloud-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
@@ -116,6 +110,11 @@
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -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:/");

View File

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

View File

@@ -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: # Minimaxhttps://www.minimaxi.com/
api-key: xxxx
moonshot: # 月之暗灭KIMI
api-key: sk-abc

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<消费者分组, <Topic, Boolean>> 。默认情况下,不配置表示监听。
erbadagang-consumer-group:
topic1: false # 关闭 test-consumer-group 对 topic1 的监听消费
topic1: false # 关闭 test-consumer-group 对 topic1 的监听消费

View File

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

View File

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

View File

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

View File

@@ -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有效性

View File

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

View File

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

View File

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

View File

@@ -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);
// 转发媒体控制指令给房间内其他成员

View File

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

View File

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

View File

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

View File

@@ -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())) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 141 KiB