Commit 4e8e491035180f3c86e4a9f0f6233d6bd43dbe08

Authored by YunaiV
2 parents 98e62211 a6e5b288

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/bpm

# Conflicts:
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
Showing 283 changed files with 5197 additions and 2493 deletions

Too many changes.

To preserve performance only 100 of 283 files are displayed.

sql/mysql/ruoyi-vue-pro.sql
... ... @@ -11,7 +11,7 @@
11 11 Target Server Version : 80200 (8.2.0)
12 12 File Encoding : 65001
13 13  
14   - Date: 28/07/2024 23:20:28
  14 + Date: 31/08/2024 09:22:45
15 15 */
16 16  
17 17 SET NAMES utf8mb4;
... ... @@ -62,7 +62,7 @@ COMMIT;
62 62 -- ----------------------------
63 63 DROP TABLE IF EXISTS `infra_api_error_log`;
64 64 CREATE TABLE `infra_api_error_log` (
65   - `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  65 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
66 66 `trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '链路追踪编号',
67 67 `user_id` int NOT NULL DEFAULT 0 COMMENT '用户编号',
68 68 `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型',
... ... @@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log` (
91 91 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
92 92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
93 93 PRIMARY KEY (`id`) USING BTREE
94   -) ENGINE = InnoDB AUTO_INCREMENT = 19166 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
  94 +) ENGINE = InnoDB AUTO_INCREMENT = 20014 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
95 95  
96 96 -- ----------------------------
97 97 -- Records of infra_api_error_log
... ... @@ -128,7 +128,7 @@ CREATE TABLE `infra_codegen_column` (
128 128 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
129 129 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
130 130 PRIMARY KEY (`id`) USING BTREE
131   -) ENGINE = InnoDB AUTO_INCREMENT = 2470 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
  131 +) ENGINE = InnoDB AUTO_INCREMENT = 2483 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
132 132  
133 133 -- ----------------------------
134 134 -- Records of infra_codegen_column
... ... @@ -166,7 +166,7 @@ CREATE TABLE `infra_codegen_table` (
166 166 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
167 167 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
168 168 PRIMARY KEY (`id`) USING BTREE
169   -) ENGINE = InnoDB AUTO_INCREMENT = 186 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
  169 +) ENGINE = InnoDB AUTO_INCREMENT = 187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
170 170  
171 171 -- ----------------------------
172 172 -- Records of infra_codegen_table
... ... @@ -179,7 +179,7 @@ COMMIT;
179 179 -- ----------------------------
180 180 DROP TABLE IF EXISTS `infra_config`;
181 181 CREATE TABLE `infra_config` (
182   - `id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键',
  182 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '参数主键',
183 183 `category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数分组',
184 184 `type` tinyint NOT NULL COMMENT '参数类型',
185 185 `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数名称',
... ... @@ -250,7 +250,7 @@ CREATE TABLE `infra_file` (
250 250 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
251 251 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
252 252 PRIMARY KEY (`id`) USING BTREE
253   -) ENGINE = InnoDB AUTO_INCREMENT = 1447 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
  253 +) ENGINE = InnoDB AUTO_INCREMENT = 1472 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
254 254  
255 255 -- ----------------------------
256 256 -- Records of infra_file
... ... @@ -438,7 +438,7 @@ CREATE TABLE `system_dict_data` (
438 438 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
439 439 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
440 440 PRIMARY KEY (`id`) USING BTREE
441   -) ENGINE = InnoDB AUTO_INCREMENT = 1588 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
  441 +) ENGINE = InnoDB AUTO_INCREMENT = 1592 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
442 442  
443 443 -- ----------------------------
444 444 -- Records of system_dict_data
... ... @@ -858,6 +858,10 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
858 858 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1585, 2, '回复', '2', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:06', '1', '2024-07-10 21:26:06', b'0');
859 859 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1586, 2, '腾讯云', 'TENCENT', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:16', '1', '2024-07-22 22:23:16', b'0');
860 860 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1587, 3, '华为云', 'HUAWEI', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:46', '1', '2024-07-22 22:23:53', b'0');
  861 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1588, 1, 'OpenAI 微软', 'AzureOpenAI', 'ai_platform', 0, '', '', '', '1', '2024-08-10 14:07:41', '1', '2024-08-10 14:07:41', b'0');
  862 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1589, 10, 'BPMN 设计器', '10', 'bpm_model_type', 0, 'primary', '', '', '1', '2024-08-26 15:22:17', '1', '2024-08-26 16:46:02', b'0');
  863 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1590, 20, 'SIMPLE 设计器', '20', 'bpm_model_type', 0, 'success', '', '', '1', '2024-08-26 15:22:27', '1', '2024-08-26 16:45:58', b'0');
  864 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1591, 4, '七牛云', 'QINIU', 'system_sms_channel_code', 0, '', '', '', '1', '2024-08-31 08:45:03', '1', '2024-08-31 08:45:24', b'0');
861 865 COMMIT;
862 866  
863 867 -- ----------------------------
... ... @@ -877,7 +881,7 @@ CREATE TABLE `system_dict_type` (
877 881 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
878 882 `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
879 883 PRIMARY KEY (`id`) USING BTREE
880   -) ENGINE = InnoDB AUTO_INCREMENT = 629 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
  884 +) ENGINE = InnoDB AUTO_INCREMENT = 630 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
881 885  
882 886 -- ----------------------------
883 887 -- Records of system_dict_type
... ... @@ -975,6 +979,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
975 979 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (626, '写作长度', 'ai_write_length', 0, '', '1', '2024-07-07 15:18:41', '1', '2024-07-07 15:18:41', b'0', '1970-01-01 00:00:00');
976 980 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (627, '写作格式', 'ai_write_format', 0, '', '1', '2024-07-07 15:14:34', '1', '2024-07-07 15:14:34', b'0', '1970-01-01 00:00:00');
977 981 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', b'0', '1970-01-01 00:00:00');
  982 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', b'0', '1970-01-01 00:00:00');
978 983 COMMIT;
979 984  
980 985 -- ----------------------------
... ... @@ -998,7 +1003,7 @@ CREATE TABLE `system_login_log` (
998 1003 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
999 1004 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
1000 1005 PRIMARY KEY (`id`) USING BTREE
1001   -) ENGINE = InnoDB AUTO_INCREMENT = 3261 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
  1006 +) ENGINE = InnoDB AUTO_INCREMENT = 3289 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1002 1007  
1003 1008 -- ----------------------------
1004 1009 -- Records of system_login_log
... ... @@ -1129,7 +1134,7 @@ CREATE TABLE `system_menu` (
1129 1134 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1130 1135 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1131 1136 PRIMARY KEY (`id`) USING BTREE
1132   -) ENGINE = InnoDB AUTO_INCREMENT = 2798 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
  1137 +) ENGINE = InnoDB AUTO_INCREMENT = 2808 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
1133 1138  
1134 1139 -- ----------------------------
1135 1140 -- Records of system_menu
... ... @@ -1293,7 +1298,6 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1293 1298 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1193, '流程模型', '', 2, 1, 1186, 'model', 'fa-solid:project-diagram', 'bpm/model/index', 'BpmModel', 0, b'1', b'1', b'1', '1', '2021-12-31 23:24:58', '1', '2024-03-19 12:25:19', b'0');
1294 1299 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1194, '模型查询', 'bpm:model:query', 3, 1, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:01:10', '1', '2022-04-20 17:03:10', b'0');
1295 1300 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1195, '模型创建', 'bpm:model:create', 3, 2, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:01:24', '1', '2022-04-20 17:03:10', b'0');
1296   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1196, '模型导入', 'bpm:model:import', 3, 3, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:01:35', '1', '2022-04-20 17:03:10', b'0');
1297 1301 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1197, '模型更新', 'bpm:model:update', 3, 4, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:02:28', '1', '2022-04-20 17:03:10', b'0');
1298 1302 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1198, '模型删除', 'bpm:model:delete', 3, 5, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:02:43', '1', '2022-04-20 17:03:10', b'0');
1299 1303 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1199, '模型发布', 'bpm:model:deploy', 3, 6, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:03:24', '1', '2022-04-20 17:03:10', b'0');
... ... @@ -1952,7 +1956,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1952 1956 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2784, '绘画管理', '', 2, 11, 2760, 'image', 'fa:file-image-o', 'ai/image/manager/index.vue', 'AiImageManager', 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', '1', '2024-06-26 21:37:13', b'0');
1953 1957 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2785, '绘画查询', 'ai:image:query', 3, 1, 2784, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:21:57', b'0');
1954 1958 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2786, '绘画删除', 'ai:image:delete', 3, 4, 2784, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:22:08', b'0');
1955   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2787, '会话更新公开状态', 'ai:image:update-public-status', 3, 2, 2784, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-06-26 22:47:56', '1', '2024-06-26 22:47:56', b'0');
  1959 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2787, '绘图更新', 'ai:image:update', 3, 2, 2784, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-06-26 22:47:56', '1', '2024-08-31 09:21:35', b'0');
1956 1960 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2788, '音乐管理', '', 2, 12, 2760, 'music', 'fa:music', 'ai/music/manager/index.vue', 'AiMusicManager', 0, b'1', b'1', b'1', '', '2024-06-27 15:03:33', '1', '2024-06-27 23:04:19', b'0');
1957 1961 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2789, '音乐查询', 'ai:music:query', 3, 1, 2788, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', b'0');
1958 1962 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2790, '音乐更新', 'ai:music:update', 3, 3, 2788, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', b'0');
... ... @@ -1961,8 +1965,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1961 1965 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2793, '写作管理', '', 2, 13, 2760, 'write', 'fa:bookmark-o', 'ai/write/manager/index.vue', 'AiWriteManager', 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '1', '2024-07-10 21:31:59', b'0');
1962 1966 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2794, 'AI 写作查询', 'ai:write:query', 3, 1, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0');
1963 1967 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0');
1964   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiWrite', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-17 09:36:12', b'0');
  1968 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', b'0');
1965 1969 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0');
  1970 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2798, 'AI 思维导图', '', 2, 5, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2024-07-29 21:33:20', b'0');
  1971 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', b'0');
  1972 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
  1973 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
  1974 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2802, '会话查询', 'promotion:kefu-conversation:query', 3, 1, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:17:52', '1', '2024-08-31 09:18:52', b'0');
  1975 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2803, '会话更新', 'promotion:kefu-conversation:update', 3, 2, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:18:15', '1', '2024-08-31 09:19:29', b'0');
  1976 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2804, '消息查询', 'promotion:kefu-message:query', 3, 10, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:18:42', '1', '2024-08-31 09:18:42', b'0');
  1977 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2805, '会话删除', 'promotion:kefu-conversation:delete', 3, 3, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:19:51', '1', '2024-08-31 09:20:32', b'0');
  1978 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2806, '消息发送', 'promotion:kefu-message:send', 3, 12, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:20:06', '1', '2024-08-31 09:20:06', b'0');
  1979 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2807, '消息更新', 'promotion:kefu-message:update', 3, 11, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-08-31 09:20:22', '1', '2024-08-31 09:20:22', b'0');
1966 1980 COMMIT;
1967 1981  
1968 1982 -- ----------------------------
... ... @@ -2084,7 +2098,7 @@ CREATE TABLE `system_oauth2_access_token` (
2084 2098 PRIMARY KEY (`id`) USING BTREE,
2085 2099 INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
2086 2100 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
2087   -) ENGINE = InnoDB AUTO_INCREMENT = 8784 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
  2101 +) ENGINE = InnoDB AUTO_INCREMENT = 9563 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
2088 2102  
2089 2103 -- ----------------------------
2090 2104 -- Records of system_oauth2_access_token
... ... @@ -2206,7 +2220,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
2206 2220 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2207 2221 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2208 2222 PRIMARY KEY (`id`) USING BTREE
2209   -) ENGINE = InnoDB AUTO_INCREMENT = 1598 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
  2223 +) ENGINE = InnoDB AUTO_INCREMENT = 1620 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
2210 2224  
2211 2225 -- ----------------------------
2212 2226 -- Records of system_oauth2_refresh_token
... ... @@ -2239,7 +2253,7 @@ CREATE TABLE `system_operate_log` (
2239 2253 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2240 2254 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2241 2255 PRIMARY KEY (`id`) USING BTREE
2242   -) ENGINE = InnoDB AUTO_INCREMENT = 9053 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
  2256 +) ENGINE = InnoDB AUTO_INCREMENT = 9056 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
2243 2257  
2244 2258 -- ----------------------------
2245 2259 -- Records of system_operate_log
... ... @@ -2298,7 +2312,7 @@ CREATE TABLE `system_role` (
2298 2312 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2299 2313 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2300 2314 PRIMARY KEY (`id`) USING BTREE
2301   -) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表';
  2315 +) ENGINE = InnoDB AUTO_INCREMENT = 154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表';
2302 2316  
2303 2317 -- ----------------------------
2304 2318 -- Records of system_role
... ... @@ -2307,9 +2321,10 @@ BEGIN;
2307 2321 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:21', b'0', 1);
2308 2322 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', b'0', 1);
2309 2323 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', '2024-02-24 02:51:32', b'0', 1);
2310   -INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '测试账号', 'test', 0, 2, '[105,106,107]', 0, 2, '', '', '2021-01-06 13:49:35', '1', '2024-07-27 23:30:47', b'0', 1);
  2324 +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '', '', '2021-01-06 13:49:35', '1', '2024-08-11 10:41:10', b'0', 1);
2311 2325 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121);
2312 2326 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
  2327 +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (153, '某角色', 'tt', 4, 1, '', 0, 2, '', '1', '2024-08-17 14:09:35', '1', '2024-08-17 14:09:35', b'0', 1);
2313 2328 COMMIT;
2314 2329  
2315 2330 -- ----------------------------
... ... @@ -2390,7 +2405,6 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
2390 2405 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1623, 101, 1193, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2391 2406 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1624, 101, 1194, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2392 2407 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1625, 101, 1195, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2393   -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1626, 101, 1196, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2394 2408 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1627, 101, 1197, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2395 2409 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1628, 101, 1198, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
2396 2410 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1629, 101, 1199, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1);
... ... @@ -3195,9 +3209,8 @@ CREATE TABLE `system_sms_channel` (
3195 3209 -- Records of system_sms_channel
3196 3210 -- ----------------------------
3197 3211 BEGIN;
3198   -INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'Ballcat', 'ALIYUN', 0, '你要改哦,只有我可以用!!!!', 'LTAI5tCnKso2uG3kJ5gRav88', 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2023-12-02 22:10:17', b'0');
  3212 +INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'Ballcat', 'ALIYUN', 0, '你要改哦,只有我可以用!!!!', 'LTAI5tCnKso2uG3kJ5gRav88', 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2024-08-04 08:53:26', b'0');
3199 3213 INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '测试渠道', 'DEBUG_DING_TALK', 0, '123', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2021-04-13 00:23:14', '1', '2022-03-27 20:29:49', b'0');
3200   -INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, '测试演示', 'DEBUG_DING_TALK', 0, '仅测试', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2022-04-10 23:07:59', '1', '2023-12-02 22:10:08', b'0');
3201 3214 COMMIT;
3202 3215  
3203 3216 -- ----------------------------
... ... @@ -3222,7 +3235,7 @@ CREATE TABLE `system_sms_code` (
3222 3235 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3223 3236 PRIMARY KEY (`id`) USING BTREE,
3224 3237 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
3225   -) ENGINE = InnoDB AUTO_INCREMENT = 628 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
  3238 +) ENGINE = InnoDB AUTO_INCREMENT = 632 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
3226 3239  
3227 3240 -- ----------------------------
3228 3241 -- Records of system_sms_code
... ... @@ -3263,7 +3276,7 @@ CREATE TABLE `system_sms_log` (
3263 3276 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
3264 3277 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3265 3278 PRIMARY KEY (`id`) USING BTREE
3266   -) ENGINE = InnoDB AUTO_INCREMENT = 987 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
  3279 +) ENGINE = InnoDB AUTO_INCREMENT = 1088 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
3267 3280  
3268 3281 -- ----------------------------
3269 3282 -- Records of system_sms_log
... ... @@ -3293,24 +3306,25 @@ CREATE TABLE `system_sms_template` (
3293 3306 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
3294 3307 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3295 3308 PRIMARY KEY (`id`) USING BTREE
3296   -) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
  3309 +) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
3297 3310  
3298 3311 -- ----------------------------
3299 3312 -- Records of system_sms_template
3300 3313 -- ----------------------------
3301 3314 BEGIN;
3302   -INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 1, 0, 'test_01', '测试验证码短信', '正在进行登录操作{operation},您的验证码是{code}', '[\"operation\",\"code\"]', '测试备注', '4383920', 6, 'DEBUG_DING_TALK', '', '2021-03-31 10:49:38', '1', '2023-12-02 22:32:47', b'0');
  3315 +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 1, 0, 'test_01', '测试验证码短信', '正在进行登录操作{operation},您的验证码是{code}', '[\"operation\",\"code\"]', '测试备注', '4383920', 4, 'DEBUG_DING_TALK', '', '2021-03-31 10:49:38', '1', '2024-08-18 11:57:18', b'0');
3303 3316 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, 1, 0, 'test_02', '公告通知', '您的验证码{code},该验证码5分钟内有效,请勿泄漏于他人!', '[\"code\"]', NULL, 'SMS_207945135', 2, 'ALIYUN', '', '2021-03-31 11:56:30', '1', '2021-04-10 01:22:02', b'0');
3304   -INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, 3, 0, 'test-01', '测试模板', '哈哈哈 {name}', '[\"name\"]', 'f哈哈哈', '4383920', 6, 'DEBUG_DING_TALK', '1', '2021-04-10 01:07:21', '1', '2022-12-10 21:26:09', b'0');
  3317 +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (6, 3, 0, 'test-01', '测试模板', '哈哈哈 {name}', '[\"name\"]', 'f哈哈哈', '4383920', 4, 'DEBUG_DING_TALK', '1', '2021-04-10 01:07:21', '1', '2024-08-18 11:57:07', b'0');
3305 3318 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (7, 3, 0, 'test-04', '测试下', '老鸡{name},牛逼{code}', '[\"name\",\"code\"]', '哈哈哈哈', 'suibian', 4, 'DEBUG_DING_TALK', '1', '2021-04-13 00:29:53', '1', '2023-12-02 22:35:34', b'0');
3306   -INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8, 1, 0, 'user-sms-login', '前台用户短信登录', '您的验证码是{code}', '[\"code\"]', NULL, '4372216', 6, 'DEBUG_DING_TALK', '1', '2021-10-11 08:10:00', '1', '2022-12-10 21:25:59', b'0');
  3319 +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8, 1, 0, 'user-sms-login', '前台用户短信登录', '您的验证码是{code}', '[\"code\"]', NULL, '4372216', 4, 'DEBUG_DING_TALK', '1', '2021-10-11 08:10:00', '1', '2024-08-18 11:57:06', b'0');
3307 3320 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (9, 2, 0, 'bpm_task_assigned', '【工作流】任务被分配', '您收到了一条新的待办任务:{processInstanceName}-{taskName},申请人:{startUserNickname},处理链接:{detailUrl}', '[\"processInstanceName\",\"taskName\",\"startUserNickname\",\"detailUrl\"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-21 22:31:19', '1', '2022-01-22 00:03:36', b'0');
3308 3321 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (10, 2, 0, 'bpm_process_instance_reject', '【工作流】流程被不通过', '您的流程被审批不通过:{processInstanceName},原因:{reason},查看链接:{detailUrl}', '[\"processInstanceName\",\"reason\",\"detailUrl\"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-22 00:03:31', '1', '2022-05-01 12:33:14', b'0');
3309 3322 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (11, 2, 0, 'bpm_process_instance_approve', '【工作流】流程被通过', '您的流程被审批通过:{processInstanceName},查看链接:{detailUrl}', '[\"processInstanceName\",\"detailUrl\"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-22 00:04:31', '1', '2022-03-27 20:32:21', b'0');
3310   -INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (12, 2, 0, 'demo', '演示模板', '我就是测试一下下', '[]', NULL, 'biubiubiu', 6, 'DEBUG_DING_TALK', '1', '2022-04-10 23:22:49', '1', '2023-03-24 23:45:07', b'0');
  3323 +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (12, 2, 0, 'demo', '演示模板', '我就是测试一下下', '[]', NULL, 'biubiubiu', 4, 'DEBUG_DING_TALK', '1', '2022-04-10 23:22:49', '1', '2024-08-18 11:57:04', b'0');
3311 3324 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (14, 1, 0, 'user-update-mobile', '会员用户 - 修改手机', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:04', b'0');
3312 3325 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (15, 1, 0, 'user-update-password', '会员用户 - 修改密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:18', b'0');
3313 3326 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (16, 1, 0, 'user-reset-password', '会员用户 - 重置密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-12-02 22:35:27', b'0');
  3327 +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (17, 2, 0, 'bpm_task_timeout', '【工作流】任务审批超时', '您收到了一条超时的待办任务:{processInstanceName}-{taskName},处理链接:{detailUrl}', '[\"processInstanceName\",\"taskName\",\"detailUrl\"]', '', 'X', 4, 'DEBUG_DING_TALK', '1', '2024-08-16 21:59:15', '1', '2024-08-16 21:59:34', b'0');
3314 3328 COMMIT;
3315 3329  
3316 3330 -- ----------------------------
... ... @@ -3367,7 +3381,7 @@ CREATE TABLE `system_social_user` (
3367 3381 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3368 3382 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3369 3383 PRIMARY KEY (`id`) USING BTREE
3370   -) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
  3384 +) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
3371 3385  
3372 3386 -- ----------------------------
3373 3387 -- Records of system_social_user
... ... @@ -3392,7 +3406,7 @@ CREATE TABLE `system_social_user_bind` (
3392 3406 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3393 3407 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3394 3408 PRIMARY KEY (`id`) USING BTREE
3395   -) ENGINE = InnoDB AUTO_INCREMENT = 119 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
  3409 +) ENGINE = InnoDB AUTO_INCREMENT = 120 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
3396 3410  
3397 3411 -- ----------------------------
3398 3412 -- Records of system_social_user_bind
... ... @@ -3559,10 +3573,10 @@ CREATE TABLE `system_users` (
3559 3573 -- Records of system_users
3560 3574 -- ----------------------------
3561 3575 BEGIN;
3562   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-07-28 11:35:00', 'admin', '2021-01-05 17:03:47', NULL, '2024-07-28 11:35:00', b'0', 1);
3563   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
3564   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-03-18 21:09:04', '', '2021-01-13 23:50:35', NULL, '2024-03-18 21:09:04', b'0', 1);
3565   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-07-13 23:13:16', '', '2021-01-21 02:13:53', NULL, '2024-07-13 23:13:16', b'0', 1);
  3576 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-08-26 16:54:00', 'admin', '2021-01-05 17:03:47', NULL, '2024-08-26 16:54:00', b'0', 1);
  3577 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 0, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', '1', '2024-08-17 11:06:13', b'0', 1);
  3578 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1);
  3579 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 09:38:08', '', '2021-01-21 02:13:53', NULL, '2024-08-11 09:38:08', b'0', 1);
3566 3580 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118);
3567 3581 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
3568 3582 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
... ... @@ -3572,7 +3586,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
3572 3586 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', NULL, '2022-03-19 18:38:51', b'0', 122);
3573 3587 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-24 22:21:05', '1', '2022-03-19 21:50:58', NULL, '2024-03-24 22:21:05', b'0', 1);
3574 3588 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', '7648@qq.com', '15601691229', 2, '', 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2024-04-04 09:37:14', b'0', 1);
3575   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$10$WI8Gg/lpZQIrOEZMHqka7OdFaD4Nx.B/qY8ZGTTUKrOJwaHFqibaC', '测试号', '1111', 100, '[2]', '', '15601691234', 1, '', 0, '', NULL, '1', '2022-07-09 17:40:26', '1', '2022-07-09 17:40:26', b'0', 1);
  3589 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$10$WI8Gg/lpZQIrOEZMHqka7OdFaD4Nx.B/qY8ZGTTUKrOJwaHFqibaC', '测试号02', '1111', 100, '[2]', '', '15601691234', 1, '', 0, '', NULL, '1', '2022-07-09 17:40:26', '1', '2024-08-11 10:12:03', b'0', 1);
3576 3590 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (118, 'goudan', '$2a$04$OB1SuphCdiLVRpiYRKeqH.8NYS7UIp5vmIv1W7U4w6toiFeOAATVK', '狗蛋', NULL, 103, '[1]', '', '15601691239', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-17 09:10:27', '1', '2022-07-09 17:44:43', '1', '2024-04-04 09:48:05', b'0', 1);
3577 3591 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (131, 'hh', '$2a$04$jyH9h6.gaw8mpOjPfHIpx.8as2Rzfcmdlj5rlJFwgCw4rsv/MTb2K', '呵呵', NULL, 100, '[]', '777@qq.com', '15601882312', 1, '', 0, '', NULL, '1', '2024-04-27 08:45:56', '1', '2024-04-27 08:45:56', b'0', 1);
3578 3592 COMMIT;
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
... ... @@ -111,7 +111,7 @@ public class HttpUtils {
111 111 authorization = Base64.decodeStr(authorization);
112 112 clientId = StrUtil.subBefore(authorization, ":", false);
113 113 clientSecret = StrUtil.subAfter(authorization, ":", false);
114   - // 再从 Param 中获取
  114 + // 再从 Param 中获取
115 115 } else {
116 116 clientId = request.getParameter("client_id");
117 117 clientSecret = request.getParameter("client_secret");
... ... @@ -143,4 +143,21 @@ public class HttpUtils {
143 143 }
144 144 }
145 145  
  146 + /**
  147 + * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
  148 + *
  149 + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
  150 + *
  151 + * @param url URL
  152 + * @param headers 请求头
  153 + * @return 请求结果
  154 + */
  155 + public static String get(String url, Map<String, String> headers) {
  156 + try (HttpResponse response = HttpRequest.get(url)
  157 + .addHeaders(headers)
  158 + .execute()) {
  159 + return response.body();
  160 + }
  161 + }
  162 +
146 163 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.tenant.core.job;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.exceptions.ExceptionUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 7 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
7 8 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
... ... @@ -44,8 +45,10 @@ public class TenantJobAspect {
44 45 // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
45 46 TenantUtils.execute(tenantId, () -> {
46 47 try {
47   - joinPoint.proceed();
  48 + Object result = joinPoint.proceed();
  49 + results.put(tenantId, StrUtil.toStringOrNull(result));
48 50 } catch (Throwable e) {
  51 + log.error("[execute][租户({}) 执行 Job 发生异常", tenantId, e);
49 52 results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
50 53 }
51 54 });
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
... ... @@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
7 7 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
8 8 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
9 9 import com.baomidou.mybatisplus.extension.incrementer.*;
  10 +import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
  11 +import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
10 12 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
11 13 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
12 14 import org.apache.ibatis.annotations.Mapper;
... ... @@ -16,6 +18,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16 18 import org.springframework.context.annotation.Bean;
17 19 import org.springframework.core.env.ConfigurableEnvironment;
18 20  
  21 +import java.util.concurrent.TimeUnit;
  22 +
19 23 /**
20 24 * MyBaits 配置类
21 25 *
... ... @@ -26,6 +30,14 @@ import org.springframework.core.env.ConfigurableEnvironment;
26 30 lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
27 31 public class YudaoMybatisAutoConfiguration {
28 32  
  33 + static {
  34 + // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存
  35 + JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(
  36 + (cache) -> cache.maximumSize(1024)
  37 + .expireAfterWrite(5, TimeUnit.SECONDS))
  38 + );
  39 + }
  40 +
29 41 @Bean
30 42 public MybatisPlusInterceptor mybatisPlusInterceptor() {
31 43 MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
... ... @@ -34,7 +46,7 @@ public class YudaoMybatisAutoConfiguration {
34 46 }
35 47  
36 48 @Bean
37   - public MetaObjectHandler defaultMetaObjectHandler(){
  49 + public MetaObjectHandler defaultMetaObjectHandler() {
38 50 return new DefaultDBFieldHandler(); // 自动填充参数类
39 51 }
40 52  
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java
... ... @@ -69,7 +69,7 @@ public class ApiSignatureAspect {
69 69  
70 70 // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
71 71 String nonce = request.getHeader(signature.nonce());
72   - signatureRedisDAO.setNonce(nonce, signature.timeout() * 2, signature.timeUnit());
  72 + signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
73 73 return true;
74 74 }
75 75  
... ... @@ -113,7 +113,7 @@ public class ApiSignatureAspect {
113 113 }
114 114  
115 115 // 3. 检查 nonce 是否存在,有且仅能使用一次
116   - return signatureRedisDAO.getNonce(nonce) == null;
  116 + return signatureRedisDAO.getNonce(appId, nonce) == null;
117 117 }
118 118  
119 119 /**
... ... @@ -165,5 +165,4 @@ public class ApiSignatureAspect {
165 165 return sortedMap;
166 166 }
167 167  
168   -}
169   -
  168 +}
170 169 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
... ... @@ -22,7 +22,7 @@ public class ApiSignatureRedisDAO {
22 22 * VALUE 格式:String
23 23 * 过期时间:不固定
24 24 */
25   - private static final String SIGNATURE_NONCE = "api_signature_nonce:%s";
  25 + private static final String SIGNATURE_NONCE = "api_signature_nonce:%s:%s";
26 26  
27 27 /**
28 28 * 签名密钥
... ... @@ -36,16 +36,16 @@ public class ApiSignatureRedisDAO {
36 36  
37 37 // ========== 验签随机数 ==========
38 38  
39   - public String getNonce(String nonce) {
40   - return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
  39 + public String getNonce(String appId, String nonce) {
  40 + return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
41 41 }
42 42  
43   - public void setNonce(String nonce, int time, TimeUnit timeUnit) {
44   - stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit);
  43 + public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
  44 + stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit);
45 45 }
46 46  
47   - private static String formatNonceKey(String key) {
48   - return String.format(SIGNATURE_NONCE, key);
  47 + private static String formatNonceKey(String appId, String nonce) {
  48 + return String.format(SIGNATURE_NONCE, appId, nonce);
49 49 }
50 50  
51 51 // ========== 签名密钥 ==========
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java
... ... @@ -69,7 +69,7 @@ public class ApiSignatureTest {
69 69 // 断言结果
70 70 assertTrue(result);
71 71 // 断言调用
72   - verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS));
  72 + verify(signatureRedisDAO).setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS));
73 73 }
74 74  
75 75 -}
  76 +}
76 77 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/LogRecordServiceImpl.java
... ... @@ -29,18 +29,22 @@ public class LogRecordServiceImpl implements ILogRecordService {
29 29  
30 30 @Override
31 31 public void record(LogRecord logRecord) {
32   - // 1. 补全通用字段
33 32 OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
34   - reqDTO.setTraceId(TracerUtils.getTraceId());
35   - // 补充用户信息
36   - fillUserFields(reqDTO);
37   - // 补全模块信息
38   - fillModuleFields(reqDTO, logRecord);
39   - // 补全请求信息
40   - fillRequestFields(reqDTO);
  33 + try {
  34 + reqDTO.setTraceId(TracerUtils.getTraceId());
  35 + // 补充用户信息
  36 + fillUserFields(reqDTO);
  37 + // 补全模块信息
  38 + fillModuleFields(reqDTO, logRecord);
  39 + // 补全请求信息
  40 + fillRequestFields(reqDTO);
41 41  
42   - // 2. 异步记录日志
43   - operateLogApi.createOperateLog(reqDTO);
  42 + // 2. 异步记录日志
  43 + operateLogApi.createOperateLogAsync(reqDTO);
  44 + } catch (Throwable ex) {
  45 + // 由于 @Async 异步调用,这里打印下日志,更容易跟进
  46 + log.error("[record][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
  47 + }
44 48 }
45 49  
46 50 private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java
... ... @@ -2,15 +2,10 @@ package cn.iocoder.yudao.framework.apilog.config;
2 2  
3 3 import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter;
4 4 import cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
5   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
6   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
7   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
8   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkServiceImpl;
9 5 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
10 6 import cn.iocoder.yudao.framework.web.config.WebProperties;
11 7 import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
12 8 import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
13   -import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
14 9 import jakarta.servlet.Filter;
15 10 import org.springframework.beans.factory.annotation.Value;
16 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
... ... @@ -23,18 +18,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
23 18 @AutoConfiguration(after = YudaoWebAutoConfiguration.class)
24 19 public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
25 20  
26   - @Bean
27   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
28   - public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
29   - return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
30   - }
31   -
32   - @Bean
33   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
34   - public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
35   - return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
36   - }
37   -
38 21 /**
39 22 * 创建 ApiAccessLogFilter Bean,记录 API 请求日志
40 23 */
... ... @@ -42,8 +25,8 @@ public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
42 25 @ConditionalOnProperty(prefix = "yudao.access-log", value = "enable", matchIfMissing = true) // 允许使用 yudao.access-log.enable=false 禁用访问日志
43 26 public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
44 27 @Value("${spring.application.name}") String applicationName,
45   - ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
46   - ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogFrameworkService);
  28 + ApiAccessLogApi apiAccessLogApi) {
  29 + ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogApi);
47 30 return createFilterBean(filter, WebFilterOrderEnum.API_ACCESS_LOG_FILTER);
48 31 }
49 32  
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java
... ... @@ -9,7 +9,6 @@ import cn.hutool.core.util.BooleanUtil;
9 9 import cn.hutool.core.util.StrUtil;
10 10 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
11 11 import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
12   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
13 12 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
14 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
15 14 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
... ... @@ -18,6 +17,7 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
18 17 import cn.iocoder.yudao.framework.web.config.WebProperties;
19 18 import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
20 19 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  20 +import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
21 21 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
22 22 import com.fasterxml.jackson.databind.JsonNode;
23 23 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -36,7 +36,7 @@ import java.time.temporal.ChronoUnit;
36 36 import java.util.Iterator;
37 37 import java.util.Map;
38 38  
39   -import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;
  39 +import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;
40 40 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
41 41  
42 42 /**
... ... @@ -53,12 +53,12 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
53 53  
54 54 private final String applicationName;
55 55  
56   - private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
  56 + private final ApiAccessLogApi apiAccessLogApi;
57 57  
58   - public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
  58 + public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogApi apiAccessLogApi) {
59 59 super(webProperties);
60 60 this.applicationName = applicationName;
61   - this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;
  61 + this.apiAccessLogApi = apiAccessLogApi;
62 62 }
63 63  
64 64 @Override
... ... @@ -91,7 +91,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
91 91 if (!enable) {
92 92 return;
93 93 }
94   - apiAccessLogFrameworkService.createApiAccessLog(accessLog);
  94 + apiAccessLogApi.createApiAccessLogAsync(accessLog);
95 95 } catch (Throwable th) {
96 96 log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
97 97 }
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
4   -
5   -/**
6   - * API 访问日志 Framework Service 接口
7   - *
8   - * @author 芋道源码
9   - */
10   -public interface ApiAccessLogFrameworkService {
11   -
12   - /**
13   - * 创建 API 访问日志
14   - *
15   - * @param reqDTO API 访问日志
16   - */
17   - void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
18   -
19   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
4   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
5   -import lombok.RequiredArgsConstructor;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.scheduling.annotation.Async;
8   -
9   -/**
10   - * API 访问日志 Framework Service 实现类
11   - *
12   - * 基于 {@link ApiAccessLogApi} 服务,记录访问日志
13   - *
14   - * @author 芋道源码
15   - */
16   -@RequiredArgsConstructor
17   -@Slf4j
18   -public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {
19   -
20   - private final ApiAccessLogApi apiAccessLogApi;
21   -
22   - @Override
23   - @Async
24   - public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
25   - try {
26   - apiAccessLogApi.createApiAccessLog(reqDTO);
27   - } catch (Throwable ex) {
28   - // 由于 @Async 异步调用,这里打印下日志,更容易跟进
29   - log.error("[createApiAccessLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
30   - }
31   - }
32   -
33   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
4   -
5   -/**
6   - * API 错误日志 Framework Service 接口
7   - *
8   - * @author 芋道源码
9   - */
10   -public interface ApiErrorLogFrameworkService {
11   -
12   - /**
13   - * 创建 API 错误日志
14   - *
15   - * @param reqDTO API 错误日志
16   - */
17   - void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
18   -
19   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
4   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
5   -import lombok.RequiredArgsConstructor;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.scheduling.annotation.Async;
8   -
9   -/**
10   - * API 错误日志 Framework Service 实现类
11   - *
12   - * 基于 {@link ApiErrorLogApi} 服务,记录错误日志
13   - *
14   - * @author 芋道源码
15   - */
16   -@RequiredArgsConstructor
17   -@Slf4j
18   -public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {
19   -
20   - private final ApiErrorLogApi apiErrorLogApi;
21   -
22   - @Override
23   - @Async
24   - public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
25   - try {
26   - apiErrorLogApi.createApiErrorLog(reqDTO);
27   - } catch (Throwable ex) {
28   - // 由于 @Async 异步调用,这里打印下日志,更容易跟进
29   - log.error("[createApiErrorLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
30   - }
31   - }
32   -
33   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.web.config;
2 2  
3   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
4 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 4 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
6 5 import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 6 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
8 7 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
9 8 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  9 +import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.servlet.Filter;
10 12 import org.springframework.beans.factory.annotation.Value;
11 13 import org.springframework.boot.autoconfigure.AutoConfiguration;
12 14 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
... ... @@ -25,9 +27,6 @@ import org.springframework.web.filter.CorsFilter;
25 27 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
26 28 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
27 29  
28   -import jakarta.annotation.Resource;
29   -import jakarta.servlet.Filter;
30   -
31 30 @AutoConfiguration
32 31 @EnableConfigurationProperties(WebProperties.class)
33 32 public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
... ... @@ -59,8 +58,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
59 58 }
60 59  
61 60 @Bean
62   - public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogFrameworkService ApiErrorLogFrameworkService) {
63   - return new GlobalExceptionHandler(applicationName, ApiErrorLogFrameworkService);
  61 + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
  62 + public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogApi apiErrorLogApi) {
  63 + return new GlobalExceptionHandler(applicationName, apiErrorLogApi);
64 64 }
65 65  
66 66 @Bean
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -4,7 +4,7 @@ import cn.hutool.core.exceptions.ExceptionUtil;
4 4 import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.core.util.ObjUtil;
6 6 import cn.hutool.core.util.StrUtil;
7   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
  7 +import cn.hutool.extra.servlet.JakartaServletUtil;
8 8 import cn.iocoder.yudao.framework.common.exception.ServiceException;
9 9 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
10 10 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -13,14 +13,16 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
13 13 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
14 14 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
15 15 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  16 +import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
16 17 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
  18 +import com.fasterxml.jackson.databind.exc.InvalidFormatException;
17 19 import jakarta.servlet.http.HttpServletRequest;
18 20 import jakarta.validation.ConstraintViolation;
19 21 import jakarta.validation.ConstraintViolationException;
20 22 import jakarta.validation.ValidationException;
21 23 import lombok.AllArgsConstructor;
22 24 import lombok.extern.slf4j.Slf4j;
23   -import org.apache.commons.lang3.exception.ExceptionUtils;
  25 +import org.springframework.http.converter.HttpMessageNotReadableException;
24 26 import org.springframework.security.access.AccessDeniedException;
25 27 import org.springframework.util.Assert;
26 28 import org.springframework.validation.BindException;
... ... @@ -58,7 +60,7 @@ public class GlobalExceptionHandler {
58 60 @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
59 61 private final String applicationName;
60 62  
61   - private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
  63 + private final ApiErrorLogApi apiErrorLogApi;
62 64  
63 65 /**
64 66 * 处理所有异常,主要是提供给 Filter 使用
... ... @@ -88,7 +90,7 @@ public class GlobalExceptionHandler {
88 90 return validationException((ValidationException) ex);
89 91 }
90 92 if (ex instanceof NoHandlerFoundException) {
91   - return noHandlerFoundExceptionHandler(request, (NoHandlerFoundException) ex);
  93 + return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
92 94 }
93 95 if (ex instanceof NoResourceFoundException) {
94 96 return noResourceFoundExceptionHandler(request, (NoResourceFoundException) ex);
... ... @@ -123,7 +125,7 @@ public class GlobalExceptionHandler {
123 125 */
124 126 @ExceptionHandler(MethodArgumentTypeMismatchException.class)
125 127 public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
126   - log.warn("[missingServletRequestParameterExceptionHandler]", ex);
  128 + log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
127 129 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
128 130 }
129 131  
... ... @@ -150,6 +152,22 @@ public class GlobalExceptionHandler {
150 152 }
151 153  
152 154 /**
  155 + * 处理 SpringMVC 请求参数类型错误
  156 + *
  157 + * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
  158 + */
  159 + @ExceptionHandler(HttpMessageNotReadableException.class)
  160 + public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
  161 + log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
  162 + if(ex.getCause() instanceof InvalidFormatException) {
  163 + InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
  164 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
  165 + }else {
  166 + return defaultExceptionHandler(ServletUtils.getRequest(), ex);
  167 + }
  168 + }
  169 +
  170 + /**
153 171 * 处理 Validator 校验不通过产生的异常
154 172 */
155 173 @ExceptionHandler(value = ConstraintViolationException.class)
... ... @@ -177,7 +195,7 @@ public class GlobalExceptionHandler {
177 195 * 2. spring.mvc.static-path-pattern 为 /statics/**
178 196 */
179 197 @ExceptionHandler(NoHandlerFoundException.class)
180   - public CommonResult<?> noHandlerFoundExceptionHandler(HttpServletRequest req, NoHandlerFoundException ex) {
  198 + public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
181 199 log.warn("[noHandlerFoundExceptionHandler]", ex);
182 200 return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
183 201 }
... ... @@ -253,7 +271,7 @@ public class GlobalExceptionHandler {
253 271 // 情况二:处理异常
254 272 log.error("[defaultExceptionHandler]", ex);
255 273 // 插入异常日志
256   - this.createExceptionLog(req, ex);
  274 + createExceptionLog(req, ex);
257 275 // 返回 ERROR CommonResult
258 276 return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
259 277 }
... ... @@ -265,7 +283,7 @@ public class GlobalExceptionHandler {
265 283 // 初始化 errorLog
266 284 buildExceptionLog(errorLog, req, e);
267 285 // 执行插入 errorLog
268   - apiErrorLogFrameworkService.createApiErrorLog(errorLog);
  286 + apiErrorLogApi.createApiErrorLogAsync(errorLog);
269 287 } catch (Throwable th) {
270 288 log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
271 289 }
... ... @@ -279,7 +297,7 @@ public class GlobalExceptionHandler {
279 297 errorLog.setExceptionName(e.getClass().getName());
280 298 errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
281 299 errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
282   - errorLog.setExceptionStackTrace(ExceptionUtils.getStackTrace(e));
  300 + errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
283 301 StackTraceElement[] stackTraceElements = e.getStackTrace();
284 302 Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
285 303 StackTraceElement stackTraceElement = stackTraceElements[0];
... ... @@ -292,12 +310,12 @@ public class GlobalExceptionHandler {
292 310 errorLog.setApplicationName(applicationName);
293 311 errorLog.setRequestUrl(request.getRequestURI());
294 312 Map<String, Object> requestParams = MapUtil.<String, Object>builder()
295   - .put("query", ServletUtils.getParamMap(request))
296   - .put("body", ServletUtils.getBody(request)).build();
  313 + .put("query", JakartaServletUtil.getParamMap(request))
  314 + .put("body", JakartaServletUtil.getBody(request)).build();
297 315 errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
298 316 errorLog.setRequestMethod(request.getMethod());
299 317 errorLog.setUserAgent(ServletUtils.getUserAgent(request));
300   - errorLog.setUserIp(ServletUtils.getClientIP(request));
  318 + errorLog.setUserIp(JakartaServletUtil.getClientIP(request));
301 319 errorLog.setExceptionTime(LocalDateTime.now());
302 320 }
303 321  
... ... @@ -314,51 +332,51 @@ public class GlobalExceptionHandler {
314 332 }
315 333 // 1. 数据报表
316 334 if (message.contains("report_")) {
317   - log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
  335 + log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
318 336 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
319   - "[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
  337 + "[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
320 338 }
321 339 // 2. 工作流
322 340 if (message.contains("bpm_")) {
323   - log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
  341 + log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
324 342 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
325   - "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
  343 + "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
326 344 }
327 345 // 3. 微信公众号
328 346 if (message.contains("mp_")) {
329   - log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
  347 + log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
330 348 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
331   - "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
  349 + "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
332 350 }
333 351 // 4. 商城系统
334 352 if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
335   - log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
  353 + log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
336 354 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
337   - "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
  355 + "[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
338 356 }
339 357 // 5. ERP 系统
340 358 if (message.contains("erp_")) {
341   - log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  359 + log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
342 360 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
343   - "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  361 + "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
344 362 }
345 363 // 6. CRM 系统
346 364 if (message.contains("crm_")) {
347   - log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
  365 + log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
348 366 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
349   - "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
  367 + "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
350 368 }
351 369 // 7. 支付平台
352 370 if (message.contains("pay_")) {
353   - log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
  371 + log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
354 372 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
355   - "[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
  373 + "[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
356 374 }
357 375 // 8. AI 大模型
358 376 if (message.contains("ai_")) {
359   - log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://doc.iocoder.cn/ai/build/ 开启]");
  377 + log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
360 378 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
361   - "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://doc.iocoder.cn/ai/build/ 开启]");
  379 + "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
362 380 }
363 381 return null;
364 382 }
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
5 5 import cn.iocoder.yudao.framework.websocket.core.handler.JsonWebSocketMessageHandler;
6 6 import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
7 7 import cn.iocoder.yudao.framework.websocket.core.security.LoginUserHandshakeInterceptor;
  8 +import cn.iocoder.yudao.framework.websocket.core.security.WebSocketAuthorizeRequestsCustomizer;
8 9 import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;
9 10 import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;
10 11 import cn.iocoder.yudao.framework.websocket.core.sender.local.LocalWebSocketMessageSender;
... ... @@ -76,6 +77,11 @@ public class YudaoWebSocketAutoConfiguration {
76 77 return new WebSocketSessionManagerImpl();
77 78 }
78 79  
  80 + @Bean
  81 + public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {
  82 + return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);
  83 + }
  84 +
79 85 // ==================== Sender 相关 ====================
80 86  
81 87 @Configuration
... ...
yudao-module-ai/pom.xml
... ... @@ -18,7 +18,7 @@
18 18  
19 19 <name>${project.artifactId}</name>
20 20 <description>
21   - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。
  21 + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
22 22 目前已接入各种模型,不限于:
23 23 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
24 24 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
1 1 package cn.iocoder.yudao.module.ai.enums;
2 2  
3   -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
4 3 import lombok.AllArgsConstructor;
5 4 import lombok.Getter;
6 5  
7   -import java.util.Arrays;
8   -
9 6 /**
10 7 * AI 内置聊天角色的枚举
11 8 *
... ... @@ -13,16 +10,16 @@ import java.util.Arrays;
13 10 */
14 11 @AllArgsConstructor
15 12 @Getter
16   -public enum AiChatRoleEnum implements IntArrayValuable {
  13 +public enum AiChatRoleEnum {
17 14  
18   - AI_WRITE_ROLE(1, "写作助手", """
  15 + AI_WRITE_ROLE("写作助手", """
19 16 你是一位出色的写作助手,能够帮助用户生成创意和灵感,并在用户提供场景和提示词时生成对应的回复。你的任务包括:
20 17 1. 撰写建议:根据用户提供的主题或问题,提供详细的写作建议、情节发展方向、角色设定以及背景描写,确保内容结构清晰、有逻辑。
21 18 2. 回复生成:根据用户提供的场景和提示词,生成合适的对话或文字回复,确保语气和风格符合场景需求。
22 19 除此之外不需要除了正文内容外的其他回复,如标题、开头、任何解释性语句或道歉。
23 20 """),
24 21  
25   - AI_MIND_MAP_ROLE(2, "脑图助手", """
  22 + AI_MIND_MAP_ROLE("导图助手", """
26 23 你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
27 24 # Geek-AI 助手
28 25 ## 完整的开源系统
... ... @@ -39,11 +36,6 @@ public enum AiChatRoleEnum implements IntArrayValuable {
39 36 除此之外不要任何解释性语句。
40 37 """);
41 38  
42   - // TODO @xin:这个 role 是不是删除掉好点哈。= = 目前主要是没做角色枚举。这里多了 role 反倒容易误解哈
43   - /**
44   - * 角色
45   - */
46   - private final Integer role;
47 39 /**
48 40 * 角色名
49 41 */
... ... @@ -54,11 +46,4 @@ public enum AiChatRoleEnum implements IntArrayValuable {
54 46 */
55 47 private final String systemMessage;
56 48  
57   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiChatRoleEnum::getRole).toArray();
58   -
59   - @Override
60   - public int[] array() {
61   - return ARRAYS;
62   - }
63   -
64 49 }
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
... ... @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4 4  
5 5 /**
6 6 * AI 错误码枚举类
7   - *
  7 + * <p>
8 8 * ai 系统,使用 1-040-000-000 段
9 9 */
10 10 public interface ErrorCodeConstants {
... ... @@ -45,9 +45,16 @@ public interface ErrorCodeConstants {
45 45 // ========== API 音乐 1-040-006-000 ==========
46 46 ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!");
47 47  
48   -
49 48 // ========== API 写作 1-022-007-000 ==========
50 49 ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!");
51 50 ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!");
52 51  
  52 + // ========== API 思维导图 1-040-008-000 ==========
  53 + ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!");
  54 +
  55 + // ========== API 知识库 1-022-008-000 ==========
  56 + ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");
  57 + ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");
  58 + ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!");
  59 +
53 60 }
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/knowledge/AiKnowledgeDocumentStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.enums.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * AI 知识库-文档状态的枚举
  11 + *
  12 + * @author xiaoxin
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum AiKnowledgeDocumentStatusEnum implements IntArrayValuable {
  17 +
  18 + IN_PROGRESS(10, "索引中"),
  19 + SUCCESS(20, "可用"),
  20 + FAIL(30, "失败");
  21 +
  22 + /**
  23 + * 状态
  24 + */
  25 + private final Integer status;
  26 +
  27 + /**
  28 + * 状态名
  29 + */
  30 + private final String name;
  31 +
  32 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiKnowledgeDocumentStatusEnum::getStatus).toArray();
  33 +
  34 + @Override
  35 + public int[] array() {
  36 + return ARRAYS;
  37 + }
  38 +
  39 +}
... ...
yudao-module-ai/yudao-module-ai-biz/pom.xml
... ... @@ -12,7 +12,7 @@
12 12  
13 13 <name>${project.artifactId}</name>
14 14 <description>
15   - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。
  15 + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
16 16 目前已接入各种模型,不限于:
17 17 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
18 18 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
... ... @@ -5,10 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
10   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO;
11   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
12 9 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
13 10 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
14 11 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
... ... @@ -45,6 +42,13 @@ public class AiImageController {
45 42 return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
46 43 }
47 44  
  45 + @GetMapping("/public-page")
  46 + @Operation(summary = "获取公开的绘图分页")
  47 + public CommonResult<PageResult<AiImageRespVO>> getImagePagePublic(AiImagePublicPageReqVO pageReqVO) {
  48 + PageResult<AiImageDO> pageResult = imageService.getImagePagePublic(pageReqVO);
  49 + return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
  50 + }
  51 +
48 52 @GetMapping("/get-my")
49 53 @Operation(summary = "获取【我的】绘图记录")
50 54 @Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePublicPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
  8 +@Data
  9 +public class AiImagePublicPageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "提示词")
  12 + private String prompt;
  13 +
  14 +}
0 15 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  21 +
  22 +@Tag(name = "管理后台 - AI 知识库")
  23 +@RestController
  24 +@RequestMapping("/ai/knowledge")
  25 +@Validated
  26 +public class AiKnowledgeController {
  27 +
  28 + @Resource
  29 + private AiKnowledgeService knowledgeService;
  30 +
  31 + @GetMapping("/my-page")
  32 + @Operation(summary = "获取【我的】知识库分页")
  33 + public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePageMy(@Validated PageParam pageReqVO) {
  34 + PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePageMy(getLoginUserId(), pageReqVO);
  35 + return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
  36 + }
  37 +
  38 + @PostMapping("/create-my")
  39 + @Operation(summary = "创建【我的】知识库")
  40 + public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) {
  41 + return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId()));
  42 + }
  43 +
  44 + @PutMapping("/update-my")
  45 + @Operation(summary = "更新【我的】知识库")
  46 + public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) {
  47 + knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId());
  48 + return success(true);
  49 + }
  50 +
  51 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - AI 知识库文档")
  22 +@RestController
  23 +@RequestMapping("/ai/knowledge/document")
  24 +@Validated
  25 +public class AiKnowledgeDocumentController {
  26 +
  27 + @Resource
  28 + private AiKnowledgeDocumentService documentService;
  29 +
  30 + @PostMapping("/create")
  31 + @Operation(summary = "新建文档")
  32 + public CommonResult<Long> createKnowledgeDocument(@Valid AiKnowledgeDocumentCreateReqVO reqVO) {
  33 + Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
  34 + return success(knowledgeDocumentId);
  35 + }
  36 +
  37 + @GetMapping("/page")
  38 + @Operation(summary = "获取文档分页")
  39 + public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPageMy(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
  40 + PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
  41 + return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
  42 + }
  43 +
  44 + @PutMapping("/update")
  45 + @Operation(summary = "更新文档")
  46 + public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
  47 + documentService.updateKnowledgeDocument(reqVO);
  48 + return success(true);
  49 + }
  50 +
  51 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - AI 知识库段落")
  22 +@RestController
  23 +@RequestMapping("/ai/knowledge/segment")
  24 +@Validated
  25 +public class AiKnowledgeSegmentController {
  26 +
  27 + @Resource
  28 + private AiKnowledgeSegmentService segmentService;
  29 +
  30 + @GetMapping("/page")
  31 + @Operation(summary = "获取段落分页")
  32 + public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPageMy(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
  33 + PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
  34 + return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
  35 + }
  36 +
  37 + @PutMapping("/update")
  38 + @Operation(summary = "更新段落内容")
  39 + public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) {
  40 + segmentService.updateKnowledgeSegment(reqVO);
  41 + return success(true);
  42 + }
  43 +
  44 + @PutMapping("/update-status")
  45 + @Operation(summary = "启禁用段落内容")
  46 + public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
  47 + segmentService.updateKnowledgeSegmentStatus(reqVO);
  48 + return success(true);
  49 + }
  50 +
  51 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - AI 知识库文档的分页 Request VO")
  8 +@Data
  9 +public class AiKnowledgeDocumentPageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "文档名称", example = "Java 开发手册")
  12 + private String name;
  13 +
  14 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
  8 +@Data
  9 +public class AiKnowledgeDocumentRespVO extends PageParam {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  12 + private Long id;
  13 +
  14 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  15 + private Long knowledgeId;
  16 +
  17 + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  18 + private String name;
  19 +
  20 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
  21 + private String content;
  22 +
  23 + @Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  24 + private String url;
  25 +
  26 + @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  27 + private Integer tokens;
  28 +
  29 + @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
  30 + private Integer wordCount;
  31 +
  32 + @Schema(description = "切片状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  33 + private Integer sliceStatus;
  34 +
  35 + @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  36 + private Integer status;
  37 +
  38 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +
  10 +@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO")
  11 +@Data
  12 +public class AiKnowledgeDocumentUpdateReqVO {
  13 +
  14 +
  15 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  16 + @NotNull(message = "编号不能为空")
  17 + private Long id;
  18 +
  19 + @Schema(description = "是否启用", example = "1")
  20 + @InEnum(CommonStatusEnum.class)
  21 + private Integer status;
  22 +
  23 + @Schema(description = "名称", example = "Java 开发手册")
  24 + private String name;
  25 +
  26 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - AI 知识库创建【我的】 Request VO")
  11 +@Data
  12 +public class AiKnowledgeCreateMyReqVO {
  13 +
  14 + @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
  15 + @NotBlank(message = "知识库名称不能为空")
  16 + private String name;
  17 +
  18 + @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
  19 + private String description;
  20 +
  21 + @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]")
  22 + private List<Long> visibilityPermissions;
  23 +
  24 + @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "嵌入模型不能为空")
  26 + private Long modelId;
  27 +
  28 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
  8 +
  9 +
  10 +@Schema(description = "管理后台 - AI 知识库文档的创建 Request VO")
  11 +@Data
  12 +public class AiKnowledgeDocumentCreateReqVO {
  13 +
  14 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
  15 + @NotNull(message = "知识库编号不能为空")
  16 + private Long knowledgeId;
  17 +
  18 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
  19 + @NotBlank(message = "文档名称不能为空")
  20 + private String name;
  21 +
  22 + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  23 + @URL(message = "文档 URL 格式不正确")
  24 + private String url;
  25 +
  26 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +
  7 +@Schema(description = "管理后台 - AI 知识库 Response VO")
  8 +@Data
  9 +public class AiKnowledgeRespVO {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  12 + private Long id;
  13 +
  14 + @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
  15 + private String name;
  16 +
  17 + @Schema(description = "知识库描述", example = "帮助你快速构建系统")
  18 + private String description;
  19 +
  20 + @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
  21 + private Long modelId;
  22 +
  23 + @Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
  24 + private String model;
  25 +
  26 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
  11 +@Data
  12 +public class AiKnowledgeUpdateMyReqVO {
  13 +
  14 + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
  15 + @NotNull(message = "知识库编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
  19 + @NotBlank(message = "知识库名称不能为空")
  20 + private String name;
  21 +
  22 + @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
  23 + private String description;
  24 +
  25 + @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]")
  26 + private List<Long> visibilityPermissions;
  27 +
  28 + @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + @NotNull(message = "嵌入模型不能为空")
  30 + private Long modelId;
  31 +
  32 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - AI 知识库分段的分页 Request VO")
  8 +@Data
  9 +public class AiKnowledgeSegmentPageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "分段状态", example = "1")
  12 + private Integer status;
  13 +
  14 + @Schema(description = "文档编号", example = "1")
  15 + private Integer documentId;
  16 +
  17 + @Schema(description = "分段内容关键字", example = "Java 开发")
  18 + private String keyword;
  19 +
  20 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentRespVO {
  9 +
  10 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  11 + private Long id;
  12 +
  13 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  14 + private Long documentId;
  15 +
  16 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  17 + private Long knowledgeId;
  18 +
  19 + @Schema(description = "向量库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c")
  20 + private String vectorId;
  21 +
  22 + @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  23 + private String content;
  24 +
  25 + @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + private Integer tokens;
  27 +
  28 + @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
  29 + private Integer wordCount;
  30 +
  31 + @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer status;
  33 +
  34 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +
  7 +@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO")
  8 +@Data
  9 +public class AiKnowledgeSegmentUpdateReqVO {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  12 + private Long id;
  13 +
  14 + @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  15 + private String content;
  16 +
  17 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +
  10 +@Schema(description = "管理后台 - AI 知识库段落的更新状态 Request VO")
  11 +@Data
  12 +public class AiKnowledgeSegmentUpdateStatusReqVO {
  13 +
  14 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  15 + private Long id;
  16 +
  17 + @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + @NotNull(message = "是否启用不能为空")
  19 + @InEnum(CommonStatusEnum.class)
  20 + private Integer status;
  21 +
  22 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.mindmap;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 6 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapRespVO;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
5 10 import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService;
6 11 import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
7 13 import io.swagger.v3.oas.annotations.tags.Tag;
8 14 import jakarta.annotation.Resource;
9 15 import jakarta.annotation.security.PermitAll;
10 16 import jakarta.validation.Valid;
11 17 import org.springframework.http.MediaType;
12   -import org.springframework.web.bind.annotation.PostMapping;
13   -import org.springframework.web.bind.annotation.RequestBody;
14   -import org.springframework.web.bind.annotation.RequestMapping;
15   -import org.springframework.web.bind.annotation.RestController;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.web.bind.annotation.*;
16 20 import reactor.core.publisher.Flux;
17 21  
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
18 23 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
19 24  
20 25 @Tag(name = "管理后台 - AI 思维导图")
... ... @@ -26,10 +31,29 @@ public class AiMindMapController {
26 31 private AiMindMapService mindMapService;
27 32  
28 33 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
29   - @Operation(summary = "脑图生成(流式)", description = "流式返回,响应较快")
  34 + @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快")
30 35 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
31 36 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) {
32 37 return mindMapService.generateMindMap(generateReqVO, getLoginUserId());
33 38 }
34 39  
  40 + // ================ 导图管理 ================
  41 +
  42 + @DeleteMapping("/delete")
  43 + @Operation(summary = "删除思维导图")
  44 + @Parameter(name = "id", description = "编号", required = true)
  45 + @PreAuthorize("@ss.hasPermission('ai:mind-map:delete')")
  46 + public CommonResult<Boolean> deleteMindMap(@RequestParam("id") Long id) {
  47 + mindMapService.deleteMindMap(id);
  48 + return success(true);
  49 + }
  50 +
  51 + @GetMapping("/page")
  52 + @Operation(summary = "获得思维导图分页")
  53 + @PreAuthorize("@ss.hasPermission('ai:mind-map:query')")
  54 + public CommonResult<PageResult<AiMindMapRespVO>> getMindMapPage(@Valid AiMindMapPageReqVO pageReqVO) {
  55 + PageResult<AiMindMapDO> pageResult = mindMapService.getMindMapPage(pageReqVO);
  56 + return success(BeanUtils.toBean(pageResult, AiMindMapRespVO.class));
  57 + }
  58 +
35 59 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - AI 思维导图分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class AiMindMapPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "用户编号", example = "4325")
  21 + private Long userId;
  22 +
  23 + @Schema(description = "生成内容提示", example = "Java 学习路线")
  24 + private String prompt;
  25 +
  26 + @Schema(description = "创建时间")
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + private LocalDateTime[] createTime;
  29 +
  30 +}
0 31 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 思维导图 Response VO")
  9 +@Data
  10 +public class AiMindMapRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3373")
  13 + private Long id;
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4325")
  16 + private Long userId;
  17 +
  18 + @Schema(description = "生成内容提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
  19 + private String prompt;
  20 +
  21 + @Schema(description = "生成的思维导图内容")
  22 + private String generatedContent;
  23 +
  24 + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
  25 + private String platform;
  26 +
  27 + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125")
  28 + private String model;
  29 +
  30 + @Schema(description = "错误信息")
  31 + private String errorMessage;
  32 +
  33 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private LocalDateTime createTime;
  35 +
  36 +}
0 37 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
... ... @@ -30,7 +30,7 @@ public class AiImageDO extends BaseDO {
30 30 /**
31 31 * 编号
32 32 */
33   - @TableId(type = IdType.AUTO)
  33 + @TableId
34 34 private Long id;
35 35  
36 36 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableField;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  9 +import lombok.Data;
  10 +
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * AI 知识库 DO
  15 + *
  16 + * @author xiaoxin
  17 + */
  18 +@TableName(value = "ai_knowledge", autoResultMap = true)
  19 +@Data
  20 +public class AiKnowledgeDO extends BaseDO {
  21 +
  22 + /**
  23 + * 编号
  24 + */
  25 + @TableId
  26 + private Long id;
  27 + /**
  28 + * 用户编号
  29 + * <p>
  30 + * 关联 AdminUserDO 的 userId 字段
  31 + */
  32 + private Long userId;
  33 + /**
  34 + * 知识库名称
  35 + */
  36 + private String name;
  37 + /**
  38 + * 知识库描述
  39 + */
  40 + private String description;
  41 + // TODO @新:如果全部可见,需要怎么设置?
  42 + /**
  43 + * 可见权限,只能选择哪些人可见
  44 + */
  45 + @TableField(typeHandler = JacksonTypeHandler.class)
  46 + private List<Long> visibilityPermissions;
  47 + /**
  48 + * 嵌入模型编号
  49 + */
  50 + private Long modelId;
  51 + /**
  52 + * 模型标识
  53 + */
  54 + private String model;
  55 + /**
  56 + * 状态
  57 + * <p>
  58 + * 枚举 {@link CommonStatusEnum}
  59 + */
  60 + private Integer status;
  61 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.Data;
  9 +
  10 +/**
  11 + * AI 知识库-文档 DO
  12 + *
  13 + * @author xiaoxin
  14 + */
  15 +@TableName(value = "ai_knowledge_document")
  16 +@Data
  17 +public class AiKnowledgeDocumentDO extends BaseDO {
  18 +
  19 + /**
  20 + * 编号
  21 + */
  22 + @TableId
  23 + private Long id;
  24 + /**
  25 + * 知识库编号
  26 + *
  27 + * 关联 {@link AiKnowledgeDO#getId()}
  28 + */
  29 + private Long knowledgeId;
  30 + /**
  31 + * 文件名称
  32 + */
  33 + private String name;
  34 + /**
  35 + * 内容
  36 + */
  37 + private String content;
  38 + /**
  39 + * 文件 URL
  40 + */
  41 + private String url;
  42 + /**
  43 + * token 数量
  44 + */
  45 + private Integer tokens;
  46 + /**
  47 + * 字符数
  48 + */
  49 + private Integer wordCount;
  50 + /**
  51 + * 切片状态
  52 + * <p>
  53 + * 枚举 {@link AiKnowledgeDocumentStatusEnum}
  54 + */
  55 + private Integer sliceStatus;
  56 +
  57 + /**
  58 + * 状态
  59 + * <p>
  60 + * 枚举 {@link CommonStatusEnum}
  61 + */
  62 + private Integer status;
  63 +
  64 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.Data;
  8 +
  9 +/**
  10 + * AI 知识库-文档分段 DO
  11 + *
  12 + * @author xiaoxin
  13 + */
  14 +@TableName(value = "ai_knowledge_segment")
  15 +@Data
  16 +public class AiKnowledgeSegmentDO extends BaseDO {
  17 +
  18 + public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
  19 +
  20 + /**
  21 + * 编号
  22 + */
  23 + @TableId
  24 + private Long id;
  25 + /**
  26 + * 向量库的编号
  27 + */
  28 + private String vectorId;
  29 + /**
  30 + * 知识库编号
  31 + *
  32 + * 关联 {@link AiKnowledgeDO#getId()}
  33 + */
  34 + private Long knowledgeId;
  35 + /**
  36 + * 文档编号
  37 + *
  38 + * 关联 {@link AiKnowledgeDocumentDO#getId()}
  39 + */
  40 + private Long documentId;
  41 + /**
  42 + * 切片内容
  43 + */
  44 + private String content;
  45 + /**
  46 + * 字符数
  47 + */
  48 + private Integer wordCount;
  49 + /**
  50 + * token 数量
  51 + */
  52 + private Integer tokens;
  53 + /**
  54 + * 状态
  55 + *
  56 + * 枚举 {@link CommonStatusEnum}
  57 + */
  58 + private Integer status;
  59 +
  60 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
... ... @@ -19,7 +19,7 @@ public class AiMindMapDO extends BaseDO {
19 19 /**
20 20 * 编号
21 21 */
22   - @TableId(type = IdType.AUTO)
  22 + @TableId
23 23 private Long id;
24 24  
25 25 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
... ... @@ -25,7 +25,7 @@ public class AiMusicDO extends BaseDO {
25 25 /**
26 26 * 编号
27 27 */
28   - @TableId(type = IdType.AUTO)
  28 + @TableId
29 29 private Long id;
30 30  
31 31 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
... ... @@ -20,7 +20,7 @@ public class AiWriteDO extends BaseDO {
20 20 /**
21 21 * 编号
22 22 */
23   - @TableId(type = IdType.AUTO)
  23 + @TableId
24 24 private Long id;
25 25  
26 26 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePublicPageReqVO;
7 8 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
... ... @@ -41,6 +42,13 @@ public interface AiImageMapper extends BaseMapperX&lt;AiImageDO&gt; {
41 42 .orderByDesc(AiImageDO::getId));
42 43 }
43 44  
  45 + default PageResult<AiImageDO> selectPage(AiImagePublicPageReqVO pageReqVO) {
  46 + return selectPage(pageReqVO, new LambdaQueryWrapperX<AiImageDO>()
  47 + .eqIfPresent(AiImageDO::getPublicStatus, Boolean.TRUE)
  48 + .likeIfPresent(AiImageDO::getPrompt, pageReqVO.getPrompt())
  49 + .orderByDesc(AiImageDO::getId));
  50 + }
  51 +
44 52 default List<AiImageDO> selectListByStatusAndPlatform(Integer status, String platform) {
45 53 return selectList(AiImageDO::getStatus, status,
46 54 AiImageDO::getPlatform, platform);
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * AI 知识库-文档 Mapper
  12 + *
  13 + * @author xiaoxin
  14 + */
  15 +@Mapper
  16 +public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> {
  17 +
  18 + default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
  20 + .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
  21 + .orderByDesc(AiKnowledgeDocumentDO::getId));
  22 + }
  23 +
  24 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  7 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +
  11 +/**
  12 + * AI 知识库基础信息 Mapper
  13 + *
  14 + * @author xiaoxin
  15 + */
  16 +@Mapper
  17 +public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
  18 +
  19 + default PageResult<AiKnowledgeDO> selectPageByMy(Long userId, PageParam pageReqVO) {
  20 + return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
  21 + .eq(AiKnowledgeDO::getUserId, userId)
  22 + .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
  23 + .orderByDesc(AiKnowledgeDO::getId));
  24 + }
  25 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * AI 知识库-分片 Mapper
  12 + *
  13 + * @author xiaoxin
  14 + */
  15 +@Mapper
  16 +public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> {
  17 +
  18 + default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
  20 + .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
  21 + .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
  22 + .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
  23 + .orderByDesc(AiKnowledgeSegmentDO::getId));
  24 + }
  25 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql.mindmap;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
4 7 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
5 8 import org.apache.ibatis.annotations.Mapper;
6 9  
... ... @@ -11,4 +14,13 @@ import org.apache.ibatis.annotations.Mapper;
11 14 */
12 15 @Mapper
13 16 public interface AiMindMapMapper extends BaseMapperX<AiMindMapDO> {
  17 +
  18 + default PageResult<AiMindMapDO> selectPage(AiMindMapPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<AiMindMapDO>()
  20 + .eqIfPresent(AiMindMapDO::getUserId, reqVO.getUserId())
  21 + .eqIfPresent(AiMindMapDO::getPrompt, reqVO.getPrompt())
  22 + .betweenIfPresent(AiMindMapDO::getCreateTime, reqVO.getCreateTime())
  23 + .orderByDesc(AiMindMapDO::getId));
  24 + }
  25 +
14 26 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
... ... @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.ai.service.image;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
6   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
7   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
8 6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
9 7 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
10 8 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
... ... @@ -29,6 +27,14 @@ public interface AiImageService {
29 27 PageResult<AiImageDO> getImagePageMy(Long userId, AiImagePageReqVO pageReqVO);
30 28  
31 29 /**
  30 + * 获取公开的绘图分页
  31 + *
  32 + * @param pageReqVO 分页条件
  33 + * @return 绘图分页
  34 + */
  35 + PageResult<AiImageDO> getImagePagePublic(AiImagePublicPageReqVO pageReqVO);
  36 +
  37 + /**
32 38 * 获得绘图记录
33 39 *
34 40 * @param id 绘图编号
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
... ... @@ -12,9 +12,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 12 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
13 13 import cn.iocoder.yudao.framework.common.pojo.PageResult;
14 14 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
15   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
16   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
17   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
  15 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
18 16 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
19 17 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
20 18 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
... ... @@ -71,6 +69,11 @@ public class AiImageServiceImpl implements AiImageService {
71 69 }
72 70  
73 71 @Override
  72 + public PageResult<AiImageDO> getImagePagePublic(AiImagePublicPageReqVO pageReqVO) {
  73 + return imageMapper.selectPage(pageReqVO);
  74 + }
  75 +
  76 + @Override
74 77 public AiImageDO getImage(Long id) {
75 78 return imageMapper.selectById(id);
76 79 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  8 +
  9 +/**
  10 + * AI 知识库-文档 Service 接口
  11 + *
  12 + * @author xiaoxin
  13 + */
  14 +public interface AiKnowledgeDocumentService {
  15 +
  16 + /**
  17 + * 创建文档
  18 + *
  19 + * @param createReqVO 文档创建 Request VO
  20 + * @return 文档编号
  21 + */
  22 + Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
  23 +
  24 +
  25 + /**
  26 + * 获取文档分页
  27 + *
  28 + * @param pageReqVO 分页参数
  29 + * @return 文档分页
  30 + */
  31 + PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
  32 +
  33 + /**
  34 + * 更新文档
  35 + *
  36 + * @param reqVO 更新信息
  37 + */
  38 + void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
  39 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.http.HttpUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  11 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
  12 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  14 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  16 +import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
  17 +import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
  18 +import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
  19 +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  20 +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  21 +import jakarta.annotation.Resource;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.springframework.ai.document.Document;
  24 +import org.springframework.ai.reader.tika.TikaDocumentReader;
  25 +import org.springframework.ai.tokenizer.TokenCountEstimator;
  26 +import org.springframework.ai.transformer.splitter.TokenTextSplitter;
  27 +import org.springframework.ai.vectorstore.VectorStore;
  28 +import org.springframework.core.io.ByteArrayResource;
  29 +import org.springframework.stereotype.Service;
  30 +import org.springframework.transaction.annotation.Transactional;
  31 +
  32 +import java.util.List;
  33 +
  34 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  35 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
  36 +
  37 +/**
  38 + * AI 知识库文档 Service 实现类
  39 + *
  40 + * @author xiaoxin
  41 + */
  42 +@Service
  43 +@Slf4j
  44 +public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService {
  45 +
  46 + @Resource
  47 + private AiKnowledgeDocumentMapper documentMapper;
  48 + @Resource
  49 + private AiKnowledgeSegmentMapper segmentMapper;
  50 +
  51 + @Resource
  52 + private TokenTextSplitter tokenTextSplitter;
  53 + @Resource
  54 + private TokenCountEstimator tokenCountEstimator;
  55 +
  56 + @Resource
  57 + private AiApiKeyService apiKeyService;
  58 + @Resource
  59 + private AiKnowledgeService knowledgeService;
  60 + @Resource
  61 + private AiChatModelService chatModelService;
  62 +
  63 + @Override
  64 + @Transactional(rollbackFor = Exception.class)
  65 + public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
  66 + // 0. 校验
  67 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
  68 + AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
  69 +
  70 + // 1.1 下载文档
  71 + TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl()));
  72 + List<Document> documents = loader.get();
  73 + Document document = CollUtil.getFirst(documents);
  74 + // 1.2 文档记录入库
  75 + String content = document.getContent();
  76 + AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
  77 + .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())
  78 + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
  79 + documentMapper.insert(documentDO);
  80 + Long documentId = documentDO.getId();
  81 + if (CollUtil.isEmpty(documents)) {
  82 + return documentId;
  83 + }
  84 +
  85 + // 2.1 文档分段
  86 + List<Document> segments = tokenTextSplitter.apply(documents);
  87 + // 2.2 分段内容入库
  88 + List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
  89 + segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)
  90 + .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
  91 + .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
  92 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  93 + segmentMapper.insertBatch(segmentDOList);
  94 +
  95 + // 3.1 获取向量存储实例
  96 + VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
  97 + // 3.2 向量化并存储
  98 + segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId()));
  99 + vectorStore.add(segments);
  100 + return documentId;
  101 + }
  102 +
  103 + @Override
  104 + public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
  105 + return documentMapper.selectPage(pageReqVO);
  106 + }
  107 +
  108 + @Override
  109 + public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
  110 + // 1. 校验文档是否存在
  111 + validateKnowledgeDocumentExists(reqVO.getId());
  112 + // 2. 更新文档
  113 + AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
  114 + documentMapper.updateById(document);
  115 + }
  116 +
  117 + /**
  118 + * 校验文档是否存在
  119 + *
  120 + * @param id 文档编号
  121 + * @return 文档信息
  122 + */
  123 + private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
  124 + AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
  125 + if (knowledgeDocument == null) {
  126 + throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
  127 + }
  128 + return knowledgeDocument;
  129 + }
  130 +
  131 + private org.springframework.core.io.Resource downloadFile(String url) {
  132 + try {
  133 + byte[] bytes = HttpUtil.downloadBytes(url);
  134 + return new ByteArrayResource(bytes);
  135 + } catch (Exception e) {
  136 + log.error("[downloadFile][url({}) 下载失败]", url, e);
  137 + throw new RuntimeException(e);
  138 + }
  139 + }
  140 +
  141 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  8 +
  9 +/**
  10 + * AI 知识库段落 Service 接口
  11 + *
  12 + * @author xiaoxin
  13 + */
  14 +public interface AiKnowledgeSegmentService {
  15 +
  16 + /**
  17 + * 获取段落分页
  18 + *
  19 + * @param pageReqVO 分页查询
  20 + * @return 文档分页
  21 + */
  22 + PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
  23 +
  24 + /**
  25 + * 更新段落的内容
  26 + *
  27 + * @param reqVO 更新内容
  28 + */
  29 + void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
  30 +
  31 + /**
  32 + * 更新段落的状态
  33 + *
  34 + * @param reqVO 更新内容
  35 + */
  36 + void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
  37 +
  38 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  9 +import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
  10 +import jakarta.annotation.Resource;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.stereotype.Service;
  13 +
  14 +/**
  15 + * AI 知识库分片 Service 实现类
  16 + *
  17 + * @author xiaoxin
  18 + */
  19 +@Service
  20 +@Slf4j
  21 +public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
  22 +
  23 + @Resource
  24 + private AiKnowledgeSegmentMapper segmentMapper;
  25 +
  26 + @Override
  27 + public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
  28 + return segmentMapper.selectPage(pageReqVO);
  29 + }
  30 +
  31 + @Override
  32 + public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
  33 + segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
  34 + // TODO @xin 重新向量化
  35 + }
  36 +
  37 + @Override
  38 + public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
  39 + segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
  40 + // TODO @xin 1.禁用删除向量 2.启用重新向量化
  41 + }
  42 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  8 +
  9 +/**
  10 + * AI 知识库-基础信息 Service 接口
  11 + *
  12 + * @author xiaoxin
  13 + */
  14 +public interface AiKnowledgeService {
  15 +
  16 + /**
  17 + * 创建【我的】知识库
  18 + *
  19 + * @param createReqVO 创建信息
  20 + * @param userId 用户编号
  21 + * @return 编号
  22 + */
  23 + Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
  24 +
  25 +
  26 + /**
  27 + * 创建【我的】知识库
  28 + *
  29 + * @param updateReqVO 更新信息
  30 + * @param userId 用户编号
  31 + */
  32 + void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId);
  33 +
  34 +
  35 + /**
  36 + * 校验知识库是否存在
  37 + *
  38 + * @param id 记录编号
  39 + */
  40 + AiKnowledgeDO validateKnowledgeExists(Long id);
  41 +
  42 + /**
  43 + * 获得【我的】知识库分页
  44 + *
  45 + * @param userId 用户编号
  46 + * @param pageReqVO 分页查询
  47 + * @return 知识库分页
  48 + */
  49 + PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO);
  50 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  12 +import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
  13 +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  14 +import jakarta.annotation.Resource;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +import org.springframework.stereotype.Service;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS;
  20 +
  21 +/**
  22 + * AI 知识库-基础信息 Service 实现类
  23 + *
  24 + * @author xiaoxin
  25 + */
  26 +@Service
  27 +@Slf4j
  28 +public class AiKnowledgeServiceImpl implements AiKnowledgeService {
  29 +
  30 + @Resource
  31 + private AiChatModelService chatModalService;
  32 +
  33 + @Resource
  34 + private AiKnowledgeMapper knowledgeMapper;
  35 +
  36 + @Override
  37 + public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) {
  38 + // 1. 校验模型配置
  39 + AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId());
  40 +
  41 + // 2. 插入知识库
  42 + AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
  43 + .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
  44 + knowledgeMapper.insert(knowledgeBase);
  45 + return knowledgeBase.getId();
  46 + }
  47 +
  48 + @Override
  49 + public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) {
  50 + // 1.1 校验知识库存在
  51 + AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
  52 + if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
  53 + throw exception(KNOWLEDGE_NOT_EXISTS);
  54 + }
  55 + // 1.2 校验模型配置
  56 + AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId());
  57 +
  58 + // 2. 更新知识库
  59 + AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
  60 + updateDO.setModel(model.getModel());
  61 + knowledgeMapper.updateById(updateDO);
  62 + }
  63 +
  64 + @Override
  65 + public AiKnowledgeDO validateKnowledgeExists(Long id) {
  66 + AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
  67 + if (knowledgeBase == null) {
  68 + throw exception(KNOWLEDGE_NOT_EXISTS);
  69 + }
  70 + return knowledgeBase;
  71 + }
  72 +
  73 + @Override
  74 + public PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO) {
  75 + return knowledgeMapper.selectPageByMy(userId, pageReqVO);
  76 + }
  77 +
  78 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java
1 1 package cn.iocoder.yudao.module.ai.service.mindmap;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
5 8 import reactor.core.publisher.Flux;
6 9  
7 10 /**
... ... @@ -20,4 +23,19 @@ public interface AiMindMapService {
20 23 */
21 24 Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId);
22 25  
  26 + /**
  27 + * 删除思维导图
  28 + *
  29 + * @param id 编号
  30 + */
  31 + void deleteMindMap(Long id);
  32 +
  33 + /**
  34 + * 获得思维导图分页
  35 + *
  36 + * @param pageReqVO 分页查询
  37 + * @return 思维导图分页
  38 + */
  39 + PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO);
  40 +
23 41 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
... ... @@ -6,9 +6,11 @@ import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
11 12 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
  13 +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
12 14 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
13 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
14 16 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
... ... @@ -33,8 +35,10 @@ import reactor.core.publisher.Flux;
33 35 import java.util.ArrayList;
34 36 import java.util.List;
35 37  
  38 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
37 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  41 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_EXISTS;
38 42  
39 43 /**
40 44 * AI 思维导图 Service 实现类
... ... @@ -57,10 +61,10 @@ public class AiMindMapServiceImpl implements AiMindMapService {
57 61  
58 62 @Override
59 63 public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) {
60   - // 1. 获取脑图模型。尝试获取思维导图助手角色,如果没有则使用默认模型
  64 + // 1. 获取导图模型。尝试获取思维导图助手角色,如果没有则使用默认模型
61 65 AiChatRoleDO role = CollUtil.getFirst(
62 66 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));
63   - // 1.1 获取脑图执行模型
  67 + // 1.1 获取导图执行模型
64 68 AiChatModelDO model = getModel(role);
65 69 // 1.2 获取角色设定消息
66 70 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage())
... ... @@ -131,4 +135,23 @@ public class AiMindMapServiceImpl implements AiMindMapService {
131 135 return model;
132 136 }
133 137  
  138 + @Override
  139 + public void deleteMindMap(Long id) {
  140 + // 校验存在
  141 + validateMindMapExists(id);
  142 + // 删除
  143 + mindMapMapper.deleteById(id);
  144 + }
  145 +
  146 + private void validateMindMapExists(Long id) {
  147 + if (mindMapMapper.selectById(id) == null) {
  148 + throw exception(MIND_MAP_NOT_EXISTS);
  149 + }
  150 + }
  151 +
  152 + @Override
  153 + public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) {
  154 + return mindMapMapper.selectPage(pageReqVO);
  155 + }
  156 +
134 157 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
... ... @@ -9,7 +9,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
9 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 10 import jakarta.validation.Valid;
11 11 import org.springframework.ai.chat.model.ChatModel;
  12 +import org.springframework.ai.embedding.EmbeddingModel;
12 13 import org.springframework.ai.image.ImageModel;
  14 +import org.springframework.ai.vectorstore.VectorStore;
13 15  
14 16 import java.util.List;
15 17  
... ... @@ -111,4 +113,20 @@ public interface AiApiKeyService {
111 113 */
112 114 SunoApi getSunoApi();
113 115  
  116 + /**
  117 + * 获得 EmbeddingModel 对象
  118 + *
  119 + * @param id 编号
  120 + * @return EmbeddingModel 对象
  121 + */
  122 + EmbeddingModel getEmbeddingModel(Long id);
  123 +
  124 + /**
  125 + * 获得 VectorStore 对象
  126 + *
  127 + * @param id 编号
  128 + * @return VectorStore 对象
  129 + */
  130 + VectorStore getOrCreateVectorStore(Long id);
  131 +
114 132 }
115 133 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory;
5 6 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
6 7 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
7 8 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
... ... @@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
13 14 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 15 import jakarta.annotation.Resource;
15 16 import org.springframework.ai.chat.model.ChatModel;
  17 +import org.springframework.ai.embedding.EmbeddingModel;
16 18 import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.VectorStore;
17 20 import org.springframework.stereotype.Service;
18 21 import org.springframework.validation.annotation.Validated;
19 22  
... ... @@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
36 39  
37 40 @Resource
38 41 private AiModelFactory modelFactory;
  42 + @Resource
  43 + private AiVectorStoreFactory vectorFactory;
39 44  
40 45 @Override
41 46 public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
... ... @@ -132,4 +137,19 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
132 137 }
133 138 return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
134 139 }
  140 +
  141 + @Override
  142 + public EmbeddingModel getEmbeddingModel(Long id) {
  143 + AiApiKeyDO apiKey = validateApiKey(id);
  144 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  145 + return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());
  146 + }
  147 +
  148 + @Override
  149 + public VectorStore getOrCreateVectorStore(Long id) {
  150 + AiApiKeyDO apiKey = validateApiKey(id);
  151 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  152 + return vectorFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl());
  153 + }
  154 +
135 155 }
136 156 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
... ... @@ -14,42 +14,68 @@
14 14 <name>${project.artifactId}</name>
15 15 <description>AI 大模型拓展,接入国内外大模型</description>
16 16 <properties>
17   - <spring-ai.version>1.0.0-M1</spring-ai.version>
  17 + <spring-ai.groupId>group.springframework.ai</spring-ai.groupId>
  18 + <spring-ai.version>1.1.0</spring-ai.version>
18 19 </properties>
19 20  
20 21 <dependencies>
21 22 <dependency>
22   - <groupId>org.springframework.ai</groupId>
  23 + <groupId>${spring-ai.groupId}</groupId>
23 24 <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
24 25 <version>${spring-ai.version}</version>
25 26 </dependency>
26   -
27 27 <dependency>
28   - <groupId>org.springframework.ai</groupId>
  28 + <groupId>${spring-ai.groupId}</groupId>
29 29 <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
30 30 <version>${spring-ai.version}</version>
31 31 </dependency>
32 32 <dependency>
33   - <groupId>org.springframework.ai</groupId>
  33 + <groupId>${spring-ai.groupId}</groupId>
  34 + <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
  35 + <version>${spring-ai.version}</version>
  36 + </dependency>
  37 + <dependency>
  38 + <groupId>${spring-ai.groupId}</groupId>
34 39 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
35 40 <version>${spring-ai.version}</version>
36 41 </dependency>
37 42 <dependency>
38   - <groupId>org.springframework.ai</groupId>
  43 + <groupId>${spring-ai.groupId}</groupId>
39 44 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
40 45 <version>${spring-ai.version}</version>
41 46 </dependency>
42 47  
  48 + <!-- 向量化,基于 Redis 存储,Tika 解析内容 -->
  49 + <dependency>
  50 + <groupId>${spring-ai.groupId}</groupId>
  51 + <artifactId>spring-ai-transformers-spring-boot-starter</artifactId>
  52 + <version>${spring-ai.version}</version>
  53 + </dependency>
  54 + <dependency>
  55 + <groupId>${spring-ai.groupId}</groupId>
  56 + <artifactId>spring-ai-tika-document-reader</artifactId>
  57 + <version>${spring-ai.version}</version>
  58 + </dependency>
  59 + <dependency>
  60 + <groupId>${spring-ai.groupId}</groupId>
  61 + <artifactId>spring-ai-redis-store</artifactId>
  62 + <version>${spring-ai.version}</version>
  63 + </dependency>
  64 +
  65 + <dependency>
  66 + <groupId>cn.iocoder.boot</groupId>
  67 + <artifactId>yudao-spring-boot-starter-redis</artifactId>
  68 + </dependency>
  69 +
43 70 <dependency>
44 71 <groupId>cn.iocoder.boot</groupId>
45 72 <artifactId>yudao-common</artifactId>
46 73 </dependency>
47 74  
48   - <!-- TODO 芋艿:等 spring-ai 官方发布后,需要把 groupId 改下 -->
49 75 <dependency>
50   - <groupId>group.springframework.ai</groupId>
  76 + <groupId>${spring-ai.groupId}</groupId>
51 77 <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
52   - <version>1.1.0</version>
  78 + <version>${spring-ai.version}</version>
53 79 </dependency>
54 80  
55 81 <!-- 阿里云 通义千问 -->
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.ai.config;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
4 4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
  5 +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory;
  6 +import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactoryImpl;
5 7 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
6 8 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
7 9 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
... ... @@ -10,11 +12,15 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
10 12 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
11 13 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
12 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
  16 +import org.springframework.ai.tokenizer.TokenCountEstimator;
  17 +import org.springframework.ai.transformer.splitter.TokenTextSplitter;
13 18 import org.springframework.boot.autoconfigure.AutoConfiguration;
14 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
15 20 import org.springframework.boot.context.properties.EnableConfigurationProperties;
16 21 import org.springframework.context.annotation.Bean;
17 22 import org.springframework.context.annotation.Import;
  23 +import org.springframework.context.annotation.Lazy;
18 24  
19 25 /**
20 26 * 芋道 AI 自动配置
... ... @@ -32,6 +38,12 @@ public class YudaoAiAutoConfiguration {
32 38 return new AiModelFactoryImpl();
33 39 }
34 40  
  41 + @Bean
  42 + public AiVectorStoreFactory aiVectorFactory() {
  43 + return new AiVectorStoreFactoryImpl();
  44 + }
  45 +
  46 +
35 47 // ========== 各种 AI Client 创建 ==========
36 48  
37 49 @Bean
... ... @@ -73,4 +85,43 @@ public class YudaoAiAutoConfiguration {
73 85 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
74 86 }
75 87  
  88 + // ========== rag 相关 ==========
  89 + // TODO @xin 免费版本
  90 +// @Bean
  91 +// @Lazy // TODO 芋艿:临时注释,避免无法启动」
  92 +// public EmbeddingModel transformersEmbeddingClient() {
  93 +// return new TransformersEmbeddingModel(MetadataMode.EMBED);
  94 +// }
  95 +
  96 + /**
  97 + * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel
  98 + */
  99 +// @Bean
  100 +// @Lazy // TODO 芋艿:临时注释,避免无法启动
  101 +// public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties,
  102 +// RedisProperties redisProperties) {
  103 +// var config = RedisVectorStore.RedisVectorStoreConfig.builder()
  104 +// .withIndexName(properties.getIndex())
  105 +// .withPrefix(properties.getPrefix())
  106 +// .build();
  107 +//
  108 +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel,
  109 +// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
  110 +// properties.isInitializeSchema());
  111 +// redisVectorStore.afterPropertiesSet();
  112 +// return redisVectorStore;
  113 +// }
  114 +
  115 + @Bean
  116 + @Lazy // TODO 芋艿:临时注释,避免无法启动
  117 + public TokenTextSplitter tokenTextSplitter() {
  118 + return new TokenTextSplitter(500, 100, 5, 10000, true);
  119 + }
  120 +
  121 + @Bean
  122 + @Lazy // TODO 芋艿:临时注释,避免无法启动
  123 + public TokenCountEstimator tokenCountEstimator() {
  124 + return new JTokkitTokenCountEstimator();
  125 + }
  126 +
76 127 }
77 128 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
... ... @@ -22,7 +22,8 @@ public enum AiPlatformEnum {
22 22  
23 23 // ========== 国外平台 ==========
24 24  
25   - OPENAI("OpenAI", "OpenAI"),
  25 + OPENAI("OpenAI", "OpenAI"), // OpenAI 官方
  26 + AZURE_OPENAI("AzureOpenAI", "AzureOpenAI"), // OpenAI 微软
26 27 OLLAMA("Ollama", "Ollama"),
27 28  
28 29 STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5 5 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
6 6 import org.springframework.ai.chat.model.ChatModel;
  7 +import org.springframework.ai.embedding.EmbeddingModel;
7 8 import org.springframework.ai.image.ImageModel;
8 9  
9 10 /**
... ... @@ -79,4 +80,16 @@ public interface AiModelFactory {
79 80 */
80 81 SunoApi getOrCreateSunoApi(String apiKey, String url);
81 82  
  83 + /**
  84 + * 基于指定配置,获得 EmbeddingModel 对象
  85 + *
  86 + * 如果不存在,则进行创建
  87 + *
  88 + * @param platform 平台
  89 + * @param apiKey API KEY
  90 + * @param url API URL
  91 + * @return ChatModel 对象
  92 + */
  93 + EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url);
  94 +
82 95 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
... ... @@ -21,6 +21,11 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
21 21 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
22 22 import com.alibaba.dashscope.aigc.generation.Generation;
23 23 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
  24 +import com.alibaba.dashscope.embeddings.TextEmbedding;
  25 +import com.azure.ai.openai.OpenAIClient;
  26 +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
  27 +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
  28 +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
24 29 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
25 30 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
26 31 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
... ... @@ -31,7 +36,9 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
31 36 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties;
32 37 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
33 38 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
  39 +import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
34 40 import org.springframework.ai.chat.model.ChatModel;
  41 +import org.springframework.ai.embedding.EmbeddingModel;
35 42 import org.springframework.ai.image.ImageModel;
36 43 import org.springframework.ai.model.function.FunctionCallbackContext;
37 44 import org.springframework.ai.ollama.OllamaChatModel;
... ... @@ -82,6 +89,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
82 89 return buildXingHuoChatModel(apiKey);
83 90 case OPENAI:
84 91 return buildOpenAiChatModel(apiKey, url);
  92 + case AZURE_OPENAI:
  93 + return buildAzureOpenAiChatModel(apiKey, url);
85 94 case OLLAMA:
86 95 return buildOllamaChatModel(url);
87 96 default:
... ... @@ -106,6 +115,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
106 115 return SpringUtil.getBean(XingHuoChatModel.class);
107 116 case OPENAI:
108 117 return SpringUtil.getBean(OpenAiChatModel.class);
  118 + case AZURE_OPENAI:
  119 + return SpringUtil.getBean(AzureOpenAiChatModel.class);
109 120 case OLLAMA:
110 121 return SpringUtil.getBean(OllamaChatModel.class);
111 122 default:
... ... @@ -166,6 +177,20 @@ public class AiModelFactoryImpl implements AiModelFactory {
166 177 return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url));
167 178 }
168 179  
  180 + @Override
  181 + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url) {
  182 + String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url);
  183 + return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
  184 + // TODO @xin 先测试一个
  185 + switch (platform) {
  186 + case TONG_YI:
  187 + return buildTongYiEmbeddingModel(apiKey);
  188 + default:
  189 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  190 + }
  191 + });
  192 + }
  193 +
169 194 private static String buildClientCacheKey(Class<?> clazz, Object... params) {
170 195 if (ArrayUtil.isEmpty(params)) {
171 196 return clazz.getName();
... ... @@ -229,8 +254,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
229 254 }
230 255  
231 256 /**
232   - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(
233   - * ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
  257 + * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
234 258 */
235 259 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
236 260 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
... ... @@ -239,8 +263,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
239 263 }
240 264  
241 265 /**
242   - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(
243   - * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
  266 + * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
244 267 */
245 268 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
246 269 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
... ... @@ -269,6 +292,21 @@ public class AiModelFactoryImpl implements AiModelFactory {
269 292 }
270 293  
271 294 /**
  295 + * 可参考 {@link AzureOpenAiAutoConfiguration}
  296 + */
  297 + private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
  298 + AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
  299 + // 创建 OpenAIClient 对象
  300 + AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
  301 + connectionProperties.setApiKey(apiKey);
  302 + connectionProperties.setEndpoint(url);
  303 + OpenAIClient openAIClient = azureOpenAiAutoConfiguration.openAIClient(connectionProperties);
  304 + // 获取 AzureOpenAiChatProperties 对象
  305 + AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
  306 + return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties, null, null);
  307 + }
  308 +
  309 + /**
272 310 * 可参考 {@link OpenAiAutoConfiguration}
273 311 */
274 312 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
... ... @@ -291,4 +329,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
291 329 return new StabilityAiImageModel(stabilityAiApi);
292 330 }
293 331  
  332 + // ========== 各种创建 EmbeddingModel 的方法 ==========
  333 +
  334 + /**
  335 + * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)}
  336 + */
  337 + private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
  338 + TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
  339 + connectionProperties.setApiKey(apiKey);
  340 + return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties);
  341 + }
  342 +
294 343 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.factory;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import org.springframework.ai.embedding.EmbeddingModel;
  5 +import org.springframework.ai.vectorstore.VectorStore;
  6 +
  7 +// TODO @xin:也放到 AiModelFactory 里面好了,后续改成 AiFactory
  8 +/**
  9 + * AI Vector 模型工厂的接口类
  10 + *
  11 + * @author xiaoxin
  12 + */
  13 +public interface AiVectorStoreFactory {
  14 +
  15 + /**
  16 + * 基于指定配置,获得 VectorStore 对象
  17 + * <p>
  18 + * 如果不存在,则进行创建
  19 + *
  20 + * @param embeddingModel 嵌入模型
  21 + * @param platform 平台
  22 + * @param apiKey API KEY
  23 + * @param url API URL
  24 + * @return VectorStore 对象
  25 + */
  26 + VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url);
  27 +
  28 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.factory;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +import cn.hutool.core.lang.func.Func0;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  8 +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
  9 +import org.springframework.ai.embedding.EmbeddingModel;
  10 +import org.springframework.ai.vectorstore.RedisVectorStore;
  11 +import org.springframework.ai.vectorstore.VectorStore;
  12 +import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
  13 +import redis.clients.jedis.JedisPooled;
  14 +
  15 +/**
  16 + * AI Vector 模型工厂的实现类
  17 + * 使用 redisVectorStore 实现 VectorStore
  18 + *
  19 + * @author xiaoxin
  20 + */
  21 +public class AiVectorStoreFactoryImpl implements AiVectorStoreFactory {
  22 +
  23 + @Override
  24 + public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) {
  25 + String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
  26 + return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
  27 + // TODO 芋艿 @xin 这两个配置取哪好呢
  28 + // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上
  29 + // TODO 回复:好的哈
  30 + String index = "default-index";
  31 + String prefix = "default:";
  32 + var config = RedisVectorStore.RedisVectorStoreConfig.builder()
  33 + .withIndexName(index)
  34 + .withPrefix(prefix)
  35 + .build();
  36 + RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
  37 + RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
  38 + new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
  39 + true);
  40 + redisVectorStore.afterPropertiesSet();
  41 + return redisVectorStore;
  42 + });
  43 + }
  44 +
  45 + private static String buildClientCacheKey(Class<?> clazz, Object... params) {
  46 + if (ArrayUtil.isEmpty(params)) {
  47 + return clazz.getName();
  48 + }
  49 + return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
  50 + }
  51 +
  52 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java
1 1 package cn.iocoder.yudao.framework.ai.core.model.deepseek;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import lombok.extern.slf4j.Slf4j;
5 6 import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
... ... @@ -70,12 +71,12 @@ public class DeepSeekChatModel implements ChatModel {
70 71 OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody();
71 72 if (chatCompletion == null) {
72 73 log.warn("No chat completion returned for prompt: {}", prompt);
73   - return new ChatResponse(List.of());
  74 + return new ChatResponse(ListUtil.of());
74 75 }
75 76 List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices();
76 77 if (choices == null) {
77 78 log.warn("No choices returned for prompt: {}", prompt);
78   - return new ChatResponse(List.of());
  79 + return new ChatResponse(ListUtil.of());
79 80 }
80 81  
81 82 // 2. 转换 ChatResponse 返回
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java
1 1 package cn.iocoder.yudao.framework.ai.core.model.xinghuo;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import lombok.extern.slf4j.Slf4j;
5 6 import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
... ... @@ -72,12 +73,12 @@ public class XingHuoChatModel implements ChatModel {
72 73 OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody();
73 74 if (chatCompletion == null) {
74 75 log.warn("No chat completion returned for prompt: {}", prompt);
75   - return new ChatResponse(List.of());
  76 + return new ChatResponse(ListUtil.of());
76 77 }
77 78 List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices();
78 79 if (choices == null) {
79 80 log.warn("No choices returned for prompt: {}", prompt);
80   - return new ChatResponse(List.of());
  81 + return new ChatResponse(ListUtil.of());
81 82 }
82 83  
83 84 // 2. 转换 ChatResponse 返回
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 5 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
6 6 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
7 7 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
  8 +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
8 9 import org.springframework.ai.chat.messages.*;
9 10 import org.springframework.ai.chat.prompt.ChatOptions;
10 11 import org.springframework.ai.ollama.api.OllamaOptions;
... ... @@ -35,6 +36,9 @@ public class AiUtils {
35 36 return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build();
36 37 case OPENAI:
37 38 return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  39 + case AZURE_OPENAI:
  40 + // TODO 芋艿:貌似没 model 字段???!
  41 + return AzureOpenAiChatOptions.builder().withDeploymentName(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
38 42 case OLLAMA:
39 43 return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
40 44 default:
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/audio/transcription/TongYiAudioTranscriptionModel.java
... ... @@ -16,6 +16,7 @@
16 16  
17 17 package com.alibaba.cloud.ai.tongyi.audio.transcription;
18 18  
  19 +import cn.hutool.core.collection.ListUtil;
19 20 import com.alibaba.cloud.ai.tongyi.audio.AudioTranscriptionModels;
20 21 import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionPrompt;
21 22 import com.alibaba.cloud.ai.tongyi.audio.transcription.api.AudioTranscriptionResponse;
... ... @@ -82,7 +83,7 @@ public class TongYiAudioTranscriptionModel
82 83 try {
83 84 transcriptionParam = TranscriptionParam.builder()
84 85 .model(AudioTranscriptionModels.Paraformer_V1)
85   - .fileUrls(List.of(String.valueOf(instructions.getURL())))
  86 + .fileUrls(ListUtil.of(String.valueOf(instructions.getURL())))
86 87 .build();
87 88 }
88 89 catch (IOException e) {
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/chat/TongYiChatModel.java
... ... @@ -16,6 +16,7 @@
16 16  
17 17 package com.alibaba.cloud.ai.tongyi.chat;
18 18  
  19 +import cn.hutool.core.collection.ListUtil;
19 20 import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
20 21 import com.alibaba.dashscope.aigc.conversation.ConversationParam;
21 22 import com.alibaba.dashscope.aigc.generation.Generation;
... ... @@ -207,7 +208,7 @@ public class TongYiChatModel extends
207 208 .getChoices()
208 209 .get(0)
209 210 ));
210   - return new ChatResponse(List.of(gen));
  211 + return new ChatResponse(ListUtil.of(gen));
211 212 })
212 213 )
213 214 .publishOn(Schedulers.parallel());
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/embedding/TongYiTextEmbeddingModel.java
... ... @@ -16,6 +16,7 @@
16 16  
17 17 package com.alibaba.cloud.ai.tongyi.embedding;
18 18  
  19 +import cn.hutool.core.collection.ListUtil;
19 20 import com.alibaba.cloud.ai.tongyi.common.exception.TongYiException;
20 21 import com.alibaba.cloud.ai.tongyi.metadata.TongYiTextEmbeddingResponseMetadata;
21 22 import com.alibaba.dashscope.embeddings.TextEmbedding;
... ... @@ -100,7 +101,7 @@ public class TongYiTextEmbeddingModel extends AbstractEmbeddingModel {
100 101  
101 102 return this.call(
102 103 new EmbeddingRequest(
103   - List.of(document.getFormattedContent(this.metadataMode)),
  104 + ListUtil.of(document.getFormattedContent(this.metadataMode)),
104 105 null)
105 106 ).getResults().stream()
106 107 .map(Embedding::getOutput)
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 - 2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.springframework.ai.autoconfigure.vectorstore.redis;
  17 +
  18 +import org.springframework.ai.embedding.EmbeddingModel;
  19 +import org.springframework.ai.vectorstore.RedisVectorStore;
  20 +import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
  21 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  22 +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  25 +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
  26 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  27 +import org.springframework.context.annotation.Bean;
  28 +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
  29 +import redis.clients.jedis.JedisPooled;
  30 +
  31 +/**
  32 + * TODO @xin 先拿 spring-ai 最新代码覆盖,1.0.0-M1 跟 redis 自动配置会冲突
  33 + *
  34 + * TODO 这个官方,有说啥时候 fix 哇?
  35 + * TODO 看着是列在1.0.0-M2版本
  36 + *
  37 + * @author Christian Tzolov
  38 + * @author Eddú Meléndez
  39 + */
  40 +@AutoConfiguration(after = RedisAutoConfiguration.class)
  41 +@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
  42 +@ConditionalOnBean(JedisConnectionFactory.class)
  43 +@EnableConfigurationProperties(RedisVectorStoreProperties.class)
  44 +public class RedisVectorStoreAutoConfiguration {
  45 +
  46 + @Bean
  47 + @ConditionalOnMissingBean
  48 + public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
  49 + JedisConnectionFactory jedisConnectionFactory) {
  50 +
  51 + var config = RedisVectorStoreConfig.builder()
  52 + .withIndexName(properties.getIndex())
  53 + .withPrefix(properties.getPrefix())
  54 + .build();
  55 +
  56 + return new RedisVectorStore(config, embeddingModel,
  57 + new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
  58 + properties.isInitializeSchema());
  59 + }
  60 +
  61 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java 0 → 100644
  1 +/*
  2 + * Copyright 2023 - 2024 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.springframework.ai.vectorstore;
  17 +
  18 +import org.slf4j.Logger;
  19 +import org.slf4j.LoggerFactory;
  20 +import org.springframework.ai.document.Document;
  21 +import org.springframework.ai.embedding.EmbeddingModel;
  22 +import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
  23 +import org.springframework.beans.factory.InitializingBean;
  24 +import org.springframework.util.Assert;
  25 +import org.springframework.util.CollectionUtils;
  26 +import redis.clients.jedis.JedisPooled;
  27 +import redis.clients.jedis.Pipeline;
  28 +import redis.clients.jedis.json.Path2;
  29 +import redis.clients.jedis.search.*;
  30 +import redis.clients.jedis.search.Schema.FieldType;
  31 +import redis.clients.jedis.search.schemafields.*;
  32 +import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
  33 +
  34 +import java.text.MessageFormat;
  35 +import java.util.*;
  36 +import java.util.function.Function;
  37 +import java.util.function.Predicate;
  38 +import java.util.stream.Collectors;
  39 +
  40 +/**
  41 + * The RedisVectorStore is for managing and querying vector data in a Redis database. It
  42 + * offers functionalities like adding, deleting, and performing similarity searches on
  43 + * documents.
  44 + *
  45 + * The store utilizes RedisJSON and RedisSearch to handle JSON documents and to index and
  46 + * search vector data. It supports various vector algorithms (e.g., FLAT, HSNW) for
  47 + * efficient similarity searches. Additionally, it allows for custom metadata fields in
  48 + * the documents to be stored alongside the vector and content data.
  49 + *
  50 + * This class requires a RedisVectorStoreConfig configuration object for initialization,
  51 + * which includes settings like Redis URI, index name, field names, and vector algorithms.
  52 + * It also requires an EmbeddingModel to convert documents into embeddings before storing
  53 + * them.
  54 + *
  55 + * @author Julien Ruaux
  56 + * @author Christian Tzolov
  57 + * @author Eddú Meléndez
  58 + * @see VectorStore
  59 + * @see RedisVectorStoreConfig
  60 + * @see EmbeddingModel
  61 + */
  62 +public class RedisVectorStore implements VectorStore, InitializingBean {
  63 +
  64 + public enum Algorithm {
  65 +
  66 + FLAT, HSNW
  67 +
  68 + }
  69 +
  70 + public record MetadataField(String name, FieldType fieldType) {
  71 +
  72 + public static MetadataField text(String name) {
  73 + return new MetadataField(name, FieldType.TEXT);
  74 + }
  75 +
  76 + public static MetadataField numeric(String name) {
  77 + return new MetadataField(name, FieldType.NUMERIC);
  78 + }
  79 +
  80 + public static MetadataField tag(String name) {
  81 + return new MetadataField(name, FieldType.TAG);
  82 + }
  83 +
  84 + }
  85 +
  86 + /**
  87 + * Configuration for the Redis vector store.
  88 + */
  89 + public static final class RedisVectorStoreConfig {
  90 +
  91 + private final String indexName;
  92 +
  93 + private final String prefix;
  94 +
  95 + private final String contentFieldName;
  96 +
  97 + private final String embeddingFieldName;
  98 +
  99 + private final Algorithm vectorAlgorithm;
  100 +
  101 + private final List<MetadataField> metadataFields;
  102 +
  103 + private RedisVectorStoreConfig() {
  104 + this(builder());
  105 + }
  106 +
  107 + private RedisVectorStoreConfig(Builder builder) {
  108 + this.indexName = builder.indexName;
  109 + this.prefix = builder.prefix;
  110 + this.contentFieldName = builder.contentFieldName;
  111 + this.embeddingFieldName = builder.embeddingFieldName;
  112 + this.vectorAlgorithm = builder.vectorAlgorithm;
  113 + this.metadataFields = builder.metadataFields;
  114 + }
  115 +
  116 + /**
  117 + * Start building a new configuration.
  118 + * @return The entry point for creating a new configuration.
  119 + */
  120 + public static Builder builder() {
  121 +
  122 + return new Builder();
  123 + }
  124 +
  125 + /**
  126 + * {@return the default config}
  127 + */
  128 + public static RedisVectorStoreConfig defaultConfig() {
  129 +
  130 + return builder().build();
  131 + }
  132 +
  133 + public static class Builder {
  134 +
  135 + private String indexName = DEFAULT_INDEX_NAME;
  136 +
  137 + private String prefix = DEFAULT_PREFIX;
  138 +
  139 + private String contentFieldName = DEFAULT_CONTENT_FIELD_NAME;
  140 +
  141 + private String embeddingFieldName = DEFAULT_EMBEDDING_FIELD_NAME;
  142 +
  143 + private Algorithm vectorAlgorithm = DEFAULT_VECTOR_ALGORITHM;
  144 +
  145 + private List<MetadataField> metadataFields = new ArrayList<>();
  146 +
  147 + private Builder() {
  148 + }
  149 +
  150 + /**
  151 + * Configures the Redis index name to use.
  152 + * @param name the index name to use
  153 + * @return this builder
  154 + */
  155 + public Builder withIndexName(String name) {
  156 + this.indexName = name;
  157 + return this;
  158 + }
  159 +
  160 + /**
  161 + * Configures the Redis key prefix to use (default: "embedding:").
  162 + * @param prefix the prefix to use
  163 + * @return this builder
  164 + */
  165 + public Builder withPrefix(String prefix) {
  166 + this.prefix = prefix;
  167 + return this;
  168 + }
  169 +
  170 + /**
  171 + * Configures the Redis content field name to use.
  172 + * @param name the content field name to use
  173 + * @return this builder
  174 + */
  175 + public Builder withContentFieldName(String name) {
  176 + this.contentFieldName = name;
  177 + return this;
  178 + }
  179 +
  180 + /**
  181 + * Configures the Redis embedding field name to use.
  182 + * @param name the embedding field name to use
  183 + * @return this builder
  184 + */
  185 + public Builder withEmbeddingFieldName(String name) {
  186 + this.embeddingFieldName = name;
  187 + return this;
  188 + }
  189 +
  190 + /**
  191 + * Configures the Redis vector algorithmto use.
  192 + * @param algorithm the vector algorithm to use
  193 + * @return this builder
  194 + */
  195 + public Builder withVectorAlgorithm(Algorithm algorithm) {
  196 + this.vectorAlgorithm = algorithm;
  197 + return this;
  198 + }
  199 +
  200 + public Builder withMetadataFields(MetadataField... fields) {
  201 + return withMetadataFields(Arrays.asList(fields));
  202 + }
  203 +
  204 + public Builder withMetadataFields(List<MetadataField> fields) {
  205 + this.metadataFields = fields;
  206 + return this;
  207 + }
  208 +
  209 + /**
  210 + * {@return the immutable configuration}
  211 + */
  212 + public RedisVectorStoreConfig build() {
  213 +
  214 + return new RedisVectorStoreConfig(this);
  215 + }
  216 +
  217 + }
  218 +
  219 + }
  220 +
  221 + private final boolean initializeSchema;
  222 +
  223 + public static final String DEFAULT_INDEX_NAME = "spring-ai-index";
  224 +
  225 + public static final String DEFAULT_CONTENT_FIELD_NAME = "content";
  226 +
  227 + public static final String DEFAULT_EMBEDDING_FIELD_NAME = "embedding";
  228 +
  229 + public static final String DEFAULT_PREFIX = "embedding:";
  230 +
  231 + public static final Algorithm DEFAULT_VECTOR_ALGORITHM = Algorithm.HSNW;
  232 +
  233 + private static final String QUERY_FORMAT = "%s=>[KNN %s @%s $%s AS %s]";
  234 +
  235 + private static final Path2 JSON_SET_PATH = Path2.of("$");
  236 +
  237 + private static final String JSON_PATH_PREFIX = "$.";
  238 +
  239 + private static final Logger logger = LoggerFactory.getLogger(RedisVectorStore.class);
  240 +
  241 + private static final Predicate<Object> RESPONSE_OK = Predicate.isEqual("OK");
  242 +
  243 + private static final Predicate<Object> RESPONSE_DEL_OK = Predicate.isEqual(1l);
  244 +
  245 + private static final String VECTOR_TYPE_FLOAT32 = "FLOAT32";
  246 +
  247 + private static final String EMBEDDING_PARAM_NAME = "BLOB";
  248 +
  249 + public static final String DISTANCE_FIELD_NAME = "vector_score";
  250 +
  251 + private static final String DEFAULT_DISTANCE_METRIC = "COSINE";
  252 +
  253 + private final JedisPooled jedis;
  254 +
  255 + private final EmbeddingModel embeddingModel;
  256 +
  257 + private final RedisVectorStoreConfig config;
  258 +
  259 + private FilterExpressionConverter filterExpressionConverter;
  260 +
  261 + public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis,
  262 + boolean initializeSchema) {
  263 +
  264 + Assert.notNull(config, "Config must not be null");
  265 + Assert.notNull(embeddingModel, "Embedding model must not be null");
  266 + this.initializeSchema = initializeSchema;
  267 +
  268 + this.jedis = jedis;
  269 + this.embeddingModel = embeddingModel;
  270 + this.config = config;
  271 + this.filterExpressionConverter = new RedisFilterExpressionConverter(this.config.metadataFields);
  272 + }
  273 +
  274 + public JedisPooled getJedis() {
  275 + return this.jedis;
  276 + }
  277 +
  278 + @Override
  279 + public void add(List<Document> documents) {
  280 + try (Pipeline pipeline = this.jedis.pipelined()) {
  281 + for (Document document : documents) {
  282 + var embedding = this.embeddingModel.embed(document);
  283 + document.setEmbedding(embedding);
  284 +
  285 + var fields = new HashMap<String, Object>();
  286 + fields.put(this.config.embeddingFieldName, embedding);
  287 + fields.put(this.config.contentFieldName, document.getContent());
  288 + fields.putAll(document.getMetadata());
  289 + pipeline.jsonSetWithEscape(key(document.getId()), JSON_SET_PATH, fields);
  290 + }
  291 + List<Object> responses = pipeline.syncAndReturnAll();
  292 + Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_OK)).findAny();
  293 + if (errResponse.isPresent()) {
  294 + String message = MessageFormat.format("Could not add document: {0}", errResponse.get());
  295 + if (logger.isErrorEnabled()) {
  296 + logger.error(message);
  297 + }
  298 + throw new RuntimeException(message);
  299 + }
  300 + }
  301 + }
  302 +
  303 + private String key(String id) {
  304 + return this.config.prefix + id;
  305 + }
  306 +
  307 + @Override
  308 + public Optional<Boolean> delete(List<String> idList) {
  309 + try (Pipeline pipeline = this.jedis.pipelined()) {
  310 + for (String id : idList) {
  311 + pipeline.jsonDel(key(id));
  312 + }
  313 + List<Object> responses = pipeline.syncAndReturnAll();
  314 + Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_DEL_OK)).findAny();
  315 + if (errResponse.isPresent()) {
  316 + if (logger.isErrorEnabled()) {
  317 + logger.error("Could not delete document: {}", errResponse.get());
  318 + }
  319 + return Optional.of(false);
  320 + }
  321 + return Optional.of(true);
  322 + }
  323 + }
  324 +
  325 + @Override
  326 + public List<Document> similaritySearch(SearchRequest request) {
  327 +
  328 + Assert.isTrue(request.getTopK() > 0, "The number of documents to returned must be greater than zero");
  329 + Assert.isTrue(request.getSimilarityThreshold() >= 0 && request.getSimilarityThreshold() <= 1,
  330 + "The similarity score is bounded between 0 and 1; least to most similar respectively.");
  331 +
  332 + String filter = nativeExpressionFilter(request);
  333 +
  334 + String queryString = String.format(QUERY_FORMAT, filter, request.getTopK(), this.config.embeddingFieldName,
  335 + EMBEDDING_PARAM_NAME, DISTANCE_FIELD_NAME);
  336 +
  337 + List<String> returnFields = new ArrayList<>();
  338 + this.config.metadataFields.stream().map(MetadataField::name).forEach(returnFields::add);
  339 + returnFields.add(this.config.embeddingFieldName);
  340 + returnFields.add(this.config.contentFieldName);
  341 + returnFields.add(DISTANCE_FIELD_NAME);
  342 + var embedding = toFloatArray(this.embeddingModel.embed(request.getQuery()));
  343 + Query query = new Query(queryString).addParam(EMBEDDING_PARAM_NAME, RediSearchUtil.toByteArray(embedding))
  344 + .returnFields(returnFields.toArray(new String[0]))
  345 + .setSortBy(DISTANCE_FIELD_NAME, true)
  346 + .dialect(2);
  347 +
  348 + SearchResult result = this.jedis.ftSearch(this.config.indexName, query);
  349 + return result.getDocuments()
  350 + .stream()
  351 + .filter(d -> similarityScore(d) >= request.getSimilarityThreshold())
  352 + .map(this::toDocument)
  353 + .toList();
  354 + }
  355 +
  356 + private Document toDocument(redis.clients.jedis.search.Document doc) {
  357 + var id = doc.getId().substring(this.config.prefix.length());
  358 + var content = doc.hasProperty(this.config.contentFieldName) ? doc.getString(this.config.contentFieldName)
  359 + : null;
  360 + Map<String, Object> metadata = this.config.metadataFields.stream()
  361 + .map(MetadataField::name)
  362 + .filter(doc::hasProperty)
  363 + .collect(Collectors.toMap(Function.identity(), doc::getString));
  364 + metadata.put(DISTANCE_FIELD_NAME, 1 - similarityScore(doc));
  365 + return new Document(id, content, metadata);
  366 + }
  367 +
  368 + private float similarityScore(redis.clients.jedis.search.Document doc) {
  369 + return (2 - Float.parseFloat(doc.getString(DISTANCE_FIELD_NAME))) / 2;
  370 + }
  371 +
  372 + private String nativeExpressionFilter(SearchRequest request) {
  373 + if (request.getFilterExpression() == null) {
  374 + return "*";
  375 + }
  376 + return "(" + this.filterExpressionConverter.convertExpression(request.getFilterExpression()) + ")";
  377 + }
  378 +
  379 + @Override
  380 + public void afterPropertiesSet() {
  381 +
  382 + if (!this.initializeSchema) {
  383 + return;
  384 + }
  385 +
  386 + // If index already exists don't do anything
  387 + if (this.jedis.ftList().contains(this.config.indexName)) {
  388 + return;
  389 + }
  390 +
  391 + String response = this.jedis.ftCreate(this.config.indexName,
  392 + FTCreateParams.createParams().on(IndexDataType.JSON).addPrefix(this.config.prefix), schemaFields());
  393 + if (!RESPONSE_OK.test(response)) {
  394 + String message = MessageFormat.format("Could not create index: {0}", response);
  395 + throw new RuntimeException(message);
  396 + }
  397 + }
  398 +
  399 + private Iterable<SchemaField> schemaFields() {
  400 + Map<String, Object> vectorAttrs = new HashMap<>();
  401 + vectorAttrs.put("DIM", this.embeddingModel.dimensions());
  402 + vectorAttrs.put("DISTANCE_METRIC", DEFAULT_DISTANCE_METRIC);
  403 + vectorAttrs.put("TYPE", VECTOR_TYPE_FLOAT32);
  404 + List<SchemaField> fields = new ArrayList<>();
  405 + fields.add(TextField.of(jsonPath(this.config.contentFieldName)).as(this.config.contentFieldName).weight(1.0));
  406 + fields.add(VectorField.builder()
  407 + .fieldName(jsonPath(this.config.embeddingFieldName))
  408 + .algorithm(vectorAlgorithm())
  409 + .attributes(vectorAttrs)
  410 + .as(this.config.embeddingFieldName)
  411 + .build());
  412 +
  413 + if (!CollectionUtils.isEmpty(this.config.metadataFields)) {
  414 + for (MetadataField field : this.config.metadataFields) {
  415 + fields.add(schemaField(field));
  416 + }
  417 + }
  418 + return fields;
  419 + }
  420 +
  421 + private SchemaField schemaField(MetadataField field) {
  422 + String fieldName = jsonPath(field.name);
  423 + switch (field.fieldType) {
  424 + case NUMERIC:
  425 + return NumericField.of(fieldName).as(field.name);
  426 + case TAG:
  427 + return TagField.of(fieldName).as(field.name);
  428 + case TEXT:
  429 + return TextField.of(fieldName).as(field.name);
  430 + default:
  431 + throw new IllegalArgumentException(
  432 + MessageFormat.format("Field {0} has unsupported type {1}", field.name, field.fieldType));
  433 + }
  434 + }
  435 +
  436 + private VectorAlgorithm vectorAlgorithm() {
  437 + if (config.vectorAlgorithm == Algorithm.HSNW) {
  438 + return VectorAlgorithm.HNSW;
  439 + }
  440 + return VectorAlgorithm.FLAT;
  441 + }
  442 +
  443 + private String jsonPath(String field) {
  444 + return JSON_PATH_PREFIX + field;
  445 + }
  446 +
  447 + private static float[] toFloatArray(List<Double> embeddingDouble) {
  448 + float[] embeddingFloat = new float[embeddingDouble.size()];
  449 + int i = 0;
  450 + for (Double d : embeddingDouble) {
  451 + embeddingFloat[i++] = d.floatValue();
  452 + }
  453 + return embeddingFloat;
  454 + }
  455 +
  456 +}
0 457 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/AzureOpenAIChatModelTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import com.azure.ai.openai.OpenAIClient;
  4 +import com.azure.ai.openai.OpenAIClientBuilder;
  5 +import com.azure.core.credential.AzureKeyCredential;
  6 +import com.azure.core.util.ClientOptions;
  7 +import org.junit.jupiter.api.Disabled;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
  10 +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
  11 +import org.springframework.ai.chat.messages.Message;
  12 +import org.springframework.ai.chat.messages.SystemMessage;
  13 +import org.springframework.ai.chat.messages.UserMessage;
  14 +import org.springframework.ai.chat.model.ChatResponse;
  15 +import org.springframework.ai.chat.prompt.Prompt;
  16 +import reactor.core.publisher.Flux;
  17 +
  18 +import java.util.ArrayList;
  19 +import java.util.List;
  20 +
  21 +import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
  22 +
  23 +/**
  24 + * {@link AzureOpenAiChatModel} 集成测试
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +public class AzureOpenAIChatModelTests {
  29 +
  30 + private final OpenAIClient openAiApi = (new OpenAIClientBuilder())
  31 + .endpoint("https://eastusprejade.openai.azure.com")
  32 + .credential(new AzureKeyCredential("xxx"))
  33 + .clientOptions((new ClientOptions()).setApplicationId("spring-ai"))
  34 + .buildClient();
  35 + private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
  36 + AzureOpenAiChatOptions.builder().withDeploymentName(DEFAULT_DEPLOYMENT_NAME).build());
  37 +
  38 + @Test
  39 + @Disabled
  40 + public void testCall() {
  41 + // 准备参数
  42 + List<Message> messages = new ArrayList<>();
  43 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  44 + messages.add(new UserMessage("1 + 1 = ?"));
  45 +
  46 + // 调用
  47 + ChatResponse response = chatModel.call(new Prompt(messages));
  48 + // 打印结果
  49 + System.out.println(response);
  50 + System.out.println(response.getResult().getOutput());
  51 + }
  52 +
  53 + @Test
  54 + @Disabled
  55 + public void testStream() {
  56 + // 准备参数
  57 + List<Message> messages = new ArrayList<>();
  58 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  59 + messages.add(new UserMessage("1 + 1 = ?"));
  60 +
  61 + // 调用
  62 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  63 + // 打印结果
  64 + flux.doOnNext(response -> {
  65 +// System.out.println(response);
  66 + System.out.println(response.getResult().getOutput());
  67 + }).then().block();
  68 + }
  69 +
  70 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java
1 1 package cn.iocoder.yudao.framework.ai.chat;
2 2  
3   -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
4 3 import org.junit.jupiter.api.Disabled;
5 4 import org.junit.jupiter.api.Test;
6 5 import org.springframework.ai.chat.messages.Message;
... ... @@ -17,7 +16,7 @@ import java.util.ArrayList;
17 16 import java.util.List;
18 17  
19 18 /**
20   - * {@link XingHuoChatModel} 集成测试
  19 + * {@link OpenAiChatModel} 集成测试
21 20 *
22 21 * @author 芋道源码
23 22 */
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoApiTests.java
1 1 package cn.iocoder.yudao.framework.ai.music;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
3 4 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
4 5 import org.junit.jupiter.api.Disabled;
5 6 import org.junit.jupiter.api.Test;
... ... @@ -66,7 +67,7 @@ public class SunoApiTests {
66 67 String id = "584729e5-0fe9-4157-86da-1b4803ff42bf";
67 68  
68 69 // 调用方法
69   - List<SunoApi.MusicData> musicList = sunoApi.getMusicList(List.of(id));
  70 + List<SunoApi.MusicData> musicList = sunoApi.getMusicList(ListUtil.of(id));
70 71 // 打印结果
71 72 System.out.println(musicList);
72 73 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
... ... @@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModel
11 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
12 12 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
13 13 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
  14 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  15 +import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
14 16 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
15 17 import org.flowable.common.engine.impl.db.SuspensionState;
16 18 import org.flowable.engine.repository.Deployment;
... ... @@ -52,7 +54,7 @@ public interface BpmModelConvert {
52 54 BpmModelMetaInfoVO metaInfo = buildMetaInfo(model);
53 55 BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
54 56 if (ArrayUtil.isNotEmpty(bpmnBytes)) {
55   - modelVO.setBpmnXml(new String(bpmnBytes));
  57 + modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
56 58 }
57 59 return modelVO;
58 60 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
5 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
6 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  6 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
7 7 import lombok.Setter;
8 8 import org.flowable.bpmn.model.Activity;
9 9 import org.flowable.engine.delegate.DelegateExecution;
... ... @@ -50,14 +50,18 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
50 50 super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
51 51  
52 52 // 第二步,获取任务的所有处理人
53   - Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
54   - if (CollUtil.isEmpty(assigneeUserIds)) {
55   - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
56   - // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
57   - // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
58   - assigneeUserIds = SetUtils.asSet((Long) null);
  53 + @SuppressWarnings("unchecked")
  54 + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
  55 + if (assigneeUserIds == null) {
  56 + assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
  57 + execution.setVariable(super.collectionVariable, assigneeUserIds);
  58 + if (CollUtil.isEmpty(assigneeUserIds)) {
  59 + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
  60 + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
  61 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
  62 + assigneeUserIds = SetUtils.asSet((Long) null);
  63 + }
59 64 }
60   - execution.setVariable(super.collectionVariable, assigneeUserIds);
61 65 return assigneeUserIds.size();
62 66 }
63 67  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
... ... @@ -43,14 +43,18 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
43 43 super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
44 44  
45 45 // 第二步,获取任务的所有处理人
46   - Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
47   - if (CollUtil.isEmpty(assigneeUserIds)) {
48   - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
49   - // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
50   - // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
51   - assigneeUserIds = SetUtils.asSet((Long) null);
  46 + @SuppressWarnings("unchecked")
  47 + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
  48 + if (assigneeUserIds == null) {
  49 + assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
  50 + execution.setVariable(super.collectionVariable, assigneeUserIds);
  51 + if (CollUtil.isEmpty(assigneeUserIds)) {
  52 + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
  53 + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
  54 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
  55 + assigneeUserIds = SetUtils.asSet((Long) null);
  56 + }
52 57 }
53   - execution.setVariable(super.collectionVariable, assigneeUserIds);
54 58 return assigneeUserIds.size();
55 59 }
56 60  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -211,7 +211,14 @@ public class BpmnModelUtils {
211 211 return null;
212 212 }
213 213 BpmnXMLConverter converter = new BpmnXMLConverter();
214   - return new String(converter.convertToXML(model));
  214 + return StrUtil.utf8Str(converter.convertToXML(model));
  215 + }
  216 +
  217 + public static String getBpmnXml(byte[] bpmnBytes) {
  218 + if (ArrayUtil.isEmpty(bpmnBytes)) {
  219 + return null;
  220 + }
  221 + return StrUtil.utf8Str(bpmnBytes);
215 222 }
216 223  
217 224 // ========== 遍历相关的方法 ==========
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -65,6 +65,7 @@ public class BpmModelServiceImpl implements BpmModelService {
65 65 @Override
66 66 public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) {
67 67 ModelQuery modelQuery = repositoryService.createModelQuery();
  68 + modelQuery.modelTenantId(FlowableUtils.getTenantId());
68 69 if (StrUtil.isNotBlank(pageVO.getKey())) {
69 70 modelQuery.modelKey(pageVO.getKey());
70 71 }
... ... @@ -80,7 +81,6 @@ public class BpmModelServiceImpl implements BpmModelService {
80 81 return PageResult.empty(count);
81 82 }
82 83 List<Model> models = modelQuery
83   - .modelTenantId(FlowableUtils.getTenantId())
84 84 .orderByCreateTime().desc()
85 85 .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
86 86 return new PageResult<>(models, count);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -106,7 +106,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
106 106 }
107 107 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
108 108 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
109   - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
  109 + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
110 110 }
111 111 long count = taskQuery.count();
112 112 if (count == 0) {
... ... @@ -128,7 +128,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
128 128 }
129 129 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
130 130 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
131   - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
  131 + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
132 132 }
133 133 // 执行查询
134 134 long count = taskQuery.count();
... ... @@ -150,7 +150,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
150 150 }
151 151 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
152 152 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
153   - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
  153 + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
154 154 }
155 155 // 执行查询
156 156 long count = taskQuery.count();
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
... ... @@ -92,7 +92,7 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
92 92 List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>()
93 93 .select("contract_id, SUM(price) AS total_price")
94 94 .in("audit_status", CrmAuditStatusEnum.DRAFT.getStatus(), // 草稿 + 审批中 + 审批通过
95   - CrmAuditStatusEnum.PROCESS, CrmAuditStatusEnum.APPROVE.getStatus())
  95 + CrmAuditStatusEnum.PROCESS.getStatus(), CrmAuditStatusEnum.APPROVE.getStatus())
96 96 .groupBy("contract_id")
97 97 .in("contract_id", contractIds));
98 98 // 获得金额
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java
1 1 package cn.iocoder.yudao.module.crm.service.statistics;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.collection.ListUtil;
4 5 import cn.hutool.core.util.ObjUtil;
5 6 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
... ... @@ -106,7 +107,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
106 107 private List<Long> getUserIds(CrmStatisticsPerformanceReqVO reqVO) {
107 108 // 情况一:选中某个用户
108 109 if (ObjUtil.isNotNull(reqVO.getUserId())) {
109   - return List.of(reqVO.getUserId());
  110 + return ListUtil.of(reqVO.getUserId());
110 111 }
111 112 // 情况二:选中某个部门
112 113 // 2.1 获得部门列表
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApi.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.api.logger;
3 3 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
4 4  
5 5 import jakarta.validation.Valid;
  6 +import org.springframework.scheduling.annotation.Async;
6 7  
7 8 /**
8 9 * API 访问日志的 API 接口
... ... @@ -18,4 +19,14 @@ public interface ApiAccessLogApi {
18 19 */
19 20 void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);
20 21  
  22 + /**
  23 + * 【异步】创建 API 访问日志
  24 + *
  25 + * @param createDTO 访问日志 DTO
  26 + */
  27 + @Async
  28 + default void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) {
  29 + createApiAccessLog(createDTO);
  30 + }
  31 +
21 32 }
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApi.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.api.logger;
3 3 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
4 4  
5 5 import jakarta.validation.Valid;
  6 +import org.springframework.scheduling.annotation.Async;
6 7  
7 8 /**
8 9 * API 错误日志的 API 接口
... ... @@ -18,4 +19,14 @@ public interface ApiErrorLogApi {
18 19 */
19 20 void createApiErrorLog(@Valid ApiErrorLogCreateReqDTO createDTO);
20 21  
  22 + /**
  23 + * 【异步】创建 API 异常日志
  24 + *
  25 + * @param createDTO 异常日志 DTO
  26 + */
  27 + @Async
  28 + default void createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) {
  29 + createApiErrorLog(createDTO);
  30 + }
  31 +
21 32 }
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
... ... @@ -22,7 +22,7 @@ public interface ErrorCodeConstants {
22 22 ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改");
23 23 ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改");
24 24 ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确");
25   - ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在");
  25 + ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在,注意 Bean 默认首字母小写");
26 26 ErrorCode JOB_HANDLER_BEAN_TYPE_ERROR = new ErrorCode(1_001_001_007, "定时任务的处理器 Bean 类型不正确,未实现 JobHandler 接口");
27 27  
28 28 // ========== API 错误日志 1-001-002-000 ==========
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
... ... @@ -14,7 +14,6 @@ public enum CodegenFrontTypeEnum {
14 14  
15 15 VUE2(10), // Vue2 Element UI 标准模版
16 16 VUE3(20), // Vue3 Element Plus 标准模版
17   - VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版
18 17 VUE3_VBEN(30), // Vue3 VBEN 模版
19 18 ;
20 19  
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
... ... @@ -91,7 +91,7 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
91 91 * 开启 VirtualStyle 模式
92 92 */
93 93 private void enableVirtualStyleEndpoint() {
94   - if (StrUtil.containsAll(config.getEndpoint(),
  94 + if (StrUtil.containsAny(config.getEndpoint(),
95 95 S3FileClientConfig.ENDPOINT_TENCENT, // 腾讯云 https://cloud.tencent.com/document/product/436/41284
96 96 S3FileClientConfig.ENDPOINT_VOLCES)) { // 火山云 https://www.volcengine.com/docs/6349/1288493
97 97 client.enableVirtualStyleEndpoint();
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
... ... @@ -135,15 +135,6 @@ public class CodegenEngine {
135 135 vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
136 136 .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
137 137 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
138   - // Vue3 Schema 模版
139   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/data.ts"),
140   - vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
141   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/index.vue"),
142   - vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
143   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
144   - vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
145   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
146   - vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
147 138 // Vue3 vben 模版
148 139 .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
149 140 vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
... ... @@ -496,10 +487,6 @@ public class CodegenEngine {
496 487 "src/" + path;
497 488 }
498 489  
499   - private static String vue3SchemaTemplatePath(String path) {
500   - return "codegen/vue3_schema/" + path + ".vm";
501   - }
502   -
503 490 private static String vue3VbenTemplatePath(String path) {
504 491 return "codegen/vue3_vben/" + path + ".vm";
505 492 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java
... ... @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
14 14 import jakarta.annotation.Resource;
15 15 import lombok.extern.slf4j.Slf4j;
16 16 import org.quartz.SchedulerException;
  17 +import org.springframework.beans.factory.NoSuchBeanDefinitionException;
17 18 import org.springframework.stereotype.Service;
18 19 import org.springframework.transaction.annotation.Transactional;
19 20 import org.springframework.validation.annotation.Validated;
... ... @@ -91,13 +92,15 @@ public class JobServiceImpl implements JobService {
91 92 }
92 93  
93 94 private void validateJobHandlerExists(String handlerName) {
94   - Object handler = SpringUtil.getBean(handlerName);
95   - if (handler == null) {
  95 + try {
  96 + Object handler = SpringUtil.getBean(handlerName);
  97 + assert handler != null;
  98 + if (!(handler instanceof JobHandler)) {
  99 + throw exception(JOB_HANDLER_BEAN_TYPE_ERROR);
  100 + }
  101 + } catch (NoSuchBeanDefinitionException e) {
96 102 throw exception(JOB_HANDLER_BEAN_NOT_EXISTS);
97 103 }
98   - if (!(handler instanceof JobHandler)) {
99   - throw exception(JOB_HANDLER_BEAN_TYPE_ERROR);
100   - }
101 104 }
102 105  
103 106 @Override
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm
... ... @@ -286,6 +286,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
286 286 // 校验存在
287 287 validate${subSimpleClassName}Exists(${subClassNameVar}.getId());
288 288 // 更新
  289 + ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新
289 290 ${subClassNameVars.get($index)}Mapper.updateById(${subClassNameVar});
290 291 }
291 292  
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm
... ... @@ -64,12 +64,11 @@
64 64 <el-checkbox
65 65 v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
66 66 :key="dict.value"
67   - :label="dict.value"
68   - >
69   - {{ dict.label }}
70   - </el-checkbox>
  67 + :label="dict.label"
  68 + :value="dict.value"
  69 + />
71 70 #else##没数据字典
72   - <el-checkbox>请选择字典生成</el-checkbox>
  71 + <el-checkbox label="请选择字典生成" />
73 72 #end
74 73 </el-checkbox-group>
75 74 </el-form-item>
... ... @@ -85,7 +84,7 @@
85 84 {{ dict.label }}
86 85 </el-radio>
87 86 #else##没数据字典
88   - <el-radio label="1">请选择字典生成</el-radio>
  87 + <el-radio value="1">请选择字典生成</el-radio>
89 88 #end
90 89 </el-radio-group>
91 90 </el-form-item>
... ...