From d152fea9fcf79e2a0323363f8a21dd8b8780508f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=BE=E4=B9=BE?= <1046762075@qq.com> Date: Wed, 26 Nov 2025 08:16:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E6=88=90=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E5=8A=9F=E8=83=BD=E7=9A=84=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E9=80=82=E9=85=8D=E6=B7=B1=E5=BA=A6=E6=80=9D=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reids、mysql、rocketmq、nacos一键部署.md | 2 +- .../nacos_config_export_20251019194635.zip | Bin 16508 -> 0 bytes .../nacos_config_export_20251126080946.zip | Bin 0 -> 16245 bytes docs/install/sql/luohuo_dev.sql | 40 ++--- docs/install/sql/luohuo_im_01.sql | 37 +++-- .../flex/ai/config/AiAutoConfiguration.java | 22 +++ .../flex/ai/config/HulaAiProperties.java | 17 ++ .../AiChatConversationRespVO.java | 3 + .../AiChatConversationUpdateMyReqVO.java | 7 +- .../vo/message/AiChatMessageSendReqVO.java | 2 + .../vo/apikey/AiApiKeyBalanceRespVO.java | 3 + .../model/vo/model/AiModelRespVO.java | 3 + .../model/vo/model/AiModelSaveReqVO.java | 3 + .../flex/ai/core/AiModelFactoryImpl.java | 9 ++ .../flex/ai/core/model/google/GeminiApi.java | 82 ++++++++++ .../ai/core/model/google/GeminiChatModel.java | 30 ++++ .../ai/dal/chat/AiChatConversationDO.java | 5 + .../luohuo/flex/ai/dal/model/AiModelDO.java | 5 + .../luohuo/flex/ai/enums/AiPlatformEnum.java | 2 + .../flex/ai/enums/ErrorCodeConstants.java | 3 + .../chat/AiChatConversationServiceImpl.java | 10 +- .../chat/AiChatMessageServiceImpl.java | 152 +++++++++++++++++- .../ai/service/model/AiApiKeyServiceImpl.java | 42 ++--- .../com/luohuo/flex/ai/utils/AiUtils.java | 3 + .../base/mapper/system/DefLoginLogMapper.java | 5 + .../service/system/DefLoginLogService.java | 6 + .../service/system/dto/LoginCountDTO.java | 9 ++ .../system/impl/DefLoginLogServiceImpl.java | 14 ++ .../ext/system/DefLoginLogMapper.xml | 37 +++++ .../system/BaseLoginLogController.java | 39 +++++ .../flex/im/core/admin/dto/LoginCountDTO.java | 9 ++ .../core/admin/service/AdminStatsService.java | 9 ++ .../service/impl/AdminStatsServiceImpl.java | 122 +++++++++++--- .../admin/AdminStatsController.java | 43 +++++ .../domain/vo/resp/admin/ActiveUserResp.java | 25 +++ .../domain/vo/resp/admin/AdminStatsResp.java | 3 + .../domain/vo/resp/admin/LoginRankResp.java | 27 ++++ .../luohuo/flex/im/api/BaseLoginLogApi.java | 31 ++++ .../luohuo/flex/oauth/biz/ResourceBiz.java | 20 ++- .../processor/HeartbeatProcessor.java | 2 +- 40 files changed, 789 insertions(+), 94 deletions(-) delete mode 100644 docs/install/nacos/nacos_config_export_20251019194635.zip create mode 100644 docs/install/nacos/nacos_config_export_20251126080946.zip create mode 100644 luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiApi.java create mode 100644 luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiChatModel.java create mode 100644 luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/dto/LoginCountDTO.java create mode 100644 luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/dto/LoginCountDTO.java create mode 100644 luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/ActiveUserResp.java create mode 100644 luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/LoginRankResp.java create mode 100644 luohuo-cloud/luohuo-im/luohuo-im-facade/luohuo-im-cloud-impl/src/main/java/com/luohuo/flex/im/api/BaseLoginLogApi.java diff --git a/docs/install/docker/reids、mysql、rocketmq、nacos一键部署.md b/docs/install/docker/reids、mysql、rocketmq、nacos一键部署.md index a2f545e1..959bf070 100644 --- a/docs/install/docker/reids、mysql、rocketmq、nacos一键部署.md +++ b/docs/install/docker/reids、mysql、rocketmq、nacos一键部署.md @@ -18,4 +18,4 @@ chmod 777 /home/docker/rocketmq/timerwheel/ - **打开目录**: 当前文件夹下输入 cmd 回车 - **执行命令**: docker-compose up -d - **导入nacos数据库**: [mysql-schema.sql](../mysql-schema.sql) -- **导入nacos命名空间数据**: [nacos_config_export_20251019194635.zip](../nacos/nacos_config_export_20251019194635.zip) \ No newline at end of file +- **导入nacos命名空间数据**: [nacos_config_export_20251126080946.zip](../nacos/nacos_config_export_20251126080946.zip) \ No newline at end of file diff --git a/docs/install/nacos/nacos_config_export_20251019194635.zip b/docs/install/nacos/nacos_config_export_20251019194635.zip deleted file mode 100644 index c071156d0bffdea1302fd4611e5b2db90839b494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16508 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 diff --git a/docs/install/nacos/nacos_config_export_20251126080946.zip b/docs/install/nacos/nacos_config_export_20251126080946.zip new file mode 100644 index 0000000000000000000000000000000000000000..2ada2e3a8798ea23acf82b0b32d62837dfd455a7 GIT binary patch literal 16245 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 Y?aE1kL;U_4;nyqjS8kf+{Qc>F0j22ID*ylh literal 0 HcmV?d00001 diff --git a/docs/install/sql/luohuo_dev.sql b/docs/install/sql/luohuo_dev.sql index 1c342915..8f1d16e3 100644 --- a/docs/install/sql/luohuo_dev.sql +++ b/docs/install/sql/luohuo_dev.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 21/11/2025 14:56:07 + Date: 26/11/2025 08:05:50 */ SET NAMES utf8mb4; @@ -37,7 +37,7 @@ 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 = 94780244072449 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 98364012618753 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI API 密钥表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_api_key @@ -94,10 +94,11 @@ CREATE TABLE `ai_chat_conversation` ( `model` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模型标识', `pinned` bit(1) NOT NULL COMMENT '是否置顶', `pinned_time` datetime NULL DEFAULT NULL COMMENT '置顶时间', + `token_usage` bigint NOT NULL DEFAULT 0 COMMENT '总使用量', `system_message` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色设定', `temperature` double NOT NULL COMMENT '温度参数', `max_tokens` int NOT NULL COMMENT '单条回复的最大 Token 数量', - `max_contexts` int NOT NULL COMMENT '上下文的最大 Message 数量', + `max_contexts` int NOT NULL DEFAULT 50 COMMENT '上下文的最大 Message 数量', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人', @@ -105,7 +106,7 @@ 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 = 98061603300353 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99658689418753 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天对话表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_conversation @@ -137,7 +138,7 @@ CREATE TABLE `ai_chat_message` ( `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_msg_type`(`msg_type` ASC) USING BTREE COMMENT '消息内容类型索引' -) ENGINE = InnoDB AUTO_INCREMENT = 97988513358850 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99659553445378 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_message @@ -168,7 +169,7 @@ 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 = 93210018275841 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 98376230626305 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 聊天角色表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_chat_role @@ -204,7 +205,7 @@ CREATE TABLE `ai_image` ( `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 = 97776948470785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 绘画表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99652444100098 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 绘画表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_image @@ -335,6 +336,7 @@ CREATE TABLE `ai_model` ( `status` tinyint NOT NULL COMMENT '状态', `public_status` bit(1) NOT NULL DEFAULT b'1' COMMENT '公开、私有', `temperature` double NULL DEFAULT NULL COMMENT '温度参数', + `supports_reasoning` tinyint NOT NULL DEFAULT 0 COMMENT '是否支持上下文', `max_tokens` int NULL DEFAULT NULL COMMENT '单条回复的最大 Token 数量', `max_contexts` int NULL DEFAULT NULL COMMENT '上下文的最大 Message 数量', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', @@ -347,7 +349,7 @@ 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 = 93210576118273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 模型表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 98365405127681 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 模型表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_model @@ -370,7 +372,7 @@ CREATE TABLE `ai_model_usage_record` ( `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_user_model`(`user_id` ASC, `model_id` ASC) USING BTREE COMMENT '用户-模型唯一索引' -) ENGINE = InnoDB AUTO_INCREMENT = 98017718297601 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 公开模型使用记录表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99658790082049 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 公开模型使用记录表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_model_usage_record @@ -436,13 +438,13 @@ CREATE TABLE `ai_platform` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_platform`(`platform` ASC, `deleted` ASC) USING BTREE COMMENT '平台代码唯一索引' -) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 平台配置表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 平台配置表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_platform -- ---------------------------- INSERT INTO `ai_platform` VALUES (1, 'Moonshot', '月之暗灭', 'Moonshot (KIMI)', 'moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k', 'https://platform.moonshot.cn/docs', '请前往 Moonshot 官网查看可用模型列表', 1, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 02:33:25', b'0', 1); -INSERT INTO `ai_platform` VALUES (2, 'DeepSeek', 'DeepSeek', 'DeepSeek', 'deepseek-chat, deepseek-coder, deepseek-reasoner', 'https://api-docs.deepseek.com/zh-cn/guides/reasoning_model', '请前往 DeepSeek 官网查看可用模型列表', 2, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 03:05:33', b'0', 1); +INSERT INTO `ai_platform` VALUES (2, 'DeepSeek', 'DeepSeek', 'DeepSeek', 'deepseek-chat, deepseek-coder, deepseek-reasoner, deepskke, d, deep, deep-s, deepseek-, deepseek-re', 'https://api-docs.deepseek.com/zh-cn/guides/reasoning_model', '请前往 DeepSeek 官网查看可用模型列表', 2, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 03:05:33', b'0', 1); INSERT INTO `ai_platform` VALUES (3, 'YiYan', '文心一言', 'Baidu (文心一言)', 'ernie-bot-4, ernie-bot-turbo, ernie-bot', 'https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html', '请前往百度智能云官网查看可用模型列表', 13, 0, '', '2025-11-11 01:19:48', '', '2025-11-12 11:15:21', b'0', 1); INSERT INTO `ai_platform` VALUES (4, 'TongYi', '通义千问', 'Alibaba (通义千问)', 'qwen-turbo, qwen-plus, qwen-max, qwen-long', 'https://help.aliyun.com/zh/dashscope/developer-reference/model-square', '请前往阿里云官网查看可用模型列表', 4, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 02:33:25', b'0', 1); INSERT INTO `ai_platform` VALUES (5, 'HunYuan', '混元', 'Tencent (混元)', 'hunyuan-lite, hunyuan-standard, hunyuan-pro', 'https://cloud.tencent.com/document/product/1729', '请前往腾讯云官网查看可用模型列表', 5, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 02:33:25', b'0', 1); @@ -459,6 +461,7 @@ INSERT INTO `ai_platform` VALUES (15, 'Anthropic', 'Anthropic', 'Anthropic', 'cl INSERT INTO `ai_platform` VALUES (16, 'Google', 'Google', 'Google', 'gemini-pro, gemini-pro-vision, gemini-1.5-pro', 'https://ai.google.dev/models', '请前往 Google AI 官网查看可用模型列表', 16, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 02:33:25', b'0', 1); INSERT INTO `ai_platform` VALUES (17, 'Ollama', 'Ollama', 'Ollama', 'llama2, mistral, neural-chat, dolphin-mixtral', 'https://ollama.ai/library', '请前往 Ollama 官网查看可用模型列表', 17, 0, '', '2025-11-11 01:19:48', '', '2025-11-11 02:33:25', b'0', 1); INSERT INTO `ai_platform` VALUES (18, 'OpenRouter', 'OpenRouter', 'OpenRouter', 'openai/gpt-3.5-turbo, openai/gpt-4, anthropic/claude-3-opus', 'https://openrouter.ai/docs', '请前往 OpenRouter 官网查看可用模型列表', 3, 0, '', '2025-11-12 04:16:46', '', '2025-11-12 11:15:13', b'0', 1); +INSERT INTO `ai_platform` VALUES (19, 'Gemini', 'Gemini', 'Google (Gemini)', 'gemini-1.5-pro, gemini-1.5-flash', 'https://developers.google.cn/ai/build', '请前往 Google AI Build 查看可用模型列表', 10, 0, '', '2025-11-21 00:00:00', '', '2025-11-21 00:00:00', b'0', 1); -- ---------------------------- -- Table structure for ai_tool @@ -518,7 +521,7 @@ CREATE TABLE `ai_video` ( INDEX `idx_status`(`status` ASC) USING BTREE, INDEX `idx_task_id`(`task_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 98017718297602 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 视频生成表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99652406351361 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'AI 视频生成表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of ai_video @@ -681,7 +684,7 @@ CREATE TABLE `base_employee` ( -- ---------------------------- -- Records of base_employee -- ---------------------------- -INSERT INTO `base_employee` VALUES (1, 0, b'1', 1458051094994223104, 61170828529941, 1451532773234835456, NULL, '2439646234', 0, '', '20', '10', b'1', 1451549146992345088, '2021-11-09 10:54:44', 2, '2024-10-08 15:31:36', 0, 0, 1); +INSERT INTO `base_employee` VALUES (1, 0, b'1', 1458051094994223104, 61170828529941, NULL, NULL, '2439646234', 0, '', '20', '10', b'1', 1451549146992345088, '2021-11-09 10:54:44', 2, '2024-10-08 15:31:36', 0, 0, 1); -- ---------------------------- -- Table structure for base_employee_org_rel @@ -1636,7 +1639,7 @@ INSERT INTO `def_interface_property` VALUES (250660012290998281, 250025856074776 DROP TABLE IF EXISTS `def_login_log`; CREATE TABLE `def_login_log` ( `id` bigint NOT NULL COMMENT '主键', - `tenant_id` bigint NULL DEFAULT NULL COMMENT '所属企业', + `tenant_id` bigint NULL DEFAULT 1 COMMENT '所属企业', `employee_id` bigint NULL DEFAULT NULL COMMENT '登录员工', `user_id` bigint NULL DEFAULT NULL COMMENT '登录用户', `request_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '登录IP', @@ -1784,6 +1787,7 @@ INSERT INTO `def_resource` VALUES (5, 1, 'im:moment', '朋友圈管理', '20', 1 INSERT INTO `def_resource` VALUES (6, 1, 'im:contact', '联系人 / 好友管理', '20', 1, '01', '联系人、好友关系、申请记录等', '/im/contact', 'page/ImContact', '', 'AddressBook', b'0', b'0', b'1', 60, '', b'0', b'1', NULL, NULL, NULL, '/1/', 1, '{}', 1, '2021-12-12 12:12:12', 1, '2021-12-12 12:12:12', 0); INSERT INTO `def_resource` VALUES (7, 1, 'im:config', 'IM 配置', '20', 1, '01', 'im_config 配置、系统参数与高级设置', '/im/config', 'page/ImConfig', '', 'Settings', b'0', b'0', b'1', 70, '', b'0', b'1', NULL, NULL, NULL, '/1/', 1, '{}', 1, '2021-12-12 12:12:12', 1, '2021-12-12 12:12:12', 0); INSERT INTO `def_resource` VALUES (8, 1, 'ai:model', 'AI 能力中心', '20', 1, '01', '配置 AI 平台与模型,查看调用情况', '/ai/model', 'page/AiModel', '', 'Robot', b'0', b'0', b'1', 80, '', b'0', b'1', NULL, NULL, NULL, '/1/', 1, '{}', 1, '2021-12-12 12:12:12', 1, '2021-12-12 12:12:12', 0); +INSERT INTO `def_resource` VALUES (9, 1, 'im:active', '活跃用户', '20', 1, '01', '按时间范围查看活跃用户列表', '/im/active', 'page/ActiveUsers', '', 'Users', b'0', b'0', b'1', 20, '', b'0', b'1', NULL, NULL, NULL, '/1/', 1, '{}', 1, '2021-12-12 12:12:12', 1, '2021-12-12 12:12:12', 0); INSERT INTO `def_resource` VALUES (138191972908138500, 3, 'demo:multiple:view3', '多级-隐藏菜单3', '20', 138191972908138497, '01', '', '/multiple/view3', 'demo/test/index', NULL, '', b'1', b'0', b'1', 30, '', b'0', b'1', NULL, NULL, NULL, '/138191972908138497/', 1, '{}', 2, '2021-12-12 12:12:12', 2, '2024-03-05 22:45:51', 0); INSERT INTO `def_resource` VALUES (138191972908138501, 3, 'demo:multiple:menu4', '下级是隐藏菜单', '20', 138191972908138497, '01', '', '/multiple/menu4', 'demo/test/index', NULL, '', b'0', b'0', b'1', 40, '', b'0', b'1', NULL, NULL, NULL, '/138191972908138497/', 1, '{}', 2, '2021-12-12 12:12:12', 2, '2024-03-05 22:47:07', 0); INSERT INTO `def_resource` VALUES (138191972908138502, 3, 'demo:multiple:menu4:view1', '多级-隐藏菜单-视图1', '20', 138191972908138501, '01', '', '/multiple/menu4/view1', 'demo/test/index', NULL, '', b'1', b'0', b'1', 1, '', b'0', b'1', NULL, NULL, NULL, '/138191972908138497/138191972908138501/', 2, '{\"currentActiveMenu\":\"/multiple/menu4\"}', 2, '2021-12-12 12:12:12', 2, '2024-05-15 23:08:31', 0); @@ -2281,8 +2285,8 @@ CREATE TABLE `def_user` ( -- ---------------------------- -- Records of def_user -- ---------------------------- -INSERT INTO `def_user` VALUES (61170828519936, 2, '15147891644', '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-11-17 09:42:12', 4, 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, '13275346112', '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\": \"121.35.47.142\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"121.35.47.142\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', NULL, 0, NULL, 'a4d5c225e6709ba025272a31c7e90e0121d5e5ba16695afe0b61370bedb677d0', 'Dawn', '2025-11-20 19:26:45', 1, '2025-03-27 04:23:08', NULL, '2025-11-20 23:47:36', 0, 1); +INSERT INTO `def_user` VALUES (61170828519936, 2, '15147891644', '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-11-25 12:09:10', 5, NULL, 'b6351c5c1e5c172aca2ba36c00598a4b77db41f2240f38a9ed5f6b0ed98e4251', 'HuLa小管家', '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, '13275346112', '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\": \"116.24.67.61\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"116.24.67.61\", \"isp\": \"电信\", \"area\": \"\", \"city\": \"深圳\", \"isp_id\": \"100017\", \"region\": \"广东\", \"city_id\": \"440300\", \"country\": \"中国\", \"region_id\": \"440000\", \"country_id\": \"CN\"}}', NULL, 0, NULL, 'ab6ec4358efb64760e5f248c1f9130d03aaa1856f9c03e4dc5fa63fb38a796b6', 'Dawn', '2025-11-26 05:10:02', 1, '2025-03-27 04:23:08', NULL, '2025-11-26 07:32:15', 0, 1); -- ---------------------------- -- Table structure for def_user_application @@ -2358,7 +2362,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, '腾讯邮件', 1163, 62, '2025-11-21 14:50:31', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); +INSERT INTO `extend_interface_log` VALUES (655249535051914248, 244881451621810192, '腾讯邮件', 1334, 62, '2025-11-26 04:10:51', '2025-07-16 18:41:01', NULL, '2025-07-16 18:41:01', NULL, 0, 0); -- ---------------------------- -- Table structure for extend_interface_logging @@ -2572,7 +2576,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 = 1011 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 1036 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/docs/install/sql/luohuo_im_01.sql b/docs/install/sql/luohuo_im_01.sql index c797c8eb..28e072be 100644 --- a/docs/install/sql/luohuo_im_01.sql +++ b/docs/install/sql/luohuo_im_01.sql @@ -11,7 +11,7 @@ Target Server Version : 80030 (8.0.30) File Encoding : 65001 - Date: 21/11/2025 14:56:13 + Date: 26/11/2025 08:05:58 */ SET NAMES utf8mb4; @@ -151,7 +151,7 @@ CREATE TABLE `im_contact` ( INDEX `idx_create_time`(`create_time` ASC) USING BTREE, INDEX `idx_update_time`(`update_time` ASC) USING BTREE, INDEX `idx_contact_room_uid_hide`(`room_id` ASC, `uid` ASC, `hide` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 69082079603081 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 69082079607468 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会话列表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_contact @@ -324,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 = 98068775162882 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100866 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '群成员表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_group_member @@ -387,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 = 98069286867969 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99741140639233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message @@ -417,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 = 96691856460801 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99551872670721 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息标记表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_message_mark @@ -449,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 = 98015092265987 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99540803902467 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_notice @@ -499,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 = 98068775162883 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100867 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room -- ---------------------------- -INSERT INTO `im_room` VALUES (1, 1, 1, '2025-11-21 14:52:20.024', 98069286867968, NULL, '2024-07-10 11:17:15.521', '2025-11-21 06:52:20.138', 1, 1, NULL, 0); +INSERT INTO `im_room` VALUES (1, 1, 1, '2025-11-26 05:35:41.099', 99741140639232, NULL, '2024-07-10 11:17:15.521', '2025-11-25 21:35:41.134', 1, 1, NULL, 0); -- ---------------------------- -- Table structure for im_room_friend @@ -529,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 = 98068775162884 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100868 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '单聊房间表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_room_friend @@ -628,7 +628,6 @@ CREATE TABLE `im_user` ( `resume` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '个人简介', `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间', - `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户密码', `create_by` bigint NOT NULL DEFAULT 1 COMMENT '创建者', `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', `is_del` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', @@ -641,13 +640,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 = 98068775162881 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100865 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-11-14 08:39:22.859', 'k.23772439646234', 0, NULL, 0, '2025-05-09 18:24:37.089', 0, 1, 10); -INSERT INTO `im_user` VALUES (10937855681024, 61170828519937, 3, 'Dawn', 'https://cdn.hulaspark.com/avatar/2439646234/97320189485dca88dcc7a70054445a56.webp', '2439646234@qq.com', '13275346112', NULL, '', 23, '2025-07-30 15:31:57.651', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"121.35.47.142\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"121.35.47.142\", \"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-20 11:10:32.943', 'k.2439646234', 0, NULL, 0, '2025-09-20 21:35:31.415', 0, 1, 10); +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\": \"116.24.67.61\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"116.24.67.61\", \"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-24 18:51:22.015', 0, NULL, 0, '2025-05-09 18:24:37.089', 0, 1, 10); +INSERT INTO `im_user` VALUES (10937855681024, 61170828519937, 3, 'Dawn', 'https://cdn.hulaspark.com/avatar/2439646234/97320189485dca88dcc7a70054445a56.webp', '2439646234@qq.com', '13275346112', NULL, '', 23, '2025-07-30 15:31:57.651', '{\"createIp\": \"206.237.119.215\", \"updateIp\": \"116.24.67.61\", \"createIpDetail\": null, \"updateIpDetail\": {\"ip\": \"116.24.67.61\", \"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-25 20:33:08.979', 0, NULL, 0, '2025-09-20 21:35:31.415', 0, 1, 10); -- ---------------------------- -- Table structure for im_user_apply @@ -676,7 +675,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 = 98015092265985 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99540803902465 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户申请表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_apply @@ -703,7 +702,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 = 98068775162891 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100875 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户背包表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_backpack @@ -746,7 +745,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 = 97749185974785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99458897533953 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表情包' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_emoji @@ -779,7 +778,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 = 98068775162886 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99719888100870 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户联系人表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of im_user_friend @@ -957,7 +956,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 = 98069286867970 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 99741140639234 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '本地消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of secure_invoke_record @@ -976,7 +975,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 = 235 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 240 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB;WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of worker_node diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/AiAutoConfiguration.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/AiAutoConfiguration.java index 41f7d6f0..28a2e8b3 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/AiAutoConfiguration.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/AiAutoConfiguration.java @@ -18,6 +18,10 @@ import com.luohuo.flex.ai.core.model.HunYuanChatModel; import com.luohuo.flex.ai.core.model.MidjourneyApi; import com.luohuo.flex.ai.core.model.SunoApi; import com.luohuo.flex.ai.core.model.XingHuoChatModel; +import com.luohuo.flex.ai.core.model.google.GeminiChatModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; import com.luohuo.flex.ai.core.model.openrouter.OpenRouterApiConstants; import com.luohuo.flex.ai.core.model.openrouter.OpenRouterChatModel; import com.luohuo.flex.ai.core.model.silicon.SiliconFlowApiConstants; @@ -305,6 +309,24 @@ public class AiAutoConfiguration { return new SunoApi(hulaAiProperties.getSuno().getBaseUrl()); } + @Bean + @ConditionalOnProperty(value = "luohuo.ai.gemini.enable", havingValue = "true") + public GeminiChatModel geminiChatClient(HulaAiProperties hulaAiProperties) { + HulaAiProperties.GeminiProperties properties = hulaAiProperties.getGemini(); + String baseUrl = StrUtil.isNotEmpty(properties.getBaseUrl()) ? properties.getBaseUrl() : "https://generativelanguage.googleapis.com/v1beta/openai"; + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() + .openAiApi(OpenAiApi.builder().baseUrl(baseUrl).apiKey(properties.getApiKey()).build()) + .defaultOptions(OpenAiChatOptions.builder() + .model(properties.getModel()) + .temperature(properties.getTemperature()) + .maxTokens(properties.getMaxTokens()) + .topP(properties.getTopP()) + .build()) + .toolCallingManager(getToolCallingManager()) + .build(); + return new GeminiChatModel(openAiChatModel); + } + // ========== RAG 相关 ========== @Bean diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/HulaAiProperties.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/HulaAiProperties.java index fd296fe4..55d7758d 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/HulaAiProperties.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/config/HulaAiProperties.java @@ -61,6 +61,9 @@ public class HulaAiProperties { @SuppressWarnings("SpellCheckingInspection") private OpenRouterProperties openrouter; + @SuppressWarnings("SpellCheckingInspection") + private GeminiProperties gemini; + @Data public static class DeepSeekProperties { @@ -174,4 +177,18 @@ public class HulaAiProperties { private Double topP; } + + @Data + public static class GeminiProperties { + + private String enable; + private String apiKey; + private String baseUrl; + + private String model; + private Double temperature; + private Integer maxTokens; + private Double topP; + + } } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationRespVO.java index 00779f11..57943f23 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationRespVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationRespVO.java @@ -52,6 +52,9 @@ public class AiChatConversationRespVO implements VO { @Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Integer maxContexts; + @Schema(description = "会话累计 Token 用量", example = "12345") + private Integer tokenUsage; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java index 590988b5..14aa5cf9 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java @@ -33,10 +33,7 @@ public class AiChatConversationUpdateMyReqVO { @Schema(description = "温度参数", example = "0.8") private Double temperature; - @Schema(description = "单条回复的最大 Token 数量", example = "4096") - private Integer maxTokens; - - @Schema(description = "上下文的最大 Message 数量", example = "10") - private Integer maxContexts; + @Schema(description = "会话累计 Token 用量", example = "12345") + private Integer tokenUsage; } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/message/AiChatMessageSendReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/message/AiChatMessageSendReqVO.java index 92864a82..33def797 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/message/AiChatMessageSendReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/chat/vo/message/AiChatMessageSendReqVO.java @@ -20,4 +20,6 @@ public class AiChatMessageSendReqVO { @Schema(description = "是否携带上下文", example = "true") private Boolean useContext; + @Schema(description = "是否开启深度思考模式", example = "false") + private Boolean reasoningEnabled = false; } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java index 75c55b15..c509bacf 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/apikey/AiApiKeyBalanceRespVO.java @@ -58,6 +58,9 @@ public class AiApiKeyBalanceRespVO { @Schema(description = "总余额", example = "110.00") private BigDecimal totalBalance; + @Schema(description = "使用总量(按平台返回的单位,可能为金额或额度)", example = "12.34") + private BigDecimal usageTotal; + @Schema(description = "赠送余额", example = "10.00") private BigDecimal grantedBalance; diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java index a8d1697b..10a7a230 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelRespVO.java @@ -50,6 +50,9 @@ public class AiModelRespVO { @Schema(description = "上下文的最大 Message 数量", example = "8192") private Integer maxContexts; + @Schema(description = "是否支持深度思考模式", example = "true") + private Boolean supportsReasoning; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java index 99b52d7c..24b14836 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/controller/model/vo/model/AiModelSaveReqVO.java @@ -53,6 +53,9 @@ public class AiModelSaveReqVO { @Schema(description = "上下文的最大 Message 数量", example = "8192") private Integer maxContexts; + @Schema(description = "是否支持深度思考模式", example = "false") + private Boolean supportsReasoning; + @Schema(description = "是否公开:0-公开,1-私有", example = "0") private Integer publicStatus; diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/AiModelFactoryImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/AiModelFactoryImpl.java index eaffd3c4..0690308f 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/AiModelFactoryImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/AiModelFactoryImpl.java @@ -150,6 +150,7 @@ public class AiModelFactoryImpl implements AiModelFactory { case OPENAI -> buildOpenAiChatModel(apiKey, url); case AZURE_OPENAI -> buildAzureOpenAiChatModel(apiKey, url); case OLLAMA -> buildOllamaChatModel(url); + case GEMINI -> buildGeminiChatModel(apiKey, url); default -> throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); }; } @@ -188,6 +189,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return SpringUtil.getBean(AzureOpenAiChatModel.class); case OLLAMA: return SpringUtil.getBean(OllamaChatModel.class); + case GEMINI: + return SpringUtil.getBean(com.luohuo.flex.ai.core.model.google.GeminiChatModel.class); default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); } @@ -469,6 +472,12 @@ public class AiModelFactoryImpl implements AiModelFactory { return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build(); } + private ChatModel buildGeminiChatModel(String apiKey, String url) { + String baseUrl = StrUtil.blankToDefault(url, "https://generativelanguage.googleapis.com/v1beta/openai"); + OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(baseUrl).apiKey(apiKey).build(); + return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build(); + } + /** * 构建 OpenRouter 聊天模型 */ diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiApi.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiApi.java new file mode 100644 index 00000000..55193d1b --- /dev/null +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiApi.java @@ -0,0 +1,82 @@ +package com.luohuo.flex.ai.core.model.google; + +import lombok.Data; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; + +import java.util.List; +import java.util.Map; + +public class GeminiApi { + private final RestClient client; + private final String apiKey; + private final String baseUrl; + + public GeminiApi(String baseUrl, String apiKey) { + this.client = RestClient.builder().baseUrl(baseUrl).build(); + this.apiKey = apiKey; + this.baseUrl = baseUrl; + } + + public GenerateResponse generateContent(String model, GenerateRequest request) { + String path = "/models/" + model + ":generateContent"; + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("key", apiKey); + return client.post() + .uri(builder -> builder.path(path).queryParams(params).build()) + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .retrieve() + .body(GenerateResponse.class); + } + + @Data + public static class GenerateRequest { + private List contents; + private GenerationConfig generationConfig; + private SystemInstruction systemInstruction; + } + + @Data + public static class SystemInstruction { + private List parts; + } + + @Data + public static class Content { + private String role; + private List parts; + } + + @Data + public static class Part { + private String text; + private InlineData inlineData; + private Map fileData; + } + + @Data + public static class InlineData { + private String mimeType; + private String data; + } + + @Data + public static class GenerationConfig { + private Double temperature; + private Integer maxOutputTokens; + private Double topP; + } + + @Data + public static class GenerateResponse { + private List candidates; + } + + @Data + public static class Candidate { + private Content content; + } +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiChatModel.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiChatModel.java new file mode 100644 index 00000000..77fbd14e --- /dev/null +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/core/model/google/GeminiChatModel.java @@ -0,0 +1,30 @@ +package com.luohuo.flex.ai.core.model.google; + +import lombok.RequiredArgsConstructor; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import reactor.core.publisher.Flux; + +@RequiredArgsConstructor +public class GeminiChatModel implements ChatModel, StreamingChatModel { + private final OpenAiChatModel delegate; + + @Override + public ChatResponse call(Prompt prompt) { + return delegate.call(prompt); + } + + @Override + public Flux stream(Prompt prompt) { + return delegate.stream(prompt); + } + + @Override + public ChatOptions getDefaultOptions() { + return delegate.getDefaultOptions(); + } +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/chat/AiChatConversationDO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/chat/AiChatConversationDO.java index c599340e..f25abeac 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/chat/AiChatConversationDO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/chat/AiChatConversationDO.java @@ -98,6 +98,11 @@ public class AiChatConversationDO extends BaseDO { * 上下文的最大 Message 数量 */ private Integer maxContexts; + + /** + * 会话累计 Token 用量 + */ + private Integer tokenUsage; /** * 租户id */ diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java index a42cdcc7..a361a20e 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/dal/model/AiModelDO.java @@ -107,4 +107,9 @@ public class AiModelDO extends BaseDO { */ private Integer maxContexts; + /** + * 是否支持深度思考模式 + */ + private Boolean supportsReasoning; + } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/AiPlatformEnum.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/AiPlatformEnum.java index 21814ddd..4230be4b 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/AiPlatformEnum.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/AiPlatformEnum.java @@ -53,6 +53,8 @@ public enum AiPlatformEnum { SUNO("Suno","Suno"), // Suno AI + GEMINI("Gemini","Gemini"), + ; /** diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/ErrorCodeConstants.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/ErrorCodeConstants.java index caa7e6c3..e4cb825d 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/ErrorCodeConstants.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/enums/ErrorCodeConstants.java @@ -77,4 +77,7 @@ public interface ErrorCodeConstants { // ========== AI 使用限制 1-040-012-000 ========== ErrorCode AI_USAGE_LIMIT_EXCEEDED = new ErrorCode(1_040_012_000, "AI使用次数已用完"); + // ========== 会话 Token 限制 1-040-013-000 ========== + ErrorCode CHAT_TOKEN_BUDGET_EXCEEDED = new ErrorCode(1_040_013_000, "本会话的 Token 预算已用完,请新建会话或更换模型"); + } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java index 4bf71ca1..106b8022 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatConversationServiceImpl.java @@ -71,7 +71,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService // 2. 创建 AiChatConversationDO 聊天对话 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) .setModelId(model.getId()).setModel(model.getModel()) - .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); + .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()) + .setTokenUsage(0); if (role != null) { conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); } else { @@ -108,6 +109,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService // 2. 更新对话信息 AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); + // 禁止会话层直接修改 maxTokens / maxContexts,统一由模型驱动 + updateObj.setMaxTokens(null); + updateObj.setMaxContexts(null); if (Boolean.TRUE.equals(updateReqVO.getPinned())) { updateObj.setPinnedTime(LocalDateTime.now()); } @@ -117,8 +121,10 @@ public class AiChatConversationServiceImpl implements AiChatConversationService updateObj.setSystemMessage(role.getSystemMessage()); } if (model != null) { - updateObj.setModelId(model.getId()); + updateObj.setModelId(model.getId()); updateObj.setModel(model.getModel()); + updateObj.setMaxTokens(model.getMaxTokens()); + updateObj.setMaxContexts(model.getMaxContexts()); } chatConversationMapper.updateById(updateObj); } diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java index ce5d75eb..f6fc556c 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/chat/AiChatMessageServiceImpl.java @@ -4,9 +4,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.luohuo.basic.context.ContextUtil; -import com.luohuo.basic.tenant.core.aop.TenantIgnore; import com.luohuo.flex.ai.common.pojo.PageResult; import com.luohuo.flex.ai.controller.chat.vo.conversation.AiDelReqVO; +import com.luohuo.flex.ai.controller.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessagePageReqVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageRespVO; import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageSendReqVO; @@ -61,6 +61,7 @@ import static com.luohuo.flex.ai.common.pojo.CommonResult.error; import static com.luohuo.basic.base.R.success; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; import static com.luohuo.flex.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST; +import static com.luohuo.flex.ai.enums.ErrorCodeConstants.CHAT_PROMPT_TOO_LONG; import static com.luohuo.flex.ai.utils.ServiceExceptionUtil.exception; import static com.luohuo.basic.utils.collection.CollectionUtils.convertList; @@ -110,6 +111,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 1.2 校验模型 AiModelDO model = modalService.validateModel(conversation.getModelId()); + // 1.2.1 会话 Token 预算校验:累计 tokenUsage 超过模型 maxTokens 则拒绝 + if (model.getMaxTokens() != null && conversation.getTokenUsage() != null + && conversation.getTokenUsage() >= model.getMaxTokens()) { + throw exception(ErrorCodeConstants.CHAT_TOKEN_BUDGET_EXCEEDED); + } + // 1.3 检查并扣减模型使用次数 modelUsageService.checkAndDeductUsage(userId, model); @@ -135,6 +142,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 3.3 更新响应内容 String newContent = chatResponse.getResult().getOutput().getText(); chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); + // 用量统计:prompt + completion + int promptTokens = estimateContextTokens(conversation, historyMessages, sendReqVO.getContent()); + int completionTokens = estimateTokens(newContent); + int newTotal = (conversation.getTokenUsage() == null ? 0 : conversation.getTokenUsage()) + promptTokens + completionTokens; + AiChatConversationUpdateMyReqVO updateReq = new AiChatConversationUpdateMyReqVO(); + updateReq.setId(conversation.getId()); + updateReq.setTokenUsage(newTotal); + chatConversationService.updateChatConversationMy(updateReq, userId); // 3.4 响应结果 Map documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); @@ -168,6 +183,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { StreamingChatModel chatModel = modalService.getChatModel(model.getId()); + // 1.2.1 会话 Token 预算校验:累计 tokenUsage 超过模型 maxTokens 则拒绝 + if (model.getMaxTokens() != null && conversation.getTokenUsage() != null + && conversation.getTokenUsage() >= model.getMaxTokens()) { + return Flux.just(error(ErrorCodeConstants.CHAT_TOKEN_BUDGET_EXCEEDED)); + } + // 2. 知识库找回 List knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(), conversation); @@ -204,33 +225,48 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 响应结果 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; newContent = StrUtil.nullToDefault(newContent, ""); - contentBuffer.append(newContent); + String answerChunk = stripReasoningTags(newContent); + contentBuffer.append(answerChunk); // 提取推理内容 String newReasoningContent = null; if (chunk.getMetadata() != null && chunk.getMetadata().containsKey("reasoning_content")) { newReasoningContent = String.valueOf(chunk.getMetadata().get("reasoning_content")); reasoningBuffer.append(newReasoningContent); + } else { + String extracted = extractReasoningFromText(newContent); + if (StrUtil.isNotEmpty(extracted)) { + newReasoningContent = extracted; + reasoningBuffer.append(extracted); + } } return success(new AiChatMessageSendRespVO() .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) - .setContent(newContent) - .setReasoningContent(newReasoningContent) - .setSegments(segments))); + .setContent(answerChunk) + .setReasoningContent(newReasoningContent) + .setSegments(segments))); }).doOnComplete(() -> { // 手动设置租户信息(因为 Flux 异步会切换线程,导致 ThreadLocal 丢失) try { ContextUtil.setIgnore(true); - String content = contentBuffer.toString(); + String content = stripReasoningTags(contentBuffer.toString()); String reasoningContent = reasoningBuffer.toString(); AiChatMessageDO updateMessage = new AiChatMessageDO().setId(assistantMessage.getId()).setContent(content); if (StrUtil.isNotEmpty(reasoningContent)) { updateMessage.setReasoningContent(reasoningContent); } chatMessageMapper.updateById(updateMessage); + // 用量统计:prompt + completion(流式完成时) + int promptTokens = estimateContextTokens(conversation, historyMessages, sendReqVO.getContent()); + int completionTokens = estimateTokens(content); + int newTotal = (conversation.getTokenUsage() == null ? 0 : conversation.getTokenUsage()) + promptTokens + completionTokens; + AiChatConversationUpdateMyReqVO streamUpdateReq = new AiChatConversationUpdateMyReqVO(); + streamUpdateReq.setId(conversation.getId()); + streamUpdateReq.setTokenUsage(newTotal); + chatConversationService.updateChatConversationMy(streamUpdateReq, conversation.getUserId()); } finally { ContextUtil.setIgnore(false); } @@ -261,11 +297,27 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { private Prompt buildPrompt(AiChatConversationDO conversation, List messages, List knowledgeSegments, AiModelDO model, AiChatMessageSendReqVO sendReqVO) { + // 上下文条数限制:携带上下文时,历史问答对已达上限则阻止继续对话 + if (Boolean.TRUE.equals(sendReqVO.getUseContext())) { + int pairs = countContextPairs(messages); + if (conversation.getMaxContexts() != null && pairs >= conversation.getMaxContexts()) { + throw exception(CHAT_PROMPT_TOO_LONG); + } + } List chatMessages = new ArrayList<>(); // 1.1 System Context 角色设定 if (StrUtil.isNotBlank(conversation.getSystemMessage())) { chatMessages.add(new SystemMessage(conversation.getSystemMessage())); } + // 1.1.1 深度思考系统提示(仅对支持的模型生效) + if (sendReqVO.getReasoningEnabled() && isReasoningSupported(model)) { + int thinkingBudget = 1024; + chatMessages.add(new SystemMessage( + "你是深度思考助手。先在‘思考过程’中进行详细推理,再给出最终答案。请保持推理与答案清晰分离。" + + "将推理内容使用 包裹,将最终答案使用 包裹。" + + StrUtil.format("思考阶段最多使用 {} token。", thinkingBudget) + )); + } // 1.2 历史 history message 历史消息 List contextMessages = filterContextMessages(messages, conversation, sendReqVO); @@ -300,6 +352,94 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { return new Prompt(chatMessages, chatOptions); } + private boolean isReasoningSupported(AiModelDO model) { + return Boolean.TRUE.equals(model.getSupportsReasoning()); + } + + private int estimateContextTokens(AiChatConversationDO conversation, List allMessages, String currentContent) { + int total = 0; + AiChatMessageSendReqVO ctxReq = new AiChatMessageSendReqVO(); + ctxReq.setUseContext(true); + List contextMessages = filterContextMessages(allMessages, conversation, ctxReq); + for (AiChatMessageDO m : contextMessages) { + total += estimateTokens(m.getContent()); + if (StrUtil.isNotEmpty(m.getReasoningContent())) { + total += estimateTokens(m.getReasoningContent()); + } + } + if (StrUtil.isNotEmpty(currentContent)) { + total += estimateTokens(currentContent); + } + return total; + } + + private int countContextPairs(List messages) { + int pairs = 0; + for (int i = messages.size() - 1; i >= 0; i--) { + AiChatMessageDO assistantMessage = CollUtil.get(messages, i); + if (assistantMessage == null || assistantMessage.getReplyId() == null) { + continue; + } + AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); + if (userMessage == null + || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) + || StrUtil.isEmpty(assistantMessage.getContent())) { + continue; + } + pairs++; + i--; // 跳过已配对的 user + } + return pairs; + } + + private int estimateTokens(String text) { + if (StrUtil.isEmpty(text)) return 0; + String ascii = text.replaceAll("[^\\x00-\\x7F]", ""); + int nonAsciiCount = text.length() - ascii.length(); + String[] words = ascii.trim().split("\\s+"); + int asciiTokens = 0; + for (String w : words) { + if (StrUtil.isEmpty(w)) continue; + asciiTokens += (int) Math.ceil(w.length() / 4.0); + } + return asciiTokens + nonAsciiCount; + } + + private String extractReasoningFromText(String text) { + if (StrUtil.isEmpty(text)) return null; + StringBuilder sb = new StringBuilder(); + int start = 0; + while (true) { + int s = text.indexOf("", start); + if (s < 0) break; + int e = text.indexOf("", s + 10); + if (e < 0) break; + sb.append(text, s + 10, e); + start = e + 11; + } + if (sb.length() > 0) return sb.toString(); + start = 0; + while (true) { + int s = text.indexOf("", start); + if (s < 0) break; + int e = text.indexOf("", s + 11); + if (e < 0) break; + sb.append(text, s + 11, e); + start = e + 12; + } + return sb.length() > 0 ? sb.toString() : null; + } + + private String stripReasoningTags(String text) { + if (StrUtil.isEmpty(text)) return text; + String t = text; + t = t.replaceAll("[\\s\\S]*?", ""); + t = t.replaceAll("[\\s\\S]*?", ""); + t = t.replaceAll("", ""); + t = t.replaceAll("", ""); + return t; + } + /** * 从历史消息中,获得倒序的 n 组消息作为消息上下文 *

diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java index ee5b0df5..357ffe9e 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/service/model/AiApiKeyServiceImpl.java @@ -442,30 +442,32 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { if (data != null && data.get("limit") != null && !(data.get("limit") instanceof cn.hutool.json.JSONNull)) { limit = new BigDecimal(String.valueOf(data.get("limit"))); } - if (data != null && data.get("usage") != null && !(data.get("usage") instanceof cn.hutool.json.JSONNull)) { - usage = new BigDecimal(String.valueOf(data.get("usage"))); - } + if (data != null && data.get("usage") != null && !(data.get("usage") instanceof cn.hutool.json.JSONNull)) { + usage = new BigDecimal(String.valueOf(data.get("usage"))); + } BigDecimal remaining = null; if (limit != null) { remaining = usage != null ? limit.subtract(usage) : limit; } - if (remaining != null) { - balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() - .currency("USD") - .totalBalance(remaining) - .available(true) - .build()); - totalBalance = remaining; - } else { - balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() - .currency("USD") - .totalBalance(BigDecimal.ZERO) - .grantedBalance(BigDecimal.ZERO) - .toppedUpBalance(BigDecimal.ZERO) - .available(Boolean.TRUE.equals(freeTier)) - .build()); - totalBalance = BigDecimal.ZERO; - } + if (remaining != null) { + balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() + .currency("USD") + .totalBalance(remaining) + .usageTotal(usage) + .available(true) + .build()); + totalBalance = remaining; + } else { + balanceInfos.add(AiApiKeyBalanceRespVO.BalanceInfo.builder() + .currency("USD") + .totalBalance(BigDecimal.ZERO) + .grantedBalance(BigDecimal.ZERO) + .toppedUpBalance(BigDecimal.ZERO) + .usageTotal(BigDecimal.ZERO) + .available(Boolean.TRUE.equals(freeTier)) + .build()); + totalBalance = BigDecimal.ZERO; + } } return AiApiKeyBalanceRespVO.builder() diff --git a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/utils/AiUtils.java b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/utils/AiUtils.java index a2fe80fa..1157e93a 100644 --- a/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/utils/AiUtils.java +++ b/luohuo-cloud/luohuo-ai/luohuo-ai-biz/src/main/java/com/luohuo/flex/ai/utils/AiUtils.java @@ -64,6 +64,9 @@ public class AiUtils { case GITEE_AI: // 复用 OpenAI 客户端 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolNames(toolNames).toolContext(toolContext).build(); + case GEMINI: + return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) + .toolNames(toolNames).toolContext(toolContext).build(); case AZURE_OPENAI: return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) .toolNames(toolNames).toolContext(toolContext).build(); diff --git a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/mapper/system/DefLoginLogMapper.java b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/mapper/system/DefLoginLogMapper.java index c6a831e6..75daf801 100644 --- a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/mapper/system/DefLoginLogMapper.java +++ b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/mapper/system/DefLoginLogMapper.java @@ -1,6 +1,7 @@ package com.luohuo.flex.base.mapper.system; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.luohuo.flex.base.service.system.dto.LoginCountDTO; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import com.luohuo.basic.base.mapper.SuperMapper; @@ -29,4 +30,8 @@ public interface DefLoginLogMapper extends SuperMapper { * @return 是否成功 */ Long clearLog(@Param("clearBeforeTime") LocalDateTime clearBeforeTime, @Param("idList") List idList); + + Long selectUserCountWithMinLogins(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("minTimes") Integer minTimes); + + List selectLoginCountBetween(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("limit") Integer limit); } diff --git a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/DefLoginLogService.java b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/DefLoginLogService.java index 6dbc7a87..ce195556 100644 --- a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/DefLoginLogService.java +++ b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/DefLoginLogService.java @@ -2,8 +2,10 @@ package com.luohuo.flex.base.service.system; import com.luohuo.basic.base.service.SuperService; import com.luohuo.flex.base.entity.system.DefLoginLog; +import com.luohuo.flex.base.service.system.dto.LoginCountDTO; import java.time.LocalDateTime; +import java.util.List; /** *

@@ -23,4 +25,8 @@ public interface DefLoginLogService extends SuperService { * @return 是否成功 */ boolean clearLog(LocalDateTime clearBeforeTime, Integer clearBeforeNum); + + List getLoginRank(LocalDateTime start, LocalDateTime end, Integer limit); + + Long countUsersWithMinLogins(LocalDateTime start, LocalDateTime end, Integer minTimes); } diff --git a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/dto/LoginCountDTO.java b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/dto/LoginCountDTO.java new file mode 100644 index 00000000..c1f069ca --- /dev/null +++ b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/dto/LoginCountDTO.java @@ -0,0 +1,9 @@ +package com.luohuo.flex.base.service.system.dto; + +import lombok.Data; + +@Data +public class LoginCountDTO { + private Long uid; + private Long total; +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/impl/DefLoginLogServiceImpl.java b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/impl/DefLoginLogServiceImpl.java index c1d8f901..cf646ad1 100644 --- a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/impl/DefLoginLogServiceImpl.java +++ b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/java/com/luohuo/flex/base/service/system/impl/DefLoginLogServiceImpl.java @@ -17,9 +17,12 @@ import com.luohuo.flex.base.entity.tenant.DefUser; import com.luohuo.flex.base.manager.system.DefLoginLogManager; import com.luohuo.flex.base.manager.tenant.DefUserManager; import com.luohuo.flex.base.service.system.DefLoginLogService; +import com.luohuo.flex.base.service.system.dto.LoginCountDTO; +import com.luohuo.flex.base.mapper.system.DefLoginLogMapper; import com.luohuo.flex.base.vo.save.system.DefLoginLogSaveVO; import java.time.LocalDateTime; +import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; @@ -45,6 +48,7 @@ public class DefLoginLogServiceImpl extends SuperServiceImpl StrUtil.containsIgnoreCase(operatingSystem, b)).findAny().orElse(operatingSystem); @@ -95,4 +99,14 @@ public class DefLoginLogServiceImpl extends SuperServiceImpl 0; } + + @Override + public List getLoginRank(LocalDateTime start, LocalDateTime end, Integer limit) { + return defLoginLogMapper.selectLoginCountBetween(start, end, limit); + } + + @Override + public Long countUsersWithMinLogins(LocalDateTime start, LocalDateTime end, Integer minTimes) { + return defLoginLogMapper.selectUserCountWithMinLogins(start, end, minTimes); + } } diff --git a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/resources/mapper_base/ext/system/DefLoginLogMapper.xml b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/resources/mapper_base/ext/system/DefLoginLogMapper.xml index 2fa10149..74b856b8 100644 --- a/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/resources/mapper_base/ext/system/DefLoginLogMapper.xml +++ b/luohuo-cloud/luohuo-base/luohuo-base-biz/src/main/resources/mapper_base/ext/system/DefLoginLogMapper.xml @@ -17,4 +17,41 @@ + + + + diff --git a/luohuo-cloud/luohuo-base/luohuo-base-controller/src/main/java/com/luohuo/flex/controller/system/BaseLoginLogController.java b/luohuo-cloud/luohuo-base/luohuo-base-controller/src/main/java/com/luohuo/flex/controller/system/BaseLoginLogController.java index 35a19ec0..f5e823c8 100644 --- a/luohuo-cloud/luohuo-base/luohuo-base-controller/src/main/java/com/luohuo/flex/controller/system/BaseLoginLogController.java +++ b/luohuo-cloud/luohuo-base/luohuo-base-controller/src/main/java/com/luohuo/flex/controller/system/BaseLoginLogController.java @@ -1,6 +1,7 @@ package com.luohuo.flex.controller.system; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.luohuo.flex.base.service.system.dto.LoginCountDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -8,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -25,6 +27,7 @@ import com.luohuo.flex.base.vo.save.system.DefLoginLogSaveVO; import com.luohuo.flex.base.vo.update.system.DefLoginLogUpdateVO; import java.time.LocalDateTime; +import java.util.List; /** @@ -88,4 +91,40 @@ public class BaseLoginLogController extends SuperController> getLoginRank( + @RequestParam(value = "start", required = false) LocalDateTime start, + @RequestParam(value = "end", required = false) LocalDateTime end, + @RequestParam(value = "rangeDays", required = false) Integer rangeDays, + @RequestParam(value = "limit", required = false) Integer limit + ) { + LocalDateTime now = LocalDateTime.now(); + if (start == null || end == null) { + int days = rangeDays != null ? rangeDays : 30; + start = now.minusDays(days); + end = now; + } + if (limit == null) limit = 50; + return success(superService.getLoginRank(start, end, limit)); + } + + @GetMapping("/stats/user-count") + @Operation(summary = "登录次数达到阈值的用户数量(status=01)") + public R countUsersWithMinLogins( + @RequestParam(value = "start", required = false) LocalDateTime start, + @RequestParam(value = "end", required = false) LocalDateTime end, + @RequestParam(value = "rangeDays", required = false) Integer rangeDays, + @RequestParam(value = "minTimes", required = false) Integer minTimes + ) { + LocalDateTime now = LocalDateTime.now(); + if (start == null || end == null) { + int days = rangeDays != null ? rangeDays : 30; + start = now.minusDays(days); + end = now; + } + if (minTimes == null) minTimes = 3; + return success(superService.countUsersWithMinLogins(start, end, minTimes)); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/dto/LoginCountDTO.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/dto/LoginCountDTO.java new file mode 100644 index 00000000..1b7c81ef --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/dto/LoginCountDTO.java @@ -0,0 +1,9 @@ +package com.luohuo.flex.im.core.admin.dto; + +import lombok.Data; + +@Data +public class LoginCountDTO { + private Long employeeId; + private Long total; +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/AdminStatsService.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/AdminStatsService.java index dea0a312..cb56c83e 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/AdminStatsService.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/AdminStatsService.java @@ -1,6 +1,11 @@ package com.luohuo.flex.im.core.admin.service; +import com.luohuo.flex.im.domain.vo.resp.admin.ActiveUserResp; import com.luohuo.flex.im.domain.vo.resp.admin.AdminStatsResp; +import com.luohuo.flex.im.domain.vo.resp.admin.LoginRankResp; + +import java.time.LocalDateTime; +import java.util.List; /** * 后台管理统计服务 @@ -13,4 +18,8 @@ public interface AdminStatsService { * @return 统计数据 */ AdminStatsResp getHomeStats(); + + List getLoginRank(LocalDateTime start, LocalDateTime end, Integer limit); + + List getActiveUsers(LocalDateTime start, LocalDateTime end, Integer limit); } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/impl/AdminStatsServiceImpl.java b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/impl/AdminStatsServiceImpl.java index e6a7dc80..0b40a66e 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/impl/AdminStatsServiceImpl.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-biz/src/main/java/com/luohuo/flex/im/core/admin/service/impl/AdminStatsServiceImpl.java @@ -5,16 +5,27 @@ import com.luohuo.flex.im.core.admin.service.AdminStatsService; import com.luohuo.flex.im.core.chat.dao.RoomGroupDao; import com.luohuo.flex.im.core.user.dao.BlackDao; import com.luohuo.flex.im.core.user.dao.UserDao; +import com.luohuo.flex.im.core.user.service.cache.UserCache; import com.luohuo.flex.im.domain.entity.Black; import com.luohuo.flex.im.domain.entity.RoomGroup; import com.luohuo.flex.im.domain.entity.User; +import com.luohuo.flex.im.domain.vo.resp.admin.ActiveUserResp; import com.luohuo.flex.im.domain.vo.resp.admin.AdminStatsResp; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.Comparator; + +import com.luohuo.flex.im.api.BaseLoginLogApi; +import com.luohuo.flex.im.domain.vo.resp.admin.LoginRankResp; /** * 后台管理统计服务实现 @@ -26,15 +37,15 @@ public class AdminStatsServiceImpl implements AdminStatsService { @Resource private UserDao userDao; - @Resource private RoomGroupDao roomGroupDao; - + @Autowired + private UserCache userCache; @Resource private BlackDao blackDao; @Override - public AdminStatsResp getHomeStats() { + public AdminStatsResp getHomeStats() { // 1. 统计今日活跃用户数 LocalDateTime todayStart = LocalDateTime.now().with(LocalTime.MIN); LocalDateTime todayEnd = LocalDateTime.now().with(LocalTime.MAX); @@ -63,7 +74,7 @@ public class AdminStatsServiceImpl implements AdminStatsService { LocalDateTime weekStart = LocalDateTime.now().minusDays(LocalDateTime.now().getDayOfWeek().getValue() - 1).with(LocalTime.MIN); LambdaQueryWrapper weekBlackWrapper = new LambdaQueryWrapper<>(); weekBlackWrapper.between(Black::getCreateTime, weekStart, todayEnd); - Integer weekNewBlack = Math.toIntExact(blackDao.count(weekBlackWrapper)); + Integer weekNewBlack = Math.toIntExact(blackDao.count(weekBlackWrapper)); // 4. 构建黑名单统计 AdminStatsResp.BlackStats blackStats = AdminStatsResp.BlackStats.builder() @@ -73,20 +84,93 @@ public class AdminStatsServiceImpl implements AdminStatsService { .build(); // 5. AI 统计(假数据,需要对接 AI 服务) - AdminStatsResp.AiStats aiStats = AdminStatsResp.AiStats.builder() - .todayCalls(0) - .weekCalls(0) - .activeModels(0) - .build(); + AdminStatsResp.AiStats aiStats = AdminStatsResp.AiStats.builder() + .todayCalls(0) + .weekCalls(0) + .activeModels(0) + .build(); - // 6. 构建响应 - return AdminStatsResp.builder() - .todayActiveUser(todayActiveUser) - .totalGroup(totalGroup) - .blackCount(blackCount) - .aiCallToday(0) // TODO: 对接 AI 服务 - .blackStats(blackStats) - .aiStats(aiStats) - .build(); - } + // 6. 最近一个月登录≥3次的用户总数 + LocalDateTime monthStart = LocalDateTime.now().minusDays(30).with(LocalTime.MIN); + Long month3plus = baseLoginLogApi.countUsersWithMinLogins(monthStart, todayEnd, 3).getData(); + + // 7. 构建响应 + return AdminStatsResp.builder() + .todayActiveUser(todayActiveUser) + .totalGroup(totalGroup) + .blackCount(blackCount) + .aiCallToday(0) // TODO: 对接 AI 服务 + .blackStats(blackStats) + .aiStats(aiStats) + .monthlyLogin3PlusUserCount(month3plus != null ? month3plus.intValue() : 0) + .build(); + } + + @Resource + private BaseLoginLogApi baseLoginLogApi; + + @Override + public List getLoginRank(LocalDateTime start, LocalDateTime end, Integer limit) { + List list = baseLoginLogApi.getLoginRank(start, end, limit).getData(); + Map userMap = list.stream() + .map(dto -> dto.uid) + .distinct() + .map(userDao::getById) + .filter(u -> u != null) + .collect(Collectors.toMap(User::getId, u -> u)); + return list.stream().map(dto -> { + User u = userMap.get(dto.uid); + return LoginRankResp.builder() + .userId(dto.uid) + .username(u != null ? u.getAccount() : null) + .nickName(u != null ? u.getName() : null) + .total(dto.total) + .build(); + }).collect(Collectors.toList()); + } + + @Override + public List getActiveUsers(LocalDateTime start, LocalDateTime end, Integer limit) { + LocalDateTime s = start != null ? start : LocalDateTime.now().minusDays(30).with(LocalTime.MIN); + LocalDateTime e = end != null ? end : LocalDateTime.now().with(LocalTime.MAX); + int lim = limit != null ? limit : 200; + List ranks = baseLoginLogApi.getLoginRank(s, e, lim).getData(); + List list = userCache.getBatch(ranks.stream().map(item -> item.uid).collect(Collectors.toList())).values().stream().toList(); + + Map countMap = ranks == null ? Collections.emptyMap() : ranks.stream().collect(Collectors.toMap(r -> r.uid, r -> r.total, (a, b) -> a)); + List res = list.stream().map(u -> { + String ip = null; + String location = null; + String isp = null; + var info = u.getIpInfo(); + if (info != null) { + ip = info.getUpdateIp(); + var detail = info.getUpdateIpDetail(); + if (detail != null) { + if (ip == null) ip = detail.getIp(); + String country = detail.getCountry(); + String region = detail.getRegion(); + String city = detail.getCity(); + isp = detail.getIsp(); + StringBuilder sb = new StringBuilder(); + if (country != null && !country.isEmpty()) sb.append(country); + if (region != null && !region.isEmpty()) sb.append(region); + if (city != null && !city.isEmpty()) sb.append(city); + location = sb.length() > 0 ? sb.toString() : null; + } + } + ActiveUserResp resp = new ActiveUserResp(); + resp.setUsername(u.getAccount()); + resp.setNickName(u.getName()); + resp.setAvatar(u.getAvatar()); + resp.setLastOptTime(u.getLastOptTime()); + resp.setIp(ip); + resp.setLocation(location); + resp.setIsp(isp); + resp.setLoginTimes(countMap.getOrDefault(u.getId(), 0L).intValue()); + return resp; + }).collect(Collectors.toList()); + res.sort(Comparator.comparing(ActiveUserResp::getLoginTimes, Comparator.nullsFirst(Integer::compareTo)).reversed()); + return res; + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/admin/AdminStatsController.java b/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/admin/AdminStatsController.java index 8f9202c2..56a2329c 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/admin/AdminStatsController.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-controller/src/main/java/com/luohuo/flex/im/controller/admin/AdminStatsController.java @@ -3,14 +3,21 @@ package com.luohuo.flex.im.controller.admin; import com.luohuo.basic.base.R; import com.luohuo.flex.im.core.admin.service.AdminStatsService; import com.luohuo.flex.im.domain.vo.resp.admin.AdminStatsResp; +import com.luohuo.flex.im.domain.vo.resp.admin.LoginRankResp; +import com.luohuo.flex.im.domain.vo.resp.admin.ActiveUserResp; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + /** * 后台管理统计接口 * @author 乾乾 @@ -29,4 +36,40 @@ public class AdminStatsController { public R getHomeStats() { return R.success(adminStatsService.getHomeStats()); } + + @GetMapping("/login-rank") + @Operation(summary = "用户登录次数排行榜") + public R> getLoginRank( + @RequestParam(value = "start", required = false) LocalDateTime start, + @RequestParam(value = "end", required = false) LocalDateTime end, + @RequestParam(value = "rangeDays", required = false) Integer rangeDays, + @RequestParam(value = "limit", required = false) Integer limit + ) { + LocalDateTime now = LocalDateTime.now(); + if (start == null || end == null) { + int days = rangeDays != null ? rangeDays : 30; + start = now.minusDays(days).with(LocalTime.MIN); + end = now.with(LocalTime.MAX); + } + if (limit == null) limit = 50; + return R.success(adminStatsService.getLoginRank(start, end, limit)); + } + + @GetMapping("/active-users") + @Operation(summary = "活跃用户列表(按时间范围)") + public R> getActiveUsers( + @RequestParam(value = "start", required = false) LocalDateTime start, + @RequestParam(value = "end", required = false) LocalDateTime end, + @RequestParam(value = "rangeDays", required = false) Integer rangeDays, + @RequestParam(value = "limit", required = false) Integer limit + ) { + LocalDateTime now = LocalDateTime.now(); + if (start == null || end == null) { + int days = rangeDays != null ? rangeDays : 30; + start = now.minusDays(days).with(LocalTime.MIN); + end = now.with(LocalTime.MAX); + } + if (limit == null) limit = 200; + return R.success(adminStatsService.getActiveUsers(start, end, limit)); + } } diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/ActiveUserResp.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/ActiveUserResp.java new file mode 100644 index 00000000..dddf2240 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/ActiveUserResp.java @@ -0,0 +1,25 @@ +package com.luohuo.flex.im.domain.vo.resp.admin; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "活跃用户") +public class ActiveUserResp { + private String username; + private String nickName; + private String avatar; + private LocalDateTime lastOptTime; + private String ip; + private String location; + private String isp; + private Integer loginTimes; +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/AdminStatsResp.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/AdminStatsResp.java index ae19a2d3..08ecbed9 100644 --- a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/AdminStatsResp.java +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/AdminStatsResp.java @@ -35,6 +35,9 @@ public class AdminStatsResp { @Schema(description = "AI 统计") private AiStats aiStats; + @Schema(description = "最近一个月登录≥3次的用户总数") + private Integer monthlyLogin3PlusUserCount; + @Data @Builder @AllArgsConstructor diff --git a/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/LoginRankResp.java b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/LoginRankResp.java new file mode 100644 index 00000000..fa18f0d3 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-entity/src/main/java/com/luohuo/flex/im/domain/vo/resp/admin/LoginRankResp.java @@ -0,0 +1,27 @@ +package com.luohuo.flex.im.domain.vo.resp.admin; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "用户登录次数排行榜项") +public class LoginRankResp { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "昵称") + private String nickName; + + @Schema(description = "登录总次数") + private Long total; +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-im/luohuo-im-facade/luohuo-im-cloud-impl/src/main/java/com/luohuo/flex/im/api/BaseLoginLogApi.java b/luohuo-cloud/luohuo-im/luohuo-im-facade/luohuo-im-cloud-impl/src/main/java/com/luohuo/flex/im/api/BaseLoginLogApi.java new file mode 100644 index 00000000..b08c39d1 --- /dev/null +++ b/luohuo-cloud/luohuo-im/luohuo-im-facade/luohuo-im-cloud-impl/src/main/java/com/luohuo/flex/im/api/BaseLoginLogApi.java @@ -0,0 +1,31 @@ +package com.luohuo.flex.im.api; + +import com.luohuo.basic.base.R; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.time.LocalDateTime; +import java.util.List; + +@FeignClient(name = "${luohuo.feign.base-server:luohuo-base-server}", path = "/baseLoginLog") +public interface BaseLoginLogApi { + + @GetMapping("/stats/login-rank") + @Operation(summary = "登录次数排行榜(status=01)") + R> getLoginRank(@RequestParam("start") LocalDateTime start, + @RequestParam("end") LocalDateTime end, + @RequestParam("limit") Integer limit); + + @GetMapping("/stats/user-count") + @Operation(summary = "登录次数达到阈值的用户数量(status=01)") + R countUsersWithMinLogins(@RequestParam("start") LocalDateTime start, + @RequestParam("end") LocalDateTime end, + @RequestParam("minTimes") Integer minTimes); + + class LoginRankDTO { + public Long uid; + public Long total; + } +} \ No newline at end of file diff --git a/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/biz/ResourceBiz.java b/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/biz/ResourceBiz.java index 9fb8260d..3d5c7117 100644 --- a/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/biz/ResourceBiz.java +++ b/luohuo-cloud/luohuo-oauth/luohuo-oauth-biz/src/main/java/com/luohuo/flex/oauth/biz/ResourceBiz.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import com.luohuo.basic.database.mybatis.conditions.Wraps; import com.luohuo.basic.jackson.JsonUtil; +import com.fasterxml.jackson.databind.ObjectMapper; import com.luohuo.basic.utils.BeanPlusUtil; import com.luohuo.basic.utils.CollHelper; import com.luohuo.basic.utils.StrPool; @@ -41,6 +42,7 @@ public class ResourceBiz { private final DefResourceService defResourceService; private final DefApplicationService defApplicationService; private final BaseRoleService baseRoleService; + private final ObjectMapper objectMapper; /** * 是否所有的子都是视图 @@ -205,7 +207,11 @@ public class ResourceBiz { log.debug("level={}, label={}", level, item.getName()); RouterMeta meta = null; if (StrUtil.isNotEmpty(item.getMetaJson()) && !StrPool.BRACE.equals(item.getMetaJson())) { - meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + try { + meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + } catch (Exception ignored) { + try { meta = objectMapper.readValue(item.getMetaJson(), RouterMeta.class); } catch (Exception ignored2) {} + } } if (meta == null && item.getMeta() != null) { meta = item.getMeta(); @@ -255,7 +261,11 @@ public class ResourceBiz { log.debug("level={}, label={}", level, item.getName()); RouterMeta meta = null; if (StrUtil.isNotEmpty(item.getMetaJson()) && !StrPool.BRACE.equals(item.getMetaJson())) { - meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + try { + meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + } catch (Exception ignored) { + try { meta = objectMapper.readValue(item.getMetaJson(), RouterMeta.class); } catch (Exception ignored2) {} + } } if (meta == null && item.getMeta() != null) { meta = item.getMeta(); @@ -336,7 +346,11 @@ public class ResourceBiz { RouterMeta meta = null; if (item.getMeta() == null) { if (StrUtil.isNotEmpty(item.getMetaJson()) && !StrPool.BRACE.equals(item.getMetaJson())) { - meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + try { + meta = JsonUtil.parse(item.getMetaJson(), RouterMeta.class); + } catch (Exception ignored) { + try { meta = objectMapper.readValue(item.getMetaJson(), RouterMeta.class); } catch (Exception ignored2) {} + } } if (meta == null && item.getMeta() != null) { meta = item.getMeta(); diff --git a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java index 11a548ed..8b166cfa 100644 --- a/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java +++ b/luohuo-cloud/luohuo-ws/luohuo-ws-biz/src/main/java/com/luohuo/flex/ws/websocket/processor/HeartbeatProcessor.java @@ -21,6 +21,6 @@ public class HeartbeatProcessor implements MessageProcessor { @Override public void process(WebSocketSession session, Long uid, WSBaseReq req) { - log.info("收到用户 {} 的心跳", uid); +// log.info("收到用户 {} 的心跳", uid); } } \ No newline at end of file