mirror of
https://github.com/mfish-qf/mfish-nocode.git
synced 2026-08-29 03:43:14 +08:00
feat: 增加多模型配置界面,AI服务独立剥离
This commit is contained in:
@@ -71,3 +71,4 @@
|
||||
/mf-common/mf-common-prometheus/target/
|
||||
/mf-common/mf-common-demo/target/
|
||||
/mf-api/mf-demo-api/target/
|
||||
/mf-api/mf-ai-api/target/
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
| `mf_nocode.sql` | 低代码中心数据库 |
|
||||
| `mf_workflow.sql` | 工作流数据库 |
|
||||
| `mf_demo.sql` | 样例中心数据库(非必须) |
|
||||
| `mf_ai.sql` | AI中心数据库 |
|
||||
| `mfish_nocode.sql` | 单实例数据库<br />`单实例启动只需要执行这个脚本` |
|
||||
|
||||
```
|
||||
|
||||
@@ -12,6 +12,7 @@ postgresql数据库已完全适配,有需要请联系作者...
|
||||
| `mf_nocode.sql` | 低代码中心数据库 |
|
||||
| `mf_workflow.sql` | 工作流数据库 |
|
||||
| `mf_demo.sql` | 样例中心数据库(非必须) |
|
||||
| `mf_ai.sql` | AI中心数据库 |
|
||||
| `mfish_nocode.sql` | 单实例数据库<br />`单实例启动只需要执行这个脚本` |
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
-- ----------------------------
|
||||
-- AI中心数据库 (摸鱼小助手 AI相关配置库)
|
||||
-- ----------------------------
|
||||
DROP DATABASE IF EXISTS `mf_ai`;
|
||||
CREATE DATABASE `mf_ai` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
USE `mf_ai`;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ai_model_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `ai_model_config`;
|
||||
CREATE TABLE `ai_model_config` (
|
||||
`id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '租户ID',
|
||||
`provider` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '提供者: openai/ollama/deepseek/zhipuai/anthropic',
|
||||
`model_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '模型名称: gpt-4o, qwen3:8b, deepseek-v3 等',
|
||||
`api_key` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'API密钥(加密存储)',
|
||||
`base_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'API基础地址',
|
||||
`max_tokens` int NULL DEFAULT 4096 COMMENT '最大token数',
|
||||
`temperature` double NULL DEFAULT 0.7 COMMENT '温度参数',
|
||||
`top_p` double NULL DEFAULT NULL COMMENT 'top_p参数',
|
||||
`completions_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '补全项路径',
|
||||
`enabled` tinyint NULL DEFAULT 1 COMMENT '是否启用 1启用 0禁用',
|
||||
`sort_order` int NULL DEFAULT 0 COMMENT '排序(决定fallback优先级)',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI模型配置信息' ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
+13
-13
@@ -31,33 +31,33 @@ CREATE TABLE `config_info` (
|
||||
`encrypted_data_key` varchar(1024) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL DEFAULT '' COMMENT '密钥',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_configinfo_datagrouptenant`(`data_id`, `group_id`, `tenant_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin COMMENT = 'config_info' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin COMMENT = 'config_info' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of config_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude:\n - org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration\n data:\n redis:\n host: localhost\n port: 6379\n password: \"1qaz@WSX\"\n database: 5\n timeout: 10s\n lettuce:\n pool:\n # 连接池中的最小空闲连接\n min-idle: 0\n # 连接池中的最大空闲连接\n max-idle: 8\n # 连接池的最大数据库连接数\n max-active: 8\n # #连接池最大阻塞等待时间(使用负值表示没有限制)\n max-wait: -1ms\n servlet:\n multipart:\n # 单个文件大小\n max-file-size: 1200MB\n # 设置总上传的文件大小\n max-request-size: 1200MB\n ai:\n openai:\n api-key: sk-or-v1-b4**************************************a99b135dae3b\n base-url: https://openrouter.ai/api\n chat:\n options:\n model: meta-llama/llama-3.3-70b-instruct:free\n max-tokens: 1000\n temperature: 0.7\n ollama:\n base-url: http://localhost:11434\n chat:\n model: qwen3:8b\n# feign 配置\nfeign:\n sentinel:\n enabled: true\n okhttp: \n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: false\n response:\n enabled: false\n\n# 暴露监控端点\nmanagement:\n health:\n sentinel:\n # 关闭健康检查,避免日志刷屏\n enabled: false\n endpoints:\n web:\n exposure:\n include: \n - info\n - health\n - metrics\n - prometheus\n endpoint:\n health:\n show-details: always\nlogging:\n config: classpath:logback-logstash.xml\nlog:\n level: info\n maxSize: 30MB\n# 临时缓存存储时间(单位:天)\ncache:\n temp:\n time: 7\nDBConnect:\n password:\n privateKey: 54f0e36b98cf63c3bc6185b61c6e4f2a6e1df3bdeb293a50d7b0bc66881c2419\nswagger:\n title: \'摸鱼低代码平台\'\n enabled: true\n description: 摸鱼框架接口文档\n license: Powered By mfish\n licenseUrl: http://www.mfish.com.cn\n version: 版本号:V2.1.1\n termsOfServiceUrl: http://www.mfish.com.cn\n ignoreServers: mf-gateway,mf-monitor\n contact:\n name: mfish\n email: qiufeng9862@qq.com\n url: http://www.mfish.com.cn\nspringdoc:\n swagger-ui:\n enabled: true\n path: /swagger-ui.html\n tags-sorter: alpha\n operations-sorter: alpha\n default-models-expand-depth: -1\n docExpansion: none\n api-docs:\n enabled: true\n path: /v3/api-docs\n use-management-port: false\n default-flat-param-object: true', '832d798b554120710e6615813bf9d699', '2026-02-08 10:36:25', '2026-02-08 10:36:47', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (2, 'application-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude:\n - org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration\n data:\n redis:\n host: localhost\n port: 6379\n password: \"1qaz@WSX\"\n database: 5\n timeout: 10s\n lettuce:\n pool:\n # 连接池中的最小空闲连接\n min-idle: 0\n # 连接池中的最大空闲连接\n max-idle: 8\n # 连接池的最大数据库连接数\n max-active: 8\n # #连接池最大阻塞等待时间(使用负值表示没有限制)\n max-wait: -1ms\n servlet:\n multipart:\n # 单个文件大小\n max-file-size: 1200MB\n # 设置总上传的文件大小\n max-request-size: 1200MB\n ai:\n openai:\n api-key: sk-or-v1-b4**************************************a99b135dae3b\n base-url: https://openrouter.ai/api\n chat:\n options:\n model: meta-llama/llama-3.3-70b-instruct:free\n max-tokens: 1000\n temperature: 0.7\n ollama:\n base-url: http://localhost:11434\n chat:\n model: qwen3:8b\n# feign 配置\nfeign:\n sentinel:\n enabled: true\n okhttp: \n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: false\n response:\n enabled: false\n\n# 暴露监控端点\nmanagement:\n health:\n sentinel:\n # 关闭健康检查,避免日志刷屏\n enabled: false\n endpoints:\n web:\n exposure:\n include: \n - info\n - health\n - metrics\n - prometheus\n endpoint:\n health:\n show-details: always\nlogging:\n config: classpath:logback-logstash.xml\nlog:\n level: info\n maxSize: 30MB\n# 临时缓存存储时间(单位:天)\ncache:\n temp:\n time: 7\nDBConnect:\n password:\n privateKey: 54f0e36b98cf63c3bc6185b61c6e4f2a6e1df3bdeb293a50d7b0bc66881c2419\nswagger:\n title: \'摸鱼低代码平台\'\n enabled: true\n description: 摸鱼框架接口文档\n license: Powered By mfish\n licenseUrl: http://www.mfish.com.cn\n version: 版本号:V2.1.1\n termsOfServiceUrl: http://www.mfish.com.cn\n ignoreServers: mf-gateway,mf-monitor\n contact:\n name: mfish\n email: qiufeng9862@qq.com\n url: http://www.mfish.com.cn\nspringdoc:\n swagger-ui:\n enabled: true\n path: /swagger-ui.html\n tags-sorter: alpha\n operations-sorter: alpha\n default-models-expand-depth: -1\n docExpansion: none\n api-docs:\n enabled: true\n path: /v3/api-docs\n use-management-port: false\n default-flat-param-object: true', '832d798b554120710e6615813bf9d699', '2026-02-08 10:36:25', '2026-02-08 10:36:47', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (3, 'mf-oauth-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼认证中心\'\r\n prefix: /oauth2\r\n# mybatis配置\r\nmybatis-plus:\r\n # 搜索指定包别名\r\n type-aliases-package: cn.com.mfish.oauth.mapper\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/**/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n thymeleaf:\r\n cache: false\r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_oauth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\noauth2:\r\n expire:\r\n code: 180\r\n token: 21600\r\n refreshToken: 604800\r\n login:\r\n mutex: false\r\n user:\r\n autoCreate: false\r\n token:\r\n sm4key: 143be1ae6ee10b048f7e441cec2a9803\r\nwechat:\r\n miniapp:\r\n appId: wx*************e34a3\r\n secret: a65e*************fa222\r\ngitee:\r\n clientId: a83ea66522fef5f382c6aaffa875c7e87b21d3933277c44a326cd3407fc0b651\r\n clientSecret: 3ebdce2afcb4a7df9b16355f60d898d452f4d9cf8ad1ab8279bb3bb465e614db\r\n redirectUri: http://localhost:5186/giteeOauth2\r\n bindUri: http://localhost:5186/tenant/info/1?callback=gitee\r\ngithub:\r\n clientId: Ov23li7oH0Djw3ZEJCoJ\r\n clientSecret: ada9a510b3bde542e7510142f07d32a5f6bed131\r\n redirectUri: http://localhost:5186/githubOauth2\r\n bindUri: http://localhost:5186/tenant/info/1?callback=github', '34d1ab06f05a4c251d9cb4ce7547badc', '2026-02-08 10:37:25', '2026-02-08 10:37:25', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (4, 'mf-oauth-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼认证中心\'\r\n prefix: /oauth2\r\n# mybatis配置\r\nmybatis-plus:\r\n # 搜索指定包别名\r\n type-aliases-package: cn.com.mfish.oauth.mapper\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/**/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n thymeleaf:\r\n cache: false\r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_oauth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\noauth2:\r\n expire:\r\n code: 180\r\n token: 21600\r\n refreshToken: 604800\r\n login:\r\n mutex: false\r\n user:\r\n autoCreate: false\r\n token:\r\n sm4key: 143be1ae6ee10b048f7e441cec2a9803\r\nwechat:\r\n miniapp:\r\n appId: wx*************e34a3\r\n secret: a65e*************fa222\r\ngitee:\r\n clientId: a83ea66522fef5f382c6aaffa875c7e87b21d3933277c44a326cd3407fc0b651\r\n clientSecret: 3ebdce2afcb4a7df9b16355f60d898d452f4d9cf8ad1ab8279bb3bb465e614db\r\n redirectUri: http://localhost:5186/giteeOauth2\r\n bindUri: http://localhost:5186/tenant/info/1?callback=gitee\r\ngithub:\r\n clientId: Ov23li7oH0Djw3ZEJCoJ\r\n clientSecret: ada9a510b3bde542e7510142f07d32a5f6bed131\r\n redirectUri: http://localhost:5186/githubOauth2\r\n bindUri: http://localhost:5186/tenant/info/1?callback=github', '34d1ab06f05a4c251d9cb4ce7547badc', '2026-02-08 10:37:25', '2026-02-08 10:37:25', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude:\n - org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration\n - org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration\n data:\n redis:\n host: localhost\n port: 6379\n password: \"1qaz@WSX\"\n database: 5\n timeout: 10s\n lettuce:\n pool:\n # 连接池中的最小空闲连接\n min-idle: 0\n # 连接池中的最大空闲连接\n max-idle: 8\n # 连接池的最大数据库连接数\n max-active: 8\n # #连接池最大阻塞等待时间(使用负值表示没有限制)\n max-wait: -1ms\n servlet:\n multipart:\n # 单个文件大小\n max-file-size: 1200MB\n # 设置总上传的文件大小\n max-request-size: 1200MB\n# feign 配置\nfeign:\n sentinel:\n enabled: true\n okhttp: \n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: false\n response:\n enabled: false\n\n# 暴露监控端点\nmanagement:\n metrics:\n enable:\n all: false\n health:\n sentinel:\n # 关闭健康检查,避免日志刷屏\n enabled: false\n endpoints:\n web:\n exposure:\n include: \n - info\n - health\n - metrics\n - prometheus\n endpoint:\n health:\n show-details: always\nlogging:\n config: classpath:logback-logstash.xml\nlog:\n level: info\n maxSize: 30MB\n# 临时缓存存储时间(单位:天)\ncache:\n temp:\n time: 7\nDBConnect:\n password:\n privateKey: 54f0e36b98cf63c3bc6185b61c6e4f2a6e1df3bdeb293a50d7b0bc66881c2419\nswagger:\n title: \'摸鱼低代码平台\'\n enabled: true\n description: 摸鱼框架接口文档\n license: Powered By mfish\n licenseUrl: http://www.mfish.com.cn\n version: 版本号:V2.1.1\n termsOfServiceUrl: http://www.mfish.com.cn\n ignoreServers: mf-gateway,mf-monitor\n contact:\n name: mfish\n email: qiufeng9862@qq.com\n url: http://www.mfish.com.cn\nspringdoc:\n swagger-ui:\n enabled: true\n path: /swagger-ui.html\n tags-sorter: alpha\n operations-sorter: alpha\n default-models-expand-depth: -1\n docExpansion: none\n api-docs:\n enabled: true\n path: /v3/api-docs\n use-management-port: false\n default-flat-param-object: true', '4027bdc69421ab032637384f95ae1f2c', '2026-02-08 10:36:25', '2026-07-09 23:21:21', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (2, 'application-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude:\n - org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration\n - org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration\n data:\n redis:\n host: localhost\n port: 6379\n password: \"1qaz@WSX\"\n database: 5\n timeout: 10s\n lettuce:\n pool:\n # 连接池中的最小空闲连接\n min-idle: 0\n # 连接池中的最大空闲连接\n max-idle: 8\n # 连接池的最大数据库连接数\n max-active: 8\n # #连接池最大阻塞等待时间(使用负值表示没有限制)\n max-wait: -1ms\n servlet:\n multipart:\n # 单个文件大小\n max-file-size: 1200MB\n # 设置总上传的文件大小\n max-request-size: 1200MB\n# feign 配置\nfeign:\n sentinel:\n enabled: true\n okhttp: \n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: false\n response:\n enabled: false\n\n# 暴露监控端点\nmanagement:\n metrics:\n enable:\n all: false\n health:\n sentinel:\n # 关闭健康检查,避免日志刷屏\n enabled: false\n endpoints:\n web:\n exposure:\n include: \n - info\n - health\n - metrics\n - prometheus\n endpoint:\n health:\n show-details: always\nlogging:\n config: classpath:logback-logstash.xml\nlog:\n level: info\n maxSize: 30MB\n# 临时缓存存储时间(单位:天)\ncache:\n temp:\n time: 7\nDBConnect:\n password:\n privateKey: 54f0e36b98cf63c3bc6185b61c6e4f2a6e1df3bdeb293a50d7b0bc66881c2419\nswagger:\n title: \'摸鱼低代码平台\'\n enabled: true\n description: 摸鱼框架接口文档\n license: Powered By mfish\n licenseUrl: http://www.mfish.com.cn\n version: 版本号:V2.1.1\n termsOfServiceUrl: http://www.mfish.com.cn\n ignoreServers: mf-gateway,mf-monitor\n contact:\n name: mfish\n email: qiufeng9862@qq.com\n url: http://www.mfish.com.cn\nspringdoc:\n swagger-ui:\n enabled: true\n path: /swagger-ui.html\n tags-sorter: alpha\n operations-sorter: alpha\n default-models-expand-depth: -1\n docExpansion: none\n api-docs:\n enabled: true\n path: /v3/api-docs\n use-management-port: false\n default-flat-param-object: true', '4027bdc69421ab032637384f95ae1f2c', '2026-02-08 10:36:25', '2026-07-09 23:21:21', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (3, 'mf-oauth-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼认证中心\'\n prefix: /oauth2\n# mybatis配置\nmybatis-plus:\n # 搜索指定包别名\n type-aliases-package: cn.com.mfish.oauth.mapper\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/**/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n thymeleaf:\n cache: false\n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_oauth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\noauth2:\n expire:\n code: 180\n token: 21600\n refreshToken: 604800\n login:\n mutex: false\n user:\n autoCreate: false\n token:\n sm4key: 143be1ae6ee10b048f7e441cec2a9803\nwechat:\n miniapp:\n appId: wxf7****************ee34a3\n secret: 7908**************************4a5f7d4e1\ngitee:\n clientId: a83ea665************************************6cd3407fc0b651\n clientSecret: 3ebdce2***************************************79bb3bb465e614db\n redirectUri: http://localhost:5186/giteeOauth2\n bindUri: http://localhost:5186/tenant/info/1?callback=gitee\ngithub:\n clientId: Ov2****************JCoJ\n clientSecret: ada9a***********************6bed131\n redirectUri: http://localhost:5186/githubOauth2\n bindUri: http://localhost:5186/tenant/info/1?callback=github', '748cf0dc25228959f8d09df8e56a85c1', '2026-02-08 10:37:25', '2026-07-10 09:07:14', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (4, 'mf-oauth-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼认证中心\'\n prefix: /oauth2\n# mybatis配置\nmybatis-plus:\n # 搜索指定包别名\n type-aliases-package: cn.com.mfish.oauth.mapper\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/**/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n thymeleaf:\n cache: false\n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_oauth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\noauth2:\n expire:\n code: 180\n token: 21600\n refreshToken: 604800\n login:\n mutex: false\n user:\n autoCreate: false\n token:\n sm4key: 143be1ae6ee10b048f7e441cec2a9803\nwechat:\n miniapp:\n appId: wxf7****************ee34a3\n secret: 7908**************************4a5f7d4e1\ngitee:\n clientId: a83ea665************************************6cd3407fc0b651\n clientSecret: 3ebdce2***************************************79bb3bb465e614db\n redirectUri: http://localhost:5186/giteeOauth2\n bindUri: http://localhost:5186/tenant/info/1?callback=gitee\ngithub:\n clientId: Ov2****************JCoJ\n clientSecret: ada9a***********************6bed131\n redirectUri: http://localhost:5186/githubOauth2\n bindUri: http://localhost:5186/tenant/info/1?callback=github', '748cf0dc25228959f8d09df8e56a85c1', '2026-02-08 10:37:25', '2026-07-10 09:07:14', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (5, 'mf-sys-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼系统业务中心\'\r\n prefix: /sys\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/sys/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\ncode:\r\n template:\r\n keys:\r\n - src/main/java/${packageName}/controller/${entityName}Controller.java.ftl\r\n - src/main/java/${packageName}/entity/${entityName}.java.ftl\r\n - src/main/java/${packageName}/mapper/${entityName}Mapper.java.ftl\r\n - src/main/java/${packageName}/mapper/xml/${entityName}Mapper.xml.ftl\r\n - src/main/java/${packageName}/req/Req${entityName}.java.ftl\r\n - src/main/java/${packageName}/service/${entityName}Service.java.ftl\r\n - src/main/java/${packageName}/service/impl/${entityName}ServiceImpl.java.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName#uncap_first}.data.ts.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName}Modal.vue.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName}ViewModal.vue.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/index.vue.ftl\r\n - mfish-nocode-view/src/api/${apiPrefix}/model/${entityName}Model.ts.ftl\r\n - mfish-nocode-view/src/api/${apiPrefix}/${entityName}.ts.ftl\r\n path: template\r\n savePath:\r\n #前端地址\r\n front: E:/webcode\r\n #后端地址(需要指定到具体模块路径)\r\n back: E:/javacode/mfish-nocode-pro/mf-business/mf-sys', '03eedd7cd6a21c36288215e8c4ee9c97', '2026-02-08 10:37:54', '2026-02-08 10:37:54', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (6, 'mf-sys-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼系统业务中心\'\r\n prefix: /sys\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/sys/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\ncode:\r\n template:\r\n keys:\r\n - src/main/java/${packageName}/controller/${entityName}Controller.java.ftl\r\n - src/main/java/${packageName}/entity/${entityName}.java.ftl\r\n - src/main/java/${packageName}/mapper/${entityName}Mapper.java.ftl\r\n - src/main/java/${packageName}/mapper/xml/${entityName}Mapper.xml.ftl\r\n - src/main/java/${packageName}/req/Req${entityName}.java.ftl\r\n - src/main/java/${packageName}/service/${entityName}Service.java.ftl\r\n - src/main/java/${packageName}/service/impl/${entityName}ServiceImpl.java.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName#uncap_first}.data.ts.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName}Modal.vue.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/${entityName}ViewModal.vue.ftl\r\n - mfish-nocode-view/src/views/${apiPrefix}/${entityName#kebab-case}/index.vue.ftl\r\n - mfish-nocode-view/src/api/${apiPrefix}/model/${entityName}Model.ts.ftl\r\n - mfish-nocode-view/src/api/${apiPrefix}/${entityName}.ts.ftl\r\n path: template\r\n savePath:\r\n #前端地址\r\n front: E:/webcode\r\n #后端地址(需要指定到具体模块路径)\r\n back: E:/javacode/mfish-nocode-pro/mf-business/mf-sys', '03eedd7cd6a21c36288215e8c4ee9c97', '2026-02-08 10:37:54', '2026-02-08 10:37:54', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (7, 'mf-storage-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼文件管理\'\r\n prefix: /storage\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/storage/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\nstorage:\r\n active: local\r\n address: http://localhost:8888/storage/file/\r\n local:\r\n storagePath: E:/storage\r\n aliyun:\r\n endpoint: oss-cn-hangzhou.aliyuncs.com\r\n accessKeyId: LTAI5***********FsgT\r\n accessKeySecret: ZtZGF***************JADjS2f\r\n bucketName: landwise\r\n qiniu:\r\n domain: http://cdn.mfish.com.cn\r\n accessKey: cHlX4*******************cdsexgvP\r\n secretKey: QPACH*******************1vEa1t4S\r\n bucket: mfish-nocode', '5d34bf317473dbb57abe28db773a7834', '2026-02-08 10:38:15', '2026-02-08 10:38:15', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (8, 'mf-storage-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼文件管理\'\r\n prefix: /storage\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/storage/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\nstorage:\r\n active: local\r\n address: http://localhost:8888/storage/file/\r\n local:\r\n storagePath: E:/storage\r\n aliyun:\r\n endpoint: oss-cn-hangzhou.aliyuncs.com\r\n accessKeyId: LTAI5***********FsgT\r\n accessKeySecret: ZtZGF***************JADjS2f\r\n bucketName: landwise\r\n qiniu:\r\n domain: http://cdn.mfish.com.cn\r\n accessKey: cHlX4*******************cdsexgvP\r\n secretKey: QPACH*******************1vEa1t4S\r\n bucket: mfish-nocode', '5d34bf317473dbb57abe28db773a7834', '2026-02-08 10:38:15', '2026-02-08 10:38:15', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (9, 'mf-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n server:\n webflux:\n globalcors:\n cors-configurations:\n \'[/**]\':\n max-age: 3600\n allowed-origin-patterns: \"*\"\n allowed-headers: \"*\"\n allow-credentials: true\n allowed-methods:\n - GET\n - POST\n - DELETE\n - PUT\n - OPTION \n discovery:\n locator:\n lowerCaseServiceId: true\n enabled: true\n routes:\n # 认证中心\n - id: oauth2\n uri: lb://mf-oauth\n predicates:\n - Path=/oauth2/**\n filters:\n - CacheFilter\n - StripPrefix=1\n # 测试中心\n - id: test\n uri: lb://mf-test\n predicates:\n - Path=/test/**\n filters:\n - StripPrefix=1\n - id: sys\n uri: lb://mf-sys\n predicates:\n - Path=/sys/**\n filters:\n - StripPrefix=1\n - id: demo\n uri: lb://mf-demo\n predicates:\n - Path=/demo/**\n filters:\n - StripPrefix=1\n - id: storage\n uri: lb://mf-storage\n predicates:\n - Path=/storage/**\n filters:\n - StripPrefix=1\n - id: scheduler\n uri: lb://mf-scheduler\n predicates:\n - Path=/scheduler/**\n filters:\n - StripPrefix=1\n - id: nocode\n uri: lb://mf-nocode\n predicates:\n - Path=/nocode/**\n filters:\n - StripPrefix=1\n - id: workflow\n uri: lb://mf-workflow\n predicates:\n - Path=/workflow/**\n filters:\n - StripPrefix=1\n - id: ai\n uri: lb://mf-ai\n predicates:\n - Path=/ai/**\n filters:\n - StripPrefix=1\n# 安全配置\nsecurity:\n # 验证码\n captcha:\n enabled: true\n type: math\n # 防止XSS攻击\n xss:\n enabled: true\n excludeUrls:\n # 不校验白名单\n ignore:\n whites:\n - /oauth2/user/revoke\n - /oauth2/accessToken\n - /oauth2/authorize\n - /oauth2/sendMsg\n - /oauth2/qrCodeLogin/*\n - /oauth2/wx/bind/**\n - /oauth2/static/**\n - /oauth2/gitee/token/**\n - /oauth2/gitee/url\n - /oauth2/github/token/**\n - /oauth2/github/url\n - /storage/file/*.*\n - /code/**\n - /test/**\n - /*/v3/api-docs\n# swagger configuration\nspringdoc: \n webjars: \n # 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`\n prefix:', 'be9f7df0750e8e41f064fe2896e8d13a', '2026-02-08 10:38:39', '2026-05-15 23:50:51', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (10, 'mf-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n server:\n webflux:\n globalcors:\n cors-configurations:\n \'[/**]\':\n max-age: 3600\n allowed-origin-patterns: \"*\"\n allowed-headers: \"*\"\n allow-credentials: true\n allowed-methods:\n - GET\n - POST\n - DELETE\n - PUT\n - OPTION \n discovery:\n locator:\n lowerCaseServiceId: true\n enabled: true\n routes:\n # 认证中心\n - id: oauth2\n uri: lb://mf-oauth\n predicates:\n - Path=/oauth2/**\n filters:\n - CacheFilter\n - StripPrefix=1\n # 测试中心\n - id: test\n uri: lb://mf-test\n predicates:\n - Path=/test/**\n filters:\n - StripPrefix=1\n - id: sys\n uri: lb://mf-sys\n predicates:\n - Path=/sys/**\n filters:\n - StripPrefix=1\n - id: demo\n uri: lb://mf-demo\n predicates:\n - Path=/demo/**\n filters:\n - StripPrefix=1\n - id: storage\n uri: lb://mf-storage\n predicates:\n - Path=/storage/**\n filters:\n - StripPrefix=1\n - id: scheduler\n uri: lb://mf-scheduler\n predicates:\n - Path=/scheduler/**\n filters:\n - StripPrefix=1\n - id: nocode\n uri: lb://mf-nocode\n predicates:\n - Path=/nocode/**\n filters:\n - StripPrefix=1\n - id: workflow\n uri: lb://mf-workflow\n predicates:\n - Path=/workflow/**\n filters:\n - StripPrefix=1\n - id: ai\n uri: lb://mf-ai\n predicates:\n - Path=/ai/**\n filters:\n - StripPrefix=1\n# 安全配置\nsecurity:\n # 验证码\n captcha:\n enabled: true\n type: math\n # 防止XSS攻击\n xss:\n enabled: true\n excludeUrls:\n # 不校验白名单\n ignore:\n whites:\n - /oauth2/user/revoke\n - /oauth2/accessToken\n - /oauth2/authorize\n - /oauth2/sendMsg\n - /oauth2/qrCodeLogin/*\n - /oauth2/wx/bind/**\n - /oauth2/static/**\n - /oauth2/gitee/token/**\n - /oauth2/gitee/url\n - /oauth2/github/token/**\n - /oauth2/github/url\n - /storage/file/*.*\n - /code/**\n - /test/**\n - /*/v3/api-docs\n# swagger configuration\nspringdoc: \n webjars: \n # 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`\n prefix:', 'be9f7df0750e8e41f064fe2896e8d13a', '2026-02-08 10:38:39', '2026-05-15 23:50:51', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (7, 'mf-storage-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼文件管理\'\n prefix: /storage\n# mybatis配置\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/storage/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\nstorage:\n active: local\n address: http://localhost:8888/storage/file/\n local:\n storagePath: E:/storage\n aliyun:\n endpoint: oss-cn-hangzhou.aliyuncs.com\n accessKeyId: LTAI5***********FsgT\n accessKeySecret: ZtZGF***************JADjS2f\n bucketName: mfish-nocode\n qiniu:\n domain: http://cdn.mfish.com.cn\n accessKey: cHlX4*******************cdsexgvP\n secretKey: QPACH*******************1vEa1t4S\n bucket: mfish-nocode', '99b5e4f41980a831496b58ce36650b78', '2026-02-08 10:38:15', '2026-07-10 09:12:49', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (8, 'mf-storage-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼文件管理\'\n prefix: /storage\n# mybatis配置\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/storage/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\nstorage:\n active: local\n address: http://localhost:8888/storage/file/\n local:\n storagePath: E:/storage\n aliyun:\n endpoint: oss-cn-hangzhou.aliyuncs.com\n accessKeyId: LTAI5***********FsgT\n accessKeySecret: ZtZGF***************JADjS2f\n bucketName: mfish-nocode\n qiniu:\n domain: http://cdn.mfish.com.cn\n accessKey: cHlX4*******************cdsexgvP\n secretKey: QPACH*******************1vEa1t4S\n bucket: mfish-nocode', '99b5e4f41980a831496b58ce36650b78', '2026-02-08 10:38:15', '2026-07-10 09:12:49', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (9, 'mf-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n server:\n webflux:\n globalcors:\n cors-configurations:\n \'[/**]\':\n max-age: 3600\n allowed-origin-patterns: \"*\"\n allowed-headers: \"*\"\n allow-credentials: true\n allowed-methods:\n - GET\n - POST\n - DELETE\n - PUT\n - OPTION \n discovery:\n locator:\n lowerCaseServiceId: true\n enabled: true\n routes:\n # 认证中心\n - id: oauth2\n uri: lb://mf-oauth\n predicates:\n - Path=/oauth2/**\n filters:\n - CacheFilter\n - StripPrefix=1\n # 测试中心\n - id: test\n uri: lb://mf-test\n predicates:\n - Path=/test/**\n filters:\n - StripPrefix=1\n - id: sys\n uri: lb://mf-sys\n predicates:\n - Path=/sys/**\n filters:\n - StripPrefix=1\n - id: demo\n uri: lb://mf-demo\n predicates:\n - Path=/demo/**\n filters:\n - StripPrefix=1\n - id: storage\n uri: lb://mf-storage\n predicates:\n - Path=/storage/**\n filters:\n - StripPrefix=1\n - id: scheduler\n uri: lb://mf-scheduler\n predicates:\n - Path=/scheduler/**\n filters:\n - StripPrefix=1\n - id: nocode\n uri: lb://mf-nocode\n predicates:\n - Path=/nocode/**\n filters:\n - StripPrefix=1\n - id: workflow\n uri: lb://mf-workflow\n predicates:\n - Path=/workflow/**\n filters:\n - StripPrefix=1\n - id: ai\n uri: lb://mf-ai\n predicates:\n - Path=/ai/**\n filters:\n - StripPrefix=1\n# 安全配置\nsecurity:\n # 验证码\n captcha:\n enabled: true\n type: math\n # 防止XSS攻击\n xss:\n enabled: true\n excludeUrls:\n # 不校验白名单\n ignore:\n whites:\n - /oauth2/user/revoke\n - /oauth2/accessToken\n - /oauth2/authorize\n - /oauth2/sendMsg\n - /oauth2/qrCodeLogin/*\n - /oauth2/wx/bind/**\n - /oauth2/static/**\n - /oauth2/gitee/token/**\n - /oauth2/gitee/url\n - /oauth2/github/token/**\n - /oauth2/github/url\n - /storage/file/*.*\n - /code/**\n - /test/**\n - /*/v3/api-docs\n# swagger configuration\nspringdoc: \n webjars: \n # 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`\n prefix:', 'be9f7df0750e8e41f064fe2896e8d13a', '2026-02-08 10:38:39', '2026-06-30 16:55:07', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (10, 'mf-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n server:\n webflux:\n globalcors:\n cors-configurations:\n \'[/**]\':\n max-age: 3600\n allowed-origin-patterns: \"*\"\n allowed-headers: \"*\"\n allow-credentials: true\n allowed-methods:\n - GET\n - POST\n - DELETE\n - PUT\n - OPTION \n discovery:\n locator:\n lowerCaseServiceId: true\n enabled: true\n routes:\n # 认证中心\n - id: oauth2\n uri: lb://mf-oauth\n predicates:\n - Path=/oauth2/**\n filters:\n - CacheFilter\n - StripPrefix=1\n # 测试中心\n - id: test\n uri: lb://mf-test\n predicates:\n - Path=/test/**\n filters:\n - StripPrefix=1\n - id: sys\n uri: lb://mf-sys\n predicates:\n - Path=/sys/**\n filters:\n - StripPrefix=1\n - id: demo\n uri: lb://mf-demo\n predicates:\n - Path=/demo/**\n filters:\n - StripPrefix=1\n - id: storage\n uri: lb://mf-storage\n predicates:\n - Path=/storage/**\n filters:\n - StripPrefix=1\n - id: scheduler\n uri: lb://mf-scheduler\n predicates:\n - Path=/scheduler/**\n filters:\n - StripPrefix=1\n - id: nocode\n uri: lb://mf-nocode\n predicates:\n - Path=/nocode/**\n filters:\n - StripPrefix=1\n - id: workflow\n uri: lb://mf-workflow\n predicates:\n - Path=/workflow/**\n filters:\n - StripPrefix=1\n - id: ai\n uri: lb://mf-ai\n predicates:\n - Path=/ai/**\n filters:\n - StripPrefix=1\n# 安全配置\nsecurity:\n # 验证码\n captcha:\n enabled: true\n type: math\n # 防止XSS攻击\n xss:\n enabled: true\n excludeUrls:\n # 不校验白名单\n ignore:\n whites:\n - /oauth2/user/revoke\n - /oauth2/accessToken\n - /oauth2/authorize\n - /oauth2/sendMsg\n - /oauth2/qrCodeLogin/*\n - /oauth2/wx/bind/**\n - /oauth2/static/**\n - /oauth2/gitee/token/**\n - /oauth2/gitee/url\n - /oauth2/github/token/**\n - /oauth2/github/url\n - /storage/file/*.*\n - /code/**\n - /test/**\n - /*/v3/api-docs\n# swagger configuration\nspringdoc: \n webjars: \n # 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`\n prefix:', 'be9f7df0750e8e41f064fe2896e8d13a', '2026-02-08 10:38:39', '2026-06-30 16:55:07', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (11, 'mf-scheduler-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼调度中心\'\r\n prefix: /scheduler\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/scheduler/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_scheduler?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\norg:\r\n quartz:\r\n scheduler:\r\n instanceName: MfishClusteredScheduler\r\n instanceId: AUTO\r\n threadPool:\r\n poolClass: org.quartz.simpl.SimpleThreadPool\r\n threadCount: 50\r\n threadPriority: 5\r\n jobStore:\r\n jdbcClass: org.quartz.impl.jdbcjobstore.JobStoreTX\r\n driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate\r\n useProperties: true\r\n isClustered: true\r\n tablePrefix: QRTZ_\r\n dataSource: mf_scheduler\r\n clusterCheckinInterval: 20000\r\n misfireThreshold: 60000\r\n#rocketmq配置信息\r\nrocketmq:\r\n #nameservice服务器地址(多个以英文逗号隔开)\r\n name-server: 127.0.0.1:9876\r\n #生产者配置\r\n producer:\r\n #组名\r\n group: mfish-scheduler-group\r\n sendMessageTimeout: 300000\r\n #目的地(topic:tag)\r\n #topic\r\n topic: mfish-scheduler-topic', '804951d75ca731c1bd02f89f2868abe1', '2026-02-08 10:39:07', '2026-02-08 10:39:07', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (12, 'mf-scheduler-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼调度中心\'\r\n prefix: /scheduler\r\n# mybatis配置\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/scheduler/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_scheduler?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\norg:\r\n quartz:\r\n scheduler:\r\n instanceName: MfishClusteredScheduler\r\n instanceId: AUTO\r\n threadPool:\r\n poolClass: org.quartz.simpl.SimpleThreadPool\r\n threadCount: 50\r\n threadPriority: 5\r\n jobStore:\r\n jdbcClass: org.quartz.impl.jdbcjobstore.JobStoreTX\r\n driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate\r\n useProperties: true\r\n isClustered: true\r\n tablePrefix: QRTZ_\r\n dataSource: mf_scheduler\r\n clusterCheckinInterval: 20000\r\n misfireThreshold: 60000\r\n#rocketmq配置信息\r\nrocketmq:\r\n #nameservice服务器地址(多个以英文逗号隔开)\r\n name-server: 127.0.0.1:9876\r\n #生产者配置\r\n producer:\r\n #组名\r\n group: mfish-scheduler-group\r\n sendMessageTimeout: 300000\r\n #目的地(topic:tag)\r\n #topic\r\n topic: mfish-scheduler-topic', '804951d75ca731c1bd02f89f2868abe1', '2026-02-08 10:39:07', '2026-02-08 10:39:07', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (13, 'mf-workflow-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼工作流服务\'\r\n prefix: /workflow\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/workflow/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n dynamic:\r\n hikari:\r\n # ========== 连接池核心参数 ==========\r\n minimum-idle: 5 # 连接池最小空闲连接数\r\n maximum-pool-size: 20 # 连接池最大连接数\r\n idle-timeout: 300000 # 5分钟,空闲连接存活最大时间\r\n max-lifetime: 1800000 # 30分钟 连接最大存活时间\r\n connection-timeout: 30000 # 连接超时时间\r\n validation-timeout: 5000\r\n # ========== 连接检测 ==========\r\n connection-test-query: SELECT 1\r\n # ========== PreparedStatement 缓存 ==========\r\n data-source-properties:\r\n cachePrepStmts: true\r\n prepStmtCacheSize: 250\r\n prepStmtCacheSqlLimit: 2048\r\n useServerPrepStmts: true\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_workflow?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\n# Flowable 会自动建表\r\nflowable:\r\n # 自动更新数据库结构\r\n database-schema-update: true\r\n # 启用异步执行器\r\n async-executor-activate: true\r\n eventregistry:\r\n enabled: false\r\n # 检查流程定义\r\n# check-process-definitions: false', '09248143eabcad1efb0939f571a1005b', '2026-02-08 10:40:12', '2026-02-08 10:40:12', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (14, 'mf-workflow-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼工作流服务\'\r\n prefix: /workflow\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/workflow/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n dynamic:\r\n hikari:\r\n # ========== 连接池核心参数 ==========\r\n minimum-idle: 5 # 连接池最小空闲连接数\r\n maximum-pool-size: 20 # 连接池最大连接数\r\n idle-timeout: 300000 # 5分钟,空闲连接存活最大时间\r\n max-lifetime: 1800000 # 30分钟 连接最大存活时间\r\n connection-timeout: 30000 # 连接超时时间\r\n validation-timeout: 5000\r\n # ========== 连接检测 ==========\r\n connection-test-query: SELECT 1\r\n # ========== PreparedStatement 缓存 ==========\r\n data-source-properties:\r\n cachePrepStmts: true\r\n prepStmtCacheSize: 250\r\n prepStmtCacheSqlLimit: 2048\r\n useServerPrepStmts: true\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_workflow?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\n# Flowable 会自动建表\r\nflowable:\r\n # 自动更新数据库结构\r\n database-schema-update: true\r\n # 启用异步执行器\r\n async-executor-activate: true\r\n eventregistry:\r\n enabled: false\r\n # 检查流程定义\r\n# check-process-definitions: false', '09248143eabcad1efb0939f571a1005b', '2026-02-08 10:40:12', '2026-02-08 10:40:12', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (15, 'mf-monitor-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼监控中心\'\r\nspring: \r\n security:\r\n user:\r\n name: admin\r\n password: 123456\r\n boot:\r\n admin:\r\n ui:\r\n title: 摸鱼监控中心', 'cf16dec113c8fcf0728533a30e679e0a', '2026-02-08 10:40:35', '2026-02-08 10:40:35', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (16, 'mf-monitor-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼监控中心\'\r\nspring: \r\n security:\r\n user:\r\n name: admin\r\n password: 123456\r\n boot:\r\n admin:\r\n ui:\r\n title: 摸鱼监控中心', 'cf16dec113c8fcf0728533a30e679e0a', '2026-02-08 10:40:35', '2026-02-08 10:40:35', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (17, 'mf-nocode-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼无代码中心\'\r\n prefix: /nocode\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/nocode/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_nocode?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\nstorage:\r\n active: local\r\n address: http://localhost:8888/storage/file/\r\n local:\r\n storagePath: E:/storage\r\n aliyun:\r\n endpoint: oss-cn-hangzhou.aliyuncs.com\r\n accessKeyId: LTAI5***********FsgT\r\n accessKeySecret: ZtZGF***************JADjS2f\r\n bucketName: landwise\r\n qiniu:\r\n domain: http://cdn.mfish.com.cn\r\n accessKey: cHlX4*******************cdsexgvP\r\n secretKey: QPACH*******************1vEa1t4S\r\n bucket: mfish-nocode', '9da1faf051318a3b52a6c58f8d1b3773', '2026-02-08 10:40:58', '2026-02-08 10:40:58', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'text', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (18, 'mf-nocode-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼无代码中心\'\r\n prefix: /nocode\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/nocode/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_nocode?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\nstorage:\r\n active: local\r\n address: http://localhost:8888/storage/file/\r\n local:\r\n storagePath: E:/storage\r\n aliyun:\r\n endpoint: oss-cn-hangzhou.aliyuncs.com\r\n accessKeyId: LTAI5***********FsgT\r\n accessKeySecret: ZtZGF***************JADjS2f\r\n bucketName: landwise\r\n qiniu:\r\n domain: http://cdn.mfish.com.cn\r\n accessKey: cHlX4*******************cdsexgvP\r\n secretKey: QPACH*******************1vEa1t4S\r\n bucket: mfish-nocode', '9da1faf051318a3b52a6c58f8d1b3773', '2026-02-08 10:40:58', '2026-02-08 10:40:58', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'text', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (17, 'mf-nocode-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼无代码中心\'\n prefix: /nocode\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/nocode/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_nocode?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\nstorage:\n active: local\n address: http://localhost:8888/storage/file/\n local:\n storagePath: E:/storage\n aliyun:\n endpoint: oss-cn-hangzhou.aliyuncs.com\n accessKeyId: LTAI5***********FsgT\n accessKeySecret: ZtZGF***************JADjS2f\n bucketName: mfish-nocode\n qiniu:\n domain: http://cdn.mfish.com.cn\n accessKey: cHlX4*******************cdsexgvP\n secretKey: QPACH*******************1vEa1t4S\n bucket: mfish-nocode', '8c1b715ea47953c10864fd80ac2be655', '2026-02-08 10:40:58', '2026-07-10 09:12:33', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (18, 'mf-nocode-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼无代码中心\'\n prefix: /nocode\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/nocode/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_nocode?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\nstorage:\n active: local\n address: http://localhost:8888/storage/file/\n local:\n storagePath: E:/storage\n aliyun:\n endpoint: oss-cn-hangzhou.aliyuncs.com\n accessKeyId: LTAI5***********FsgT\n accessKeySecret: ZtZGF***************JADjS2f\n bucketName: mfish-nocode\n qiniu:\n domain: http://cdn.mfish.com.cn\n accessKey: cHlX4*******************cdsexgvP\n secretKey: QPACH*******************1vEa1t4S\n bucket: mfish-nocode', '8c1b715ea47953c10864fd80ac2be655', '2026-02-08 10:40:58', '2026-07-10 09:12:33', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (19, 'mf-demo-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼样例服务\'\r\n prefix: /demo\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/demo/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭', 'df5971c7c04f34636ac6b9c790b3073d', '2026-02-08 10:41:21', '2026-02-08 10:41:21', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (20, 'mf-demo-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼样例服务\'\r\n prefix: /demo\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/demo/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭', 'df5971c7c04f34636ac6b9c790b3073d', '2026-02-08 10:41:21', '2026-02-08 10:41:21', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (21, 'mf-ai-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼AI中心\'\r\n prefix: /ai\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/ai/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\n ai:\r\n openai:\r\n api-key: sk-or-v1-b4**************************************a99b135dae3b\r\n base-url: https://openrouter.ai/api\r\n chat:\r\n options:\r\n model: meta-llama/llama-3.3-70b-instruct:free\r\n max-tokens: 1000\r\n temperature: 0.7\r\n ollama:\r\n base-url: http://localhost:11434\r\n chat:\r\n model: qwen3:8b', 'c36840b577c0940c00d629f2497cc706', '2026-02-08 10:41:48', '2026-02-08 10:41:48', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (22, 'mf-ai-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼AI中心\'\r\n prefix: /ai\r\nmybatis-plus:\r\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\r\n mapper-locations: classpath*:cn/com/mfish/ai/mapper/**/*Mapper.xml\r\n global-config:\r\n # 关闭MP3.0自带的banner\r\n banner: false\r\n db-config:\r\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\r\n #id-type: 0\r\n # 默认数据库表下划线命名\r\n table-underline: true\r\n #逻辑删除 删除前\r\n logic-not-delete-value: 0\r\n #逻辑删除 删除后\r\n logic-delete-value: 1\r\n configuration:\r\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\r\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\r\n # 返回类型为Map,显示null对应的字段\r\n call-setters-on-nulls: true\r\n map-underscore-to-camel-case: true\r\n auto-mapping-behavior: full\r\n jdbc-type-for-null:\r\nspring: \r\n datasource:\r\n druid:\r\n stat-view-servlet:\r\n enabled: true\r\n loginUsername: admin\r\n loginPassword: 123456\r\n dynamic:\r\n druid:\r\n initial-size: 5\r\n min-idle: 5\r\n maxActive: 20\r\n maxWait: 60000\r\n timeBetweenEvictionRunsMillis: 60000\r\n minEvictableIdleTimeMillis: 300000\r\n validationQuery: SELECT 1 FROM DUAL\r\n testWhileIdle: true\r\n testOnBorrow: false\r\n testOnReturn: false\r\n poolPreparedStatements: true\r\n maxPoolPreparedStatementPerConnectionSize: 20\r\n filters: stat,slf4j\r\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\r\n datasource:\r\n # 主库数据源\r\n master:\r\n driver-class-name: com.mysql.cj.jdbc.Driver\r\n url: jdbc:mysql://localhost:3306/mf_demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\r\n username: root\r\n password: 123456\r\n # 从库数据源\r\n # slave:\r\n # username: \r\n # password: \r\n # url: \r\n # driver-class-name: \r\n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\r\n ai:\r\n openai:\r\n api-key: sk-or-v1-b4**************************************a99b135dae3b\r\n base-url: https://openrouter.ai/api\r\n chat:\r\n options:\r\n model: meta-llama/llama-3.3-70b-instruct:free\r\n max-tokens: 1000\r\n temperature: 0.7\r\n ollama:\r\n base-url: http://localhost:11434\r\n chat:\r\n model: qwen3:8b', 'c36840b577c0940c00d629f2497cc706', '2026-02-08 10:41:48', '2026-02-08 10:41:48', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (21, 'mf-ai-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼AI中心\'\n prefix: /ai\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/ai/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\n ai:\n openai:\n api-key: sk-or-v1-b4**************************************a99b135dae3b\n base-url: https://openrouter.ai/api\n chat:\n model: meta-llama/llama-3.3-70b-instruct:free\n max-tokens: 1000\n temperature: 0.7\n ollama:\n base-url: http://localhost:11434\n chat:\n model: qwen3:8b\nai:\n api-key:\n hex-key: 8ca90bd312db42aac5a8313ce9a0eac3', 'be444f1caa0f727eb32de9134ccce9aa', '2026-02-08 10:41:48', '2026-07-10 09:10:53', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (22, 'mf-ai-dev.yml', 'DEFAULT_GROUP', 'swagger:\n title: \'摸鱼AI中心\'\n prefix: /ai\nmybatis-plus:\n # 配置mapper的扫描,找到所有的mapper.xml映射文件\n mapper-locations: classpath*:cn/com/mfish/ai/mapper/**/*Mapper.xml\n global-config:\n # 关闭MP3.0自带的banner\n banner: false\n db-config:\n #主键类型 0:\"数据库ID自增\",1:\"该类型为未设置主键类型\", 2:\"用户输入ID\",3:\"全局唯一ID (数字类型唯一ID)\", 4:\"全局唯一ID UUID\",5:\"字符串全局唯一ID (idWorker 的字符串表示)\";\n #id-type: 0\n # 默认数据库表下划线命名\n table-underline: true\n #逻辑删除 删除前\n logic-not-delete-value: 0\n #逻辑删除 删除后\n logic-delete-value: 1\n configuration:\n # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用\n #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n # 返回类型为Map,显示null对应的字段\n call-setters-on-nulls: true\n map-underscore-to-camel-case: true\n auto-mapping-behavior: full\n jdbc-type-for-null:\nspring: \n datasource:\n druid:\n stat-view-servlet:\n enabled: true\n loginUsername: admin\n loginPassword: 123456\n dynamic:\n druid:\n initial-size: 5\n min-idle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n maxPoolPreparedStatementPerConnectionSize: 20\n filters: stat,slf4j\n connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n datasource:\n # 主库数据源\n master:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/mf_ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8\n username: root\n password: 123456\n # 从库数据源\n # slave:\n # username: \n # password: \n # url: \n # driver-class-name: \n # seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭\n ai:\n openai:\n api-key: sk-or-v1-b4**************************************a99b135dae3b\n base-url: https://openrouter.ai/api\n chat:\n model: meta-llama/llama-3.3-70b-instruct:free\n max-tokens: 1000\n temperature: 0.7\n ollama:\n base-url: http://localhost:11434\n chat:\n model: qwen3:8b\nai:\n api-key:\n hex-key: 8ca90bd312db42aac5a8313ce9a0eac3', 'be444f1caa0f727eb32de9134ccce9aa', '2026-02-08 10:41:48', '2026-07-10 09:10:53', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (23, 'mf-consume-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼调度中心-MQ消费端测试\'\r\n#rocketmq配置信息\r\nrocketmq:\r\n #生产者配置\r\n consumer:\r\n nameServer: 127.0.0.1:9876\r\n #topic\r\n topic: mfish-scheduler-topic\r\n #组名\r\n group: mfish-scheduler-group', '3b38a5c258a1bdd32b06844e87a4e1f1', '2026-02-08 10:42:04', '2026-02-08 10:42:04', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (24, 'mf-consume-dev.yml', 'DEFAULT_GROUP', 'swagger:\r\n title: \'摸鱼调度中心-MQ消费端测试\'\r\n#rocketmq配置信息\r\nrocketmq:\r\n #生产者配置\r\n consumer:\r\n nameServer: 127.0.0.1:9876\r\n #topic\r\n topic: mfish-scheduler-topic\r\n #组名\r\n group: mfish-scheduler-group', '3b38a5c258a1bdd32b06844e87a4e1f1', '2026-02-08 10:42:04', '2026-02-08 10:42:04', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, '');
|
||||
INSERT INTO `config_info` VALUES (25, 'sentinel-mf-gateway', 'DEFAULT_GROUP', '[\r\n {\r\n \"resource\": \"mf-oauth\",\r\n \"count\": 500,\r\n \"grade\": 1,\r\n \"limitApp\": \"default\",\r\n \"strategy\": 0,\r\n \"controlBehavior\": 0\r\n },\r\n {\r\n \"resource\": \"mf-sys\",\r\n \"count\": 500,\r\n \"grade\": 1,\r\n \"limitApp\": \"default\",\r\n \"strategy\": 0,\r\n \"controlBehavior\": 0\r\n },\r\n {\r\n \"resource\": \"mf-storage\",\r\n \"count\": 100,\r\n \"grade\": 1,\r\n \"limitApp\": \"default\",\r\n \"strategy\": 0,\r\n \"controlBehavior\": 0\r\n },\r\n {\r\n \"resource\": \"mf-scheduler\",\r\n \"count\": 100,\r\n \"grade\": 1,\r\n \"limitApp\": \"default\",\r\n \"strategy\": 0,\r\n \"controlBehavior\": 0\r\n }\r\n]', '2f2496295023706014fa181fe2b4ffa3', '2026-02-08 10:42:36', '2026-02-08 10:42:36', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', NULL, NULL, NULL, 'json', NULL, '');
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
DEMO样例表(采用微服务启动,如果需要使用演示样例需要创建该库)
|
||||
根据个人使用场景选择是否初始化
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- DEMO样例表(采用微服务启动,如果需要使用演示样例需要创建该库)
|
||||
-- 根据个人使用场景选择是否初始化
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_demo`;
|
||||
CREATE DATABASE `mf_demo` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
自助大屏、自助API相关表(使用自助大屏相关功能需要创建该表)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 自助大屏、自助API相关表(使用自助大屏相关功能需要创建该表)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_nocode`;
|
||||
CREATE DATABASE `mf_nocode` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
+8
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
微服务认证、用户、权限、租户相关表(采用微服务启动,需要创建该库)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 微服务认证、用户、权限、租户相关表(采用微服务启动,需要创建该库)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_oauth`;
|
||||
CREATE DATABASE `mf_oauth` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
@@ -229,6 +229,11 @@ INSERT INTO `sso_menu` VALUES ('ad5b361ff34235e8ec85cd613a59bf6e', '8ae3ea763294
|
||||
INSERT INTO `sso_menu` VALUES ('c5309ca3ba545da9950278637c90e674', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300002', 3, '新增', '#', 2, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:insert', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('e92728a6d996d4b7cb3acb0fc031d183', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300004', 3, '删除', '#', 4, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:delete', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('c0adefdb601f6e41f82a06ff6513613e', '53e8eaceee36c1d54e43319fdd60811b', '0001300008', 2, '工作流样例', 'ant-design:fork-outlined', 11, 1, '/demo-leave-apply', '/demo/demo-leave-apply/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-04-20 17:05:36', 'admin', '2026-04-20 20:05:50');
|
||||
INSERT INTO `sso_menu` VALUES ('3993e903efe9406aca08697bf80a4140', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500004', 3, '删除', '#', 4, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:delete', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('417074a416446e7b6382691e0750bdad', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500001', 3, '查询', '#', 1, 2, '', NULL, 'ai:aiModelConfig:query', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('4ee0d5036b299c5efb7674c9ccf1d647', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500002', 3, '新增', '#', 2, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:insert', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('567c06fabc1f6c429aec3e6b1c7fa511', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500003', 3, '修改', '#', 3, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:update', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('f65cc0cfee8cced9bf0613def2d04b42', '2a4e024fdc76063da32926c63ca9ead2', '0000200015', 2, '模型配置', 'ant-design:open-a-i-outlined', 13, 1, '/ai-model-config', '/ai/ai-model-config/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-07-06 21:35:04', 'admin', '2026-07-06 21:40:34');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sso_org
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
调度策略相关表,使用系统提供调度服务需要用到相关表(采用微服务启动,需要创建该库)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 调度策略相关表,使用系统提供调度服务需要用到相关表(采用微服务启动,需要创建该库)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_scheduler`;
|
||||
CREATE DATABASE `mf_scheduler` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
+8
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
系统相关表(采用微服务启动,需要创建该库)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 系统相关表(采用微服务启动,需要创建该库)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_system`;
|
||||
CREATE DATABASE `mf_system` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
@@ -123,6 +123,7 @@ INSERT INTO `sys_dict` VALUES ('b0f0a03f9e879c2ce3b7722a3cd01061', 'HTTP请求
|
||||
INSERT INTO `sys_dict` VALUES ('4a9914bd42939163ceb9c5b4002cfa66', 'HTTP请求方式', 'http_request_method', 0, NULL, 'admin', '2025-07-22 14:30:25', 'admin', '2025-07-22 14:30:25');
|
||||
INSERT INTO `sys_dict` VALUES ('220bbd3b1dd32fd37d0abbd279a14774', '工作流任务状态', 'workflow_task_status', 0, '工作流任务状态字典', 'admin', '2025-10-10 16:27:01', 'admin', '2025-10-10 16:27:01');
|
||||
INSERT INTO `sys_dict` VALUES ('eeb27772c310addeae7c12d296521399', '工作流审批类型', 'workflow_process_key', 0, '工作流流程定义key,例如大屏发布审批是screen_release', 'admin', '2025-10-15 16:34:32', 'admin', '2025-10-15 16:34:32');
|
||||
INSERT INTO `sys_dict` VALUES ('cd338555eccf73e26e9243837d6c711e', '模型接入协议', 'ai_model_protocol', 0, 'AI模型接入的协议,比如openai协议', 'admin', '2026-07-10 16:56:59', '', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_dict_item
|
||||
@@ -272,6 +273,10 @@ INSERT INTO `sys_dict_item` VALUES ('261f489024bb90163e49ee85498df47e', '220bbd3
|
||||
INSERT INTO `sys_dict_item` VALUES ('07582f8cb8443cd3de3f5ab150386690', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已取消', 'terminated', 0, 3, '', 'red', 0, NULL, 'admin', '2025-10-10 16:29:10', 'admin', '2025-10-10 16:29:14');
|
||||
INSERT INTO `sys_dict_item` VALUES ('4e14582f0b59762f2ca1cfda04539202', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '大屏发布', 'screen_release', 0, 1, 'ant-design:fund-projection-screen-outlined', '', 0, NULL, 'admin', '2025-10-15 16:35:15', 'admin', '2025-10-16 11:04:43');
|
||||
INSERT INTO `sys_dict_item` VALUES ('581a7e287feec3be8771175bdc5dab9b', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '工作流样例审批', 'demo_leave_apply_release', 0, 2, 'ant-design:fork-outlined', 'blue', 0, '工作流试用样例', 'admin', '2026-04-08 20:08:42', 'admin', '2026-04-20 21:38:45');
|
||||
INSERT INTO `sys_dict_item` VALUES ('e100aa3c678f2bf6a4e1e24a9feee89f', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'openai', 'openai', 0, 1, 'ant-design:open-a-i-outlined', 'green', 0, NULL, 'admin', '2026-07-10 16:57:27', 'admin', '2026-07-10 16:57:37');
|
||||
INSERT INTO `sys_dict_item` VALUES ('cac0983d1430c0a7dce98bd8c01cc19c', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'ollama', 'ollama', 0, 2, '', 'blue', 0, NULL, 'admin', '2026-07-10 16:58:34', '', NULL);
|
||||
INSERT INTO `sys_dict_item` VALUES ('64a2906317754ac19c779e04ddc155de', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'deepseek', 'deepseek', 0, 3, NULL, 'cyan', 0, NULL, 'admin', '2026-07-10 16:58:49', '', NULL);
|
||||
INSERT INTO `sys_dict_item` VALUES ('76faf36cc4e459171c273fa833e79ff4', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'anthropic', 'anthropic', 0, 4, NULL, 'pink', 0, NULL, 'admin', '2026-07-10 16:59:22', '', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_log
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
工作流数据库(采用微服务启动时,需要创建工作流数据库,数据表引擎会自动创建)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 工作流数据库(采用微服务启动时,需要创建工作流数据库,数据表引擎会自动创建)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mf_workflow`;
|
||||
CREATE DATABASE `mf_workflow` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
+13
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
单实例启动需要初始化的表(如果采用单实例启动,只需要创建这个数据库,其他微服务相关数据库无需创建)
|
||||
*/
|
||||
-- ----------------------------------------
|
||||
-- 单实例启动需要初始化的表(如果采用单实例启动,只需要创建这个数据库,其他微服务相关数据库无需创建)
|
||||
-- ----------------------------------------
|
||||
DROP DATABASE IF EXISTS `mfish_nocode`;
|
||||
CREATE DATABASE `mfish_nocode` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
SET NAMES utf8mb4;
|
||||
@@ -454,6 +454,11 @@ INSERT INTO `sso_menu` VALUES ('ad5b361ff34235e8ec85cd613a59bf6e', '8ae3ea763294
|
||||
INSERT INTO `sso_menu` VALUES ('c5309ca3ba545da9950278637c90e674', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300002', 3, '新增', '#', 2, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:insert', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('e92728a6d996d4b7cb3acb0fc031d183', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300004', 3, '删除', '#', 4, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:delete', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('c0adefdb601f6e41f82a06ff6513613e', '53e8eaceee36c1d54e43319fdd60811b', '0001300008', 2, '工作流样例', 'ant-design:fork-outlined', 11, 1, '/demo-leave-apply', '/demo/demo-leave-apply/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-04-20 17:05:36', 'admin', '2026-04-20 20:05:50');
|
||||
INSERT INTO `sso_menu` VALUES ('3993e903efe9406aca08697bf80a4140', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500004', 3, '删除', '#', 4, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:delete', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('417074a416446e7b6382691e0750bdad', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500001', 3, '查询', '#', 1, 2, '', NULL, 'ai:aiModelConfig:query', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('4ee0d5036b299c5efb7674c9ccf1d647', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500002', 3, '新增', '#', 2, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:insert', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('567c06fabc1f6c429aec3e6b1c7fa511', 'f65cc0cfee8cced9bf0613def2d04b42', '000020001500003', 3, '修改', '#', 3, 2, '', NULL, 'ai:aiModelConfig:query,ai:aiModelConfig:update', 0, 1, NULL, NULL, '', 'admin', '2026-07-06 21:35:04', '', NULL);
|
||||
INSERT INTO `sso_menu` VALUES ('f65cc0cfee8cced9bf0613def2d04b42', '2a4e024fdc76063da32926c63ca9ead2', '0000200015', 2, '模型配置', 'ant-design:open-a-i-outlined', 13, 1, '/ai-model-config', '/ai/ai-model-config/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-07-06 21:35:04', 'admin', '2026-07-06 21:40:34');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sso_org
|
||||
@@ -991,6 +996,7 @@ INSERT INTO `sys_dict` VALUES ('b0f0a03f9e879c2ce3b7722a3cd01061', 'HTTP请求
|
||||
INSERT INTO `sys_dict` VALUES ('4a9914bd42939163ceb9c5b4002cfa66', 'HTTP请求方式', 'http_request_method', 0, NULL, 'admin', '2025-07-22 14:30:25', 'admin', '2025-07-22 14:30:25');
|
||||
INSERT INTO `sys_dict` VALUES ('220bbd3b1dd32fd37d0abbd279a14774', '工作流任务状态', 'workflow_task_status', 0, '工作流任务状态字典', 'admin', '2025-10-10 16:27:01', 'admin', '2025-10-10 16:27:01');
|
||||
INSERT INTO `sys_dict` VALUES ('eeb27772c310addeae7c12d296521399', '工作流审批类型', 'workflow_process_key', 0, '工作流流程定义key,例如大屏发布审批是screen_release', 'admin', '2025-10-15 16:34:32', 'admin', '2025-10-15 16:34:32');
|
||||
INSERT INTO `sys_dict` VALUES ('cd338555eccf73e26e9243837d6c711e', '模型接入协议', 'ai_model_protocol', 0, 'AI模型接入的协议,比如openai协议', 'admin', '2026-07-10 16:56:59', '', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_dict_item
|
||||
@@ -1140,6 +1146,10 @@ INSERT INTO `sys_dict_item` VALUES ('261f489024bb90163e49ee85498df47e', '220bbd3
|
||||
INSERT INTO `sys_dict_item` VALUES ('07582f8cb8443cd3de3f5ab150386690', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已取消', 'terminated', 0, 3, '', 'red', 0, NULL, 'admin', '2025-10-10 16:29:10', 'admin', '2025-10-10 16:29:14');
|
||||
INSERT INTO `sys_dict_item` VALUES ('4e14582f0b59762f2ca1cfda04539202', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '大屏发布', 'screen_release', 0, 1, 'ant-design:fund-projection-screen-outlined', '', 0, NULL, 'admin', '2025-10-15 16:35:15', 'admin', '2025-10-16 11:04:43');
|
||||
INSERT INTO `sys_dict_item` VALUES ('581a7e287feec3be8771175bdc5dab9b', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '工作流样例审批', 'demo_leave_apply_release', 0, 2, 'ant-design:fork-outlined', 'blue', 0, '工作流试用样例', 'admin', '2026-04-08 20:08:42', 'admin', '2026-04-20 21:38:45');
|
||||
INSERT INTO `sys_dict_item` VALUES ('e100aa3c678f2bf6a4e1e24a9feee89f', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'openai', 'openai', 0, 1, 'ant-design:open-a-i-outlined', 'green', 0, NULL, 'admin', '2026-07-10 16:57:27', 'admin', '2026-07-10 16:57:37');
|
||||
INSERT INTO `sys_dict_item` VALUES ('cac0983d1430c0a7dce98bd8c01cc19c', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'ollama', 'ollama', 0, 2, '', 'blue', 0, NULL, 'admin', '2026-07-10 16:58:34', '', NULL);
|
||||
INSERT INTO `sys_dict_item` VALUES ('64a2906317754ac19c779e04ddc155de', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'deepseek', 'deepseek', 0, 3, NULL, 'cyan', 0, NULL, 'admin', '2026-07-10 16:58:49', '', NULL);
|
||||
INSERT INTO `sys_dict_item` VALUES ('76faf36cc4e459171c273fa833e79ff4', 'cd338555eccf73e26e9243837d6c711e', 'ai_model_protocol', 'anthropic', 'anthropic', 0, 4, NULL, 'pink', 0, NULL, 'admin', '2026-07-10 16:59:22', '', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_log
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-api</artifactId>
|
||||
<version>mf-2.4.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mf-ai-api</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,69 @@
|
||||
package cn.com.mfish.ai.api.entity;
|
||||
|
||||
import cn.com.mfish.common.core.entity.BaseEntity;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
@Data
|
||||
@TableName("ai_model_config")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "ai_model_config对象 AI模型配置信息")
|
||||
public class AiModelConfig extends BaseEntity<String> {
|
||||
@ExcelProperty("唯一ID")
|
||||
@Schema(description = "唯一ID")
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Accessors(chain = true)
|
||||
private String id;
|
||||
@ExcelProperty("租户ID")
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
@ExcelProperty("提供者: openai/ollama/deepseek/zhipuai/anthropic等")
|
||||
@Schema(description = "提供者: openai/ollama/deepseek/zhipuai/anthropic等")
|
||||
private String provider;
|
||||
@ExcelProperty("模型名称: gpt-4o, qwen3:8b, deepseek-v3 等")
|
||||
@Schema(description = "模型名称: gpt-4o, qwen3:8b, deepseek-v3 等")
|
||||
private String modelName;
|
||||
@ExcelProperty("接入协议: openai/ollama/deepseek/anthropic")
|
||||
@Schema(description = "接入协议: openai/ollama/deepseek/anthropic")
|
||||
private String protocol;
|
||||
@ExcelProperty("API密钥(加密存储)")
|
||||
@Schema(description = "API密钥(加密存储)")
|
||||
private String apiKey;
|
||||
@ExcelProperty("API基础地址")
|
||||
@Schema(description = "API基础地址")
|
||||
private String baseUrl;
|
||||
@ExcelProperty("最大token数")
|
||||
@Schema(description = "最大token数")
|
||||
private Integer maxTokens;
|
||||
@ExcelProperty("温度参数")
|
||||
@Schema(description = "温度参数")
|
||||
private Double temperature;
|
||||
@ExcelProperty("top_p参数")
|
||||
@Schema(description = "top_p参数")
|
||||
private Double topP;
|
||||
@ExcelProperty("补全项路径")
|
||||
@Schema(description = "补全项路径")
|
||||
private String completionsPath;
|
||||
@ExcelProperty("是否启用 1启用 0禁用")
|
||||
@Schema(description = "是否启用 1启用 0禁用")
|
||||
private Short enabled;
|
||||
@ExcelProperty("排序(决定fallback优先级)")
|
||||
@Schema(description = "排序(决定fallback优先级)")
|
||||
private Integer sortOrder;
|
||||
@ExcelProperty("备注")
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.com.mfish.ai.api.fallback;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.remote.RemoteAiModelConfigService;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: ai模型配置服务降级处理
|
||||
* @author: mfish
|
||||
* @date: 2026/07/03
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RemoteAiModelConfigFallBack implements FallbackFactory<RemoteAiModelConfigService> {
|
||||
/**
|
||||
* 创建ai模型配置服务降级实例
|
||||
*
|
||||
* @param cause 导致降级的异常原因
|
||||
* @return 降级后的请假申请审批服务实例
|
||||
*/
|
||||
@Override
|
||||
public RemoteAiModelConfigService create(Throwable cause) {
|
||||
log.error("错误: demo请假申请审批回调接口调用异常", cause);
|
||||
return new RemoteAiModelConfigService() {
|
||||
@Override
|
||||
public Result<PageResult<AiModelConfigVo>> queryPageList(String origin, ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) {
|
||||
return Result.fail("错误:查询分页列表失败" + cause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<AiModelConfig>> queryList(String origin, ReqAiModelConfig reqAiModelConfig) {
|
||||
return Result.fail("错误:查询列表失败" + cause.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package cn.com.mfish.ai.api.remote;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.fallback.RemoteAiModelConfigFallBack;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.common.core.constants.RPCConstants;
|
||||
import cn.com.mfish.common.core.constants.ServiceConstants;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: ai模型配置服务
|
||||
* @author: mfish
|
||||
* @date: 2026/07/03
|
||||
*/
|
||||
@FeignClient(contextId = "remoteAiModelConfigService", value = ServiceConstants.AI_SERVICE, fallbackFactory = RemoteAiModelConfigFallBack.class)
|
||||
public interface RemoteAiModelConfigService {
|
||||
@GetMapping("/aiModelConfig")
|
||||
Result<PageResult<AiModelConfigVo>> queryPageList(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @SpringQueryMap ReqAiModelConfig reqAiModelConfig, @SpringQueryMap ReqPage reqPage);
|
||||
|
||||
@GetMapping("/aiModelConfig/list")
|
||||
Result<List<AiModelConfig>> queryList(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @SpringQueryMap ReqAiModelConfig reqAiModelConfig);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.com.mfish.ai.api.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "AI模型配置信息请求参数")
|
||||
public class ReqAiModelConfig {
|
||||
@Schema(description = "模型名称: gpt-4o, qwen3:8b, deepseek-v3 等")
|
||||
private String modelName;
|
||||
@Schema(description = "是否启用 1启用 0禁用")
|
||||
private Integer enabled;
|
||||
@Schema(description = "接入协议: openai/ollama/deepseek/anthropic")
|
||||
private String protocol;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.com.mfish.ai.api.vo;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息VO
|
||||
* @author: mfish
|
||||
* @date: 2026/7/8
|
||||
*/
|
||||
public class AiModelConfigVo extends AiModelConfig {
|
||||
public String getApiKey() {
|
||||
String apiKey = super.getApiKey();
|
||||
if (apiKey == null) {
|
||||
return null;
|
||||
}
|
||||
if (apiKey.length() <= 6) {
|
||||
return apiKey.charAt(0) + "****";
|
||||
}
|
||||
return apiKey.substring(0, 3) + "****" + apiKey.substring(apiKey.length() - 3);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.com.mfish.ai.api.fallback.RemoteAiModelConfigFallBack
|
||||
+8
@@ -3,10 +3,13 @@ package cn.com.mfish.common.oauth.api.fallback;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.remote.RemoteMenuService;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 菜单远程调用失败处理
|
||||
* @author: mfish
|
||||
@@ -25,6 +28,11 @@ public class RemoteMenuFallback implements FallbackFactory<RemoteMenuService> {
|
||||
public RemoteMenuService create(Throwable cause) {
|
||||
log.error("菜单服务调用失败:{}", cause.getMessage());
|
||||
return new RemoteMenuService() {
|
||||
@Override
|
||||
public Result<List<SsoMenu>> queryMenuTree(String origin, ReqSsoMenu reqSsoMenu) {
|
||||
return Result.fail("错误:查询菜单树失败" + cause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SsoMenu> add(String origin, SsoMenu ssoMenu) {
|
||||
return Result.fail("错误:保存菜单失败" + cause.getMessage());
|
||||
|
||||
+14
@@ -5,9 +5,13 @@ import cn.com.mfish.common.core.constants.ServiceConstants;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.fallback.RemoteMenuFallback;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 菜单远程接口
|
||||
* @author: mfish
|
||||
@@ -15,6 +19,16 @@ import org.springframework.web.bind.annotation.*;
|
||||
*/
|
||||
@FeignClient(contextId = "remoteMenuService", value = ServiceConstants.OAUTH_SERVICE, fallbackFactory = RemoteMenuFallback.class)
|
||||
public interface RemoteMenuService {
|
||||
|
||||
/**
|
||||
* 查询菜单树
|
||||
* @param origin 来源
|
||||
* @param reqSsoMenu 查询参数
|
||||
* @return 菜单树
|
||||
*/
|
||||
@GetMapping("/menu/tree")
|
||||
Result<List<SsoMenu>> queryMenuTree(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @SpringQueryMap ReqSsoMenu reqSsoMenu);
|
||||
|
||||
/**
|
||||
* 新增菜单
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cn.com.mfish.common.oauth.req;
|
||||
package cn.com.mfish.common.oauth.api.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cn.com.mfish.sys.entity;
|
||||
package cn.com.mfish.sys.api.entity;
|
||||
|
||||
import cn.com.mfish.common.core.entity.BaseEntity;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
+16
-1
@@ -1,11 +1,16 @@
|
||||
package cn.com.mfish.sys.api.fallback;
|
||||
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.api.remote.RemoteDictService;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 远程字典失败处理
|
||||
* @author: mfish
|
||||
@@ -24,6 +29,16 @@ public class RemoteDictFallback implements FallbackFactory<RemoteDictService> {
|
||||
@Override
|
||||
public RemoteDictService create(Throwable cause) {
|
||||
log.error("错误:字典调用异常", cause);
|
||||
return (origin, dictCode) -> Result.fail("错误:查询字典数据出错");
|
||||
return new RemoteDictService() {
|
||||
@Override
|
||||
public Result<List<DictItem>> queryByCode(String origin, String dictCode) {
|
||||
return Result.fail("查询字典项失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<Dict>> queryList(String origin, ReqDict reqDict) {
|
||||
return Result.fail("查询字典列表失败");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ package cn.com.mfish.sys.api.remote;
|
||||
import cn.com.mfish.common.core.constants.RPCConstants;
|
||||
import cn.com.mfish.common.core.constants.ServiceConstants;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.api.fallback.RemoteDictFallback;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
@@ -29,4 +32,14 @@ public interface RemoteDictService {
|
||||
*/
|
||||
@GetMapping("/dictItem/{dictCode}")
|
||||
Result<List<DictItem>> queryByCode(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @Parameter(name = "dictCode", description = "字典编码") @PathVariable String dictCode);
|
||||
|
||||
/**
|
||||
* 查询字典列表
|
||||
*
|
||||
* @param origin 来源
|
||||
* @param reqDict 查询参数
|
||||
* @return 字典列表
|
||||
*/
|
||||
@GetMapping("/dict/list")
|
||||
Result<List<Dict>> queryList(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @SpringQueryMap ReqDict reqDict);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cn.com.mfish.sys.req;
|
||||
package cn.com.mfish.sys.api.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<module>mf-workflow-api</module>
|
||||
<module>mf-nocode-api</module>
|
||||
<module>mf-demo-api</module>
|
||||
<module>mf-ai-api</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
+16
-21
@@ -23,40 +23,35 @@
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<!-- 核心工具(StringUtils、ServiceConstants、Jackson等) -->
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-core</artifactId>
|
||||
</dependency>
|
||||
<!-- AI公共模块(AiConfig、ChatResponseVo、AiRouteService等) -->
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
<!-- 数据源(MyBatis-Plus等,AI模型配置表需要) -->
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-oauth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ds</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
</dependency>
|
||||
<!-- WebFlux替代Servlet,LLM长连接不占用线程 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<!-- WebFlux版Swagger -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
<version>${swagger.doc.version}</version>
|
||||
</dependency>
|
||||
<!-- Jackson序列化(WebFlux不自动引入,需显式声明) -->
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-sys-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-prometheus</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.client.IClientAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI助手注册中心
|
||||
* 自动发现所有IClientAssistant实现,按getPath()注册到路由映射表
|
||||
* 用于单实例模式下替代Servlet forward,直接调用助手实现真正的SSE流式传输
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2026/07/02
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AssistantRegistry {
|
||||
private static final String DEFAULT_PATH = "/ai/agent/chat";
|
||||
|
||||
private final Map<String, IClientAssistant> assistantMap;
|
||||
private final IClientAssistant defaultAssistant;
|
||||
|
||||
public AssistantRegistry(@Autowired(required = false) List<IClientAssistant> assistants) {
|
||||
if (assistants != null && !assistants.isEmpty()) {
|
||||
this.assistantMap = assistants.stream()
|
||||
.collect(Collectors.toMap(IClientAssistant::getPath, a -> a));
|
||||
this.defaultAssistant = assistantMap.get(DEFAULT_PATH);
|
||||
} else {
|
||||
this.assistantMap = Map.of();
|
||||
this.defaultAssistant = null;
|
||||
}
|
||||
if (!assistantMap.isEmpty()) {
|
||||
log.info("[助手注册] 已注册AI助手: {}", assistantMap.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由路径获取助手
|
||||
*
|
||||
* @param path 网关路由路径,如/ai/agent/chat
|
||||
* @return 对应的助手实例,未找到时回退到默认助手
|
||||
*/
|
||||
public IClientAssistant getAssistant(String path) {
|
||||
IClientAssistant assistant = assistantMap.get(path);
|
||||
if (assistant != null) {
|
||||
return assistant;
|
||||
}
|
||||
if (defaultAssistant != null) {
|
||||
log.warn("[助手注册] 未找到路径[{}]对应的助手,回退到默认助手", path);
|
||||
return defaultAssistant;
|
||||
}
|
||||
throw new IllegalStateException("无可用的AI助手,请检查IClientAssistant实现是否已注册");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由路径调用助手聊天
|
||||
*
|
||||
* @param path 网关路由路径
|
||||
* @param aiRequest 聊天请求
|
||||
* @return 流式聊天响应
|
||||
*/
|
||||
public Flux<ChatResponseVo> chat(String path, AiRequest aiRequest) {
|
||||
return getAssistant(path).chat(aiRequest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.common.ai.client.IClientAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description: 系统中心助手
|
||||
* <p>
|
||||
* 按租户路由模型:每次请求实时解析租户、从 LlmModelRouter 取该租户的ChatModel
|
||||
* (ChatModel按配置签名缓存共享,不随租户增长),并实时构建ChatClient。
|
||||
* 不缓存ChatClient,避免为每个租户常驻一份系统提示词+advisor带来的内存膨胀。
|
||||
* <p>
|
||||
* 线程安全:ChatClient每次请求独立构建、用完即弃,无共享可变状态;
|
||||
* 模型在请求线程解析(AuthInfoUtils不支持异步),构建后的引用可安全用于reactive链。
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
public abstract class BaseAssistant implements IClientAssistant {
|
||||
private final ChatMemory chatMemory;
|
||||
private final LlmModelRouter llmModelRouter;
|
||||
|
||||
public BaseAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
this.llmModelRouter = llmModelRouter;
|
||||
this.chatMemory = chatMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子类提供系统提示词
|
||||
*/
|
||||
protected abstract String getSystemPrompt();
|
||||
|
||||
/**
|
||||
* 实时构建当前请求租户对应的ChatClient:从路由取该租户ChatModel,包装系统提示词与advisor。
|
||||
* 不缓存——ChatClient构建本身轻量(仅包装ChatModel与提示词字符串,不重建HTTP客户端),
|
||||
* 实时构建可避免为每个租户常驻一份提示词内存。必须在请求线程调用(AuthInfoUtils不支持异步)。
|
||||
*/
|
||||
protected ChatClient getChatClient() {
|
||||
String tenantId = llmModelRouter.currentTenantId();
|
||||
return ChatClient.builder(llmModelRouter.getChatModel(tenantId))
|
||||
.defaultSystem(getSystemPrompt())
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天返回id
|
||||
*
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponseVo> chat(AiRequest aiRequest) {
|
||||
return chat(aiRequest.getSessionId(), aiRequest.getMessage().getContent())
|
||||
.filter(resp -> "STOP".equals(Objects.requireNonNull(resp.getResult()).getMetadata().getFinishReason())
|
||||
|| StringUtils.isNotEmpty(resp.getResult().getOutput().getText()))
|
||||
.map(resp -> new ChatResponseVo().setId(aiRequest.getId())
|
||||
.setContent(Objects.requireNonNull(resp.getResult()).getOutput().getText())
|
||||
.setFinishReason(resp.getResult().getMetadata().getFinishReason())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.ResponseEntity;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 摸鱼小助手配置
|
||||
* <p>
|
||||
* 按租户路由模型:每次请求实时解析租户、从 LlmModelRouter 取该租户的ChatModel
|
||||
* (ChatModel按配置签名缓存共享,不随租户增长),并实时构建ChatClient。
|
||||
* 不缓存ChatClient,避免为每个租户常驻一份系统提示词+advisor带来的内存膨胀。
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2025/8/21
|
||||
*/
|
||||
@Component
|
||||
public class GatewayAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下摸鱼低代码";
|
||||
private static final String SYSTEM_PROMPT = """
|
||||
你是"摸鱼低代码"的路由小助手,你会根据用户的问题,判断用户需要访问的路由路径!
|
||||
当有人问"摸鱼低代码"相关信息时,实际是在问我们整个平台的信息
|
||||
你只能返回路由路径,不能返回其他内容!
|
||||
结果以json格式返回:
|
||||
返回格式:{"path":"/ai/agent/chat","name":"摸鱼小助手","serviceId":"mf-ai"}
|
||||
|
||||
# 路由路径
|
||||
1. /ai/agent/chat : 摸鱼小助手 默认路由 对应微服务mf-ai
|
||||
2. /ai/sys/assist : 系统信息 对应微服务mf-ai 系统信息主要包括(字典信息、分类目录信息、日志信息、代码生成功能、在线用户信息、数据库连接信息、数据源信息 等)
|
||||
3. /ai/oauth2/assist : 认证信息 对应微服务mf-ai 认证信息主要包括(菜单信息、组织信息、角色信息、帐号信息、租户信息 等)
|
||||
4. /ai/nocode/assist : 低代码信息 对应微服务mf-ai 低代码信息主要包括(自助大屏、自助API、组件管理、公式信息 等)
|
||||
|
||||
```
|
||||
如果用户需要查询字典相关信息、分类目录相关信息、日志相关信息等系统信息,返回系统中心助手的路由路径
|
||||
如果用户需要查询菜单相关信息、组织相关信息、角色相关信息、帐号相关信息、租户相关信息等认证中心信息,返回认证中心助手的路由路径
|
||||
如果用户需要查询自助大屏、自助API、数据集、可视化等低代码中心信息,返回低代码中心助手的路由路径
|
||||
```
|
||||
|
||||
注意:
|
||||
返回的路由路径必须上失眠列出来的路由路径
|
||||
如果当前问题与路由路径无关,分析下之前的问题是否是之前问题的补充,如果是则返回上一次的路由路径
|
||||
如果不知道用户需要访问的路由路径,默认返回:{"path":"/ai/agent/chat","name":"摸鱼小助手","serviceId":"mf-ai"}
|
||||
路由路径不能包含空格
|
||||
请使用中文回答
|
||||
""";
|
||||
|
||||
private final ChatMemory chatMemory;
|
||||
private final LlmModelRouter llmModelRouter;
|
||||
|
||||
public GatewayAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
this.chatMemory = chatMemory;
|
||||
this.llmModelRouter = llmModelRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时构建当前请求租户对应的ChatClient。不缓存,避免租户级提示词内存膨胀。
|
||||
* 必须在请求线程调用(AuthInfoUtils不支持异步)。
|
||||
*/
|
||||
private ChatClient getChatClient() {
|
||||
String tenantId = llmModelRouter.currentTenantId();
|
||||
return ChatClient.builder(llmModelRouter.getChatModel(tenantId))
|
||||
.defaultSystem(SYSTEM_PROMPT)
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
public Mono<Result<AiRouterVo>> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String finalPrompt = prompt;
|
||||
// 在请求线程解析租户并构建ChatClient,避免在reactive异步线程中获取租户
|
||||
ChatClient chatClient = getChatClient();
|
||||
return Mono.fromCallable(() -> {
|
||||
ResponseEntity<ChatResponse, AiRouterVo> responseEntity = chatClient.prompt()
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.call()
|
||||
.responseEntity(AiRouterVo.class);
|
||||
AiRouterVo vo = Objects.requireNonNullElseGet(responseEntity.entity(), AiRouterVo::new);
|
||||
return Result.ok(vo);
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 摸鱼小助手配置
|
||||
* @author: mfish
|
||||
* @date: 2025/8/21
|
||||
*/
|
||||
@Component
|
||||
public class MfishAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下摸鱼低代码";
|
||||
|
||||
public MfishAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
super(chatMemory, llmModelRouter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSystemPrompt() {
|
||||
return """
|
||||
你是“摸鱼低代码”的小助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
摸鱼低代码平台,是一款致力于让开发像摸鱼一样轻松的低代码/无代码平台。
|
||||
我们希望打破技术门槛,让程序员和非程序员都能快速构建业务系统,提升效率,释放创造力。
|
||||
这不仅是程序员偷闲时的效率神器,更是职场小白的建站利器,甚至是领导画原型的秘密武器!
|
||||
我可以扮演以下角色:
|
||||
1. 低代码开发人员
|
||||
2. 非技术人员(如产品经理、业务分析师等)
|
||||
3. 技术支持人员
|
||||
4. 客户服务
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
return this.getChatClient().prompt().user(prompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.stream().chatResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/ai/agent/chat";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 低代码中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Component
|
||||
public class NoCodeAssistant extends BaseAssistant {
|
||||
/**
|
||||
* 默认提示语
|
||||
*/
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下低代码中心助手";
|
||||
|
||||
/**
|
||||
* 构造低代码助手,初始化聊天客户端及系统提示词
|
||||
*
|
||||
* @param chatMemory 聊天记忆
|
||||
*/
|
||||
public NoCodeAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
super(chatMemory, llmModelRouter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSystemPrompt() {
|
||||
return """
|
||||
你是低代码中心助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
你主要辅助用户完成低代码中心的一些基础操作
|
||||
你能够通过调用工具来完成低代码中心基础操作,包括查询、新增、修改、删除、导入、导出等
|
||||
你主要辅助用户完成低代码中心相关模块的信息检索、执行操作
|
||||
低代码中心主要包含 (自助大屏、自助API、组件管理、公式信息 等)
|
||||
|
||||
注意:
|
||||
1. 我需要你调用工具来帮我回答问题,不要自己编造答案
|
||||
2. 不要随意修改查询结果,只返回工具返回的内容
|
||||
3. 如果未找到相应工具,告诉用户你执行不了这个操作
|
||||
4. 如果工具参数缺失,提示用户输入缺失参数,引导用户输入有用信息
|
||||
5. 请使用中文回答
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
return this.getChatClient().prompt()
|
||||
.system("你必须先调用工具,再基于工具结果回答问题")
|
||||
.user(prompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.stream()
|
||||
.chatResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/ai/nocode/assist";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.ai.tools.MenuTools;
|
||||
import cn.com.mfish.ai.tools.UserTools;
|
||||
import cn.com.mfish.common.core.utils.AuthInfoUtils;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.oauth.api.remote.RemoteUserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 认证中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OauthAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下认证中心助手";
|
||||
@Resource
|
||||
MenuTools menuTools;
|
||||
@Resource
|
||||
RemoteUserService remoteUserService;
|
||||
|
||||
|
||||
public OauthAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
super(chatMemory, llmModelRouter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSystemPrompt() {
|
||||
return """
|
||||
你是“摸鱼低代码”的认证中心助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
你主要辅助用户完成认证中心的一些基础操作
|
||||
你能够通过调用工具来完成认证中心基础操作,包括查询、新增、修改、删除、导入、导出等
|
||||
你主要辅助用户完成认证中心相关模块的信息检索、执行操作
|
||||
认证中心主要包含 (菜单信息、组织信息、角色信息、帐号信息、租户信息 等)
|
||||
|
||||
注意:
|
||||
1. 我需要你调用工具来帮我回答问题,不要自己编造答案
|
||||
2. 不要随意修改查询结果,只返回工具返回的内容
|
||||
3. 如果未找到相应工具,告诉用户你执行不了这个操作
|
||||
4. 如果工具参数缺失,提示用户输入缺失参数,引导用户输入有用信息
|
||||
5. 请使用中文回答
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String userId = AuthInfoUtils.getCurrentUserId();
|
||||
String tenantId = AuthInfoUtils.getCurrentTenantId();
|
||||
String finalPrompt = prompt;
|
||||
ChatClient.ChatClientRequestSpec requestSpec = this.getChatClient().prompt()
|
||||
.system("你必须先调用工具,再基于工具结果回答问题")
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.tools(menuTools, new UserTools(userId, tenantId, remoteUserService));
|
||||
return requestSpec
|
||||
.stream()
|
||||
.chatResponse()
|
||||
.onErrorResume(e -> {
|
||||
log.warn("流式调用失败,降级为非流式调用: {}", e.getMessage());
|
||||
return Mono.fromCallable(() -> requestSpec.call().chatResponse())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flux();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/ai/oauth2/assist";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cn.com.mfish.ai.agent;
|
||||
|
||||
import cn.com.mfish.ai.service.LlmModelRouter;
|
||||
import cn.com.mfish.ai.tools.DictTools;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 系统中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SysAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下系统中心助手";
|
||||
|
||||
@Resource
|
||||
private DictTools dictTools;
|
||||
|
||||
public SysAssistant(ChatMemory chatMemory, LlmModelRouter llmModelRouter) {
|
||||
super(chatMemory, llmModelRouter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSystemPrompt() {
|
||||
return """
|
||||
你是“摸鱼低代码”的系统中心助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
你主要辅助用户完成系统中心的一些基础操作
|
||||
你能够通过调用工具来完成系统中心基础操作,包括查询、新增、修改、删除等
|
||||
你主要辅助用户完成系统中心相关模块的信息检索、执行操作
|
||||
系统中心主要包含 字典信息 分类目录信息 日志信息 代码生成功能 在线用户信息 数据库连接信息 数据源信息
|
||||
|
||||
注意:
|
||||
1. 我需要你调用工具来帮我回答问题,不要自己编造答案
|
||||
2. 不要随意修改查询结果,只返回工具返回的内容
|
||||
3. 如果未找到相应工具,告诉用户你执行不了这个操作
|
||||
4. 如果工具参数缺失,提示用户输入缺失参数,引导用户输入有用信息
|
||||
5. 请使用中文回答
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String finalPrompt = prompt;
|
||||
ChatClient.ChatClientRequestSpec requestSpec = this.getChatClient().prompt()
|
||||
.system("你必须先调用工具,再基于工具结果回答问题")
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.tools(dictTools);
|
||||
return
|
||||
requestSpec.stream().chatResponse()
|
||||
.onErrorResume(e -> {
|
||||
log.warn("流式调用失败,降级为非流式调用: {}", e.getMessage());
|
||||
return Mono.fromCallable(() -> requestSpec.call().chatResponse())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flux();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/ai/sys/assist";
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package cn.com.mfish.ai.config;
|
||||
|
||||
import cn.com.mfish.common.core.utils.ServletUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* @description: Reactor上下文传播配置
|
||||
* 解决WebFlux响应式编程中subscribeOn/publishOn切换线程导致ThreadLocal丢失的问题
|
||||
* <p>
|
||||
* 支持两种运行模式:
|
||||
* 1. WebFlux模式:传播 ServerWebExchange 的 ThreadLocal
|
||||
* 2. Servlet单实例模式:传播 RequestContextHolder 的 ThreadLocal
|
||||
* </p>
|
||||
* @author: mfish
|
||||
* @date: 2026/7/6
|
||||
*/
|
||||
@Configuration
|
||||
public class ReactorContextPropagationConfig {
|
||||
private static final String SCHEDULE_HOOK_KEY = "mfish.context.propagation";
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Schedulers.onScheduleHook(SCHEDULE_HOOK_KEY, runnable -> {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
ServerWebExchange exchange = ServletUtils.getExchange();
|
||||
if (requestAttributes == null && exchange == null) {
|
||||
return runnable;
|
||||
}
|
||||
return () -> {
|
||||
if (requestAttributes != null) {
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
}
|
||||
if (exchange != null) {
|
||||
ServletUtils.setExchange(exchange);
|
||||
}
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
if (requestAttributes != null) {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
if (exchange != null) {
|
||||
ServletUtils.removeExchange();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.agent.MfishAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @description: 摸鱼小助手
|
||||
* @author: mfish
|
||||
* @date: 2026/7/2
|
||||
*/
|
||||
@Tag(name = "摸鱼小助手")
|
||||
@RestController
|
||||
@RequestMapping("/agent")
|
||||
@RequiredArgsConstructor
|
||||
public class AiController {
|
||||
private final MfishAssistant mfishAssistant;
|
||||
/**
|
||||
* 与摸鱼小助手聊天
|
||||
*
|
||||
* @param aiRequest AI请求参数,包含会话ID和提示词
|
||||
* @return 聊天响应流
|
||||
*/
|
||||
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ChatResponseVo> chat(@RequestBody AiRequest aiRequest) {
|
||||
return mfishAssistant.chat(aiRequest);
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.common.ai.service.AiModelConfigService;
|
||||
import cn.com.mfish.common.core.annotation.InnerUser;
|
||||
import cn.com.mfish.common.core.enums.OperateType;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.log.annotation.Log;
|
||||
import cn.com.mfish.common.oauth.annotation.DataScope;
|
||||
import cn.com.mfish.common.oauth.annotation.RequiresPermissions;
|
||||
import cn.com.mfish.common.oauth.common.DataScopeType;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "AI模型配置信息")
|
||||
@RestController
|
||||
@RequestMapping("/aiModelConfig")
|
||||
public class AiModelConfigController {
|
||||
@Resource
|
||||
private AiModelConfigService aiModelConfigService;
|
||||
|
||||
/**
|
||||
* 分页列表查询(脱敏处理)
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @return 返回AI模型配置信息-分页列表
|
||||
*/
|
||||
@Operation(summary = "AI模型配置信息-分页列表查询", description = "AI模型配置信息-分页列表查询")
|
||||
@GetMapping
|
||||
@RequiresPermissions("ai:aiModelConfig:query")
|
||||
@DataScope(table = "ai_model_config", type = DataScopeType.Tenant, excludes = {"tenant_id = '1'"}, superIgnore = true)
|
||||
public Result<PageResult<AiModelConfigVo>> queryPageList(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) {
|
||||
return aiModelConfigService.queryPageList(reqAiModelConfig, reqPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部列表查询(不脱敏)
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @return 返回AI模型配置信息-列表
|
||||
*/
|
||||
@Operation(summary = "AI模型配置信息-列表查询", description = "AI模型配置信息-列表查询")
|
||||
@GetMapping("/list")
|
||||
@InnerUser
|
||||
public Result<List<AiModelConfig>> queryList(ReqAiModelConfig reqAiModelConfig) {
|
||||
return Result.ok(aiModelConfigService.queryList(reqAiModelConfig));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-添加结果
|
||||
*/
|
||||
@Log(title = "AI模型配置信息-添加", operateType = OperateType.INSERT)
|
||||
@Operation(summary = "AI模型配置信息-添加")
|
||||
@PostMapping
|
||||
@RequiresPermissions("ai:aiModelConfig:insert")
|
||||
public Result<AiModelConfig> add(@RequestBody AiModelConfig aiModelConfig) {
|
||||
return aiModelConfigService.add(aiModelConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-编辑结果
|
||||
*/
|
||||
@Log(title = "AI模型配置信息-编辑", operateType = OperateType.UPDATE)
|
||||
@Operation(summary = "AI模型配置信息-编辑")
|
||||
@PutMapping
|
||||
@RequiresPermissions("ai:aiModelConfig:update")
|
||||
public Result<AiModelConfig> edit(@RequestBody AiModelConfig aiModelConfig) {
|
||||
return aiModelConfigService.edit(aiModelConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
@Log(title = "AI模型配置信息-通过id删除", operateType = OperateType.DELETE)
|
||||
@Operation(summary = "AI模型配置信息-通过id删除")
|
||||
@DeleteMapping("/{id}")
|
||||
@RequiresPermissions("ai:aiModelConfig:delete")
|
||||
public Result<Boolean> delete(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
|
||||
return aiModelConfigService.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids 批量ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
@Log(title = "AI模型配置信息-批量删除", operateType = OperateType.DELETE)
|
||||
@Operation(summary = "AI模型配置信息-批量删除")
|
||||
@DeleteMapping("/batch/{ids}")
|
||||
@RequiresPermissions("ai:aiModelConfig:delete")
|
||||
public Result<Boolean> deleteBatch(@Parameter(name = "ids", description = "唯一性ID") @PathVariable String ids) {
|
||||
return aiModelConfigService.deleteBatch(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询(不脱敏)
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息对象
|
||||
*/
|
||||
@Operation(summary = "AI模型配置信息-通过id查询")
|
||||
@GetMapping("/{id}")
|
||||
@InnerUser
|
||||
public Result<AiModelConfig> queryById(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
|
||||
return aiModelConfigService.queryById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询(脱敏处理)
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息对象
|
||||
*/
|
||||
@Operation(summary = "AI模型配置信息-通过id查询")
|
||||
@GetMapping("/mask/{id}")
|
||||
@RequiresPermissions("ai:aiModelConfig:query")
|
||||
@DataScope(table = "ai_model_config", type = DataScopeType.Tenant, excludes = {"tenant_id = '1'"}, superIgnore = true)
|
||||
public Result<AiModelConfigVo> queryByIdVo(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
|
||||
return aiModelConfigService.queryByIdVo(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
@Operation(summary = "导出AI模型配置信息", description = "导出AI模型配置信息")
|
||||
@GetMapping("/export")
|
||||
@RequiresPermissions("ai:aiModelConfig:export")
|
||||
public void export(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) throws IOException {
|
||||
aiModelConfigService.export(reqAiModelConfig, reqPage);
|
||||
}
|
||||
}
|
||||
+14
-36
@@ -8,8 +8,8 @@ import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -31,13 +31,6 @@ import java.util.UUID;
|
||||
/**
|
||||
* LLM统一代理控制器(WebFlux版)
|
||||
* 对外提供OpenAI兼容的 /v1/chat/completions 接口
|
||||
* <p>
|
||||
* 改进:
|
||||
* 1. 多模型路由 + fallback链(通过LlmModelRouter)
|
||||
* 2. 使用Spring AI Message列表保持对话结构
|
||||
* 3. 使用Jackson ObjectMapper序列化,替代手工拼接JSON
|
||||
* 4. 支持流式(SSE)和非流式(JSON)双模式
|
||||
* 5. WebFlux非Servlet架构,LLM长连接不占用Servlet线程
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2026/07/01
|
||||
@@ -116,8 +109,7 @@ public class LlmProxyController {
|
||||
.map(chatResponse -> buildResponse(completionId, model, chatResponse))
|
||||
.onErrorResume(ex -> {
|
||||
log.error("[LLM代理] 同步调用失败", ex);
|
||||
return Mono.just(buildErrorResponse(completionId, model,
|
||||
"AI服务暂时不可用,请稍后再试。"));
|
||||
return Mono.just(buildResponse(completionId, model, "AI服务暂时不可用,请稍后再试。"));
|
||||
})
|
||||
.map(resp -> response.bufferFactory()
|
||||
.wrap(writeJson(resp).getBytes(StandardCharsets.UTF_8)))
|
||||
@@ -147,7 +139,7 @@ public class LlmProxyController {
|
||||
}
|
||||
|
||||
private Prompt buildPrompt(List<Message> messages, ChatCompletionDto.Request request) {
|
||||
OpenAiChatOptions options = OpenAiChatOptions.builder()
|
||||
ChatOptions options = ChatOptions.builder()
|
||||
.model(request.getModel() != null ? request.getModel() : "default")
|
||||
.maxTokens(request.getMaxTokens() != null ? request.getMaxTokens() : 1000)
|
||||
.temperature(request.getTemperature() != null ? request.getTemperature() : 0.7)
|
||||
@@ -173,36 +165,22 @@ public class LlmProxyController {
|
||||
}
|
||||
|
||||
private ChatCompletionDto.Response buildResponse(String id, String model, ChatResponse chatResponse) {
|
||||
ChatCompletionDto.Response response = new ChatCompletionDto.Response();
|
||||
response.setId(id);
|
||||
response.setCreated(Instant.now().getEpochSecond());
|
||||
response.setModel(model != null ? model : "default");
|
||||
|
||||
ChatCompletionDto.ResponseChoice choice = new ChatCompletionDto.ResponseChoice();
|
||||
choice.setIndex(0);
|
||||
ChatCompletionDto.ResponseMessage message = new ChatCompletionDto.ResponseMessage();
|
||||
message.setContent(extractContent(chatResponse));
|
||||
choice.setMessage(message);
|
||||
choice.setFinishReason("stop");
|
||||
response.setChoices(List.of(choice));
|
||||
|
||||
ChatCompletionDto.Response response = buildResponse(id, model, extractContent(chatResponse));
|
||||
// 尝试提取usage信息
|
||||
try {
|
||||
if (chatResponse.getMetadata() != null && chatResponse.getMetadata().getUsage() != null) {
|
||||
var usageData = chatResponse.getMetadata().getUsage();
|
||||
ChatCompletionDto.Usage usage = new ChatCompletionDto.Usage();
|
||||
usage.setPromptTokens(usageData.getPromptTokens().intValue());
|
||||
usage.setCompletionTokens(usageData.getCompletionTokens().intValue());
|
||||
usage.setTotalTokens(usageData.getTotalTokens().intValue());
|
||||
response.setUsage(usage);
|
||||
}
|
||||
var usageData = chatResponse.getMetadata().getUsage();
|
||||
ChatCompletionDto.Usage usage = new ChatCompletionDto.Usage();
|
||||
usage.setPromptTokens(usageData.getPromptTokens());
|
||||
usage.setCompletionTokens(usageData.getCompletionTokens());
|
||||
usage.setTotalTokens(usageData.getTotalTokens());
|
||||
response.setUsage(usage);
|
||||
} catch (Exception e) {
|
||||
log.debug("[LLM代理] 获取usage信息失败,跳过", e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private ChatCompletionDto.Response buildErrorResponse(String id, String model, String errorMessage) {
|
||||
private ChatCompletionDto.Response buildResponse(String id, String model, String message) {
|
||||
ChatCompletionDto.Response response = new ChatCompletionDto.Response();
|
||||
response.setId(id);
|
||||
response.setCreated(Instant.now().getEpochSecond());
|
||||
@@ -210,9 +188,9 @@ public class LlmProxyController {
|
||||
|
||||
ChatCompletionDto.ResponseChoice choice = new ChatCompletionDto.ResponseChoice();
|
||||
choice.setIndex(0);
|
||||
ChatCompletionDto.ResponseMessage message = new ChatCompletionDto.ResponseMessage();
|
||||
message.setContent(errorMessage);
|
||||
choice.setMessage(message);
|
||||
ChatCompletionDto.ResponseMessage responseMessage = new ChatCompletionDto.ResponseMessage();
|
||||
responseMessage.setContent(message);
|
||||
choice.setMessage(responseMessage);
|
||||
choice.setFinishReason("stop");
|
||||
response.setChoices(List.of(choice));
|
||||
return response;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.agent.NoCodeAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @description: 低代码中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Tag(name = "低代码中心助手")
|
||||
@RestController
|
||||
@RequestMapping("/nocode")
|
||||
@RequiredArgsConstructor
|
||||
public class NoCodeAiController {
|
||||
|
||||
private final NoCodeAssistant noCodeAssistant;
|
||||
|
||||
/**
|
||||
* 低代码AI助手对话接口(流式响应)
|
||||
*
|
||||
* @param aiRequest AI请求参数
|
||||
* @return 返回流式对话响应
|
||||
*/
|
||||
@PostMapping(value = "/assist", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ChatResponseVo> noCodeAssistant(@RequestBody AiRequest aiRequest) {
|
||||
return noCodeAssistant.chat(aiRequest);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package cn.com.mfish.oauth.controller;
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.agent.OauthAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import cn.com.mfish.oauth.agent.OauthAssistant;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -19,7 +19,7 @@ import reactor.core.publisher.Flux;
|
||||
*/
|
||||
@Tag(name = "认证小助手")
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
@RequestMapping("/oauth2")
|
||||
@RequiredArgsConstructor
|
||||
public class OauthAiController {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.agent.AssistantRegistry;
|
||||
import cn.com.mfish.ai.service.AiRouteService;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import cn.com.mfish.common.prom.annotation.MetricsMonitor;
|
||||
import cn.com.mfish.common.prom.annotation.MetricsMonitors;
|
||||
import cn.com.mfish.common.prom.enums.MetricEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* @description: 路由控制器
|
||||
* @author: mfish
|
||||
* @date: 2026/7/6
|
||||
*/
|
||||
@Tag(name = "路由控制器")
|
||||
@RestController
|
||||
@RequestMapping("/router")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class RouterController {
|
||||
@Resource
|
||||
AiRouteService aiRouteService;
|
||||
@Resource
|
||||
AssistantRegistry assistantRegistry;
|
||||
|
||||
/**
|
||||
* AI智能路由(单实例模式)
|
||||
* 同步阻塞等待AI路由决策完成后,通过AssistantRegistry直接调用目标助手,
|
||||
* 返回Flux<ChatResponseVo>由Spring MVC原生处理SSE流式响应,
|
||||
* 避免Servlet forward缓冲整个响应导致流式失效的问题
|
||||
*/
|
||||
@Operation(summary = "Ai路由", description = "Ai路由")
|
||||
@PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@MetricsMonitors({
|
||||
@MetricsMonitor(metricEnum = MetricEnum.MFISH_REQUEST_COUNT, tagValues = {"POST", "/ai/router"}),
|
||||
@MetricsMonitor(metricEnum = MetricEnum.MFISH_REQUEST_DURATION, tagValues = {"POST", "/ai/router"})
|
||||
})
|
||||
public Flux<ChatResponseVo> aiRouter(@RequestBody AiRequest aiRequest) {
|
||||
String sessionId = aiRequest.getSessionId();
|
||||
String prompt = aiRequest.getMessage() != null ? aiRequest.getMessage().getContent() : null;
|
||||
log.info("[AI路由-单实例] 接收到智能路由请求, sessionId={}, prompt={}", sessionId, prompt);
|
||||
|
||||
return aiRouteService.resolve(sessionId, prompt)
|
||||
.defaultIfEmpty(new AiRouterVo())
|
||||
.flatMapMany(vo -> {
|
||||
String targetPath = vo.getPath();
|
||||
log.info("[AI路由-单实例] 路由到, path={}", targetPath);
|
||||
return assistantRegistry.chat(targetPath, aiRequest);
|
||||
});
|
||||
}
|
||||
}
|
||||
+3
-17
@@ -1,9 +1,8 @@
|
||||
package cn.com.mfish.sys.controller;
|
||||
package cn.com.mfish.ai.controller;
|
||||
|
||||
import cn.com.mfish.ai.agent.SysAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import cn.com.mfish.sys.agent.MfishAssistant;
|
||||
import cn.com.mfish.sys.agent.SysAssistant;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -20,25 +19,12 @@ import reactor.core.publisher.Flux;
|
||||
*/
|
||||
@Tag(name = "系统小助手")
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
@RequestMapping("/sys")
|
||||
@RequiredArgsConstructor
|
||||
public class SysAiController {
|
||||
|
||||
private final MfishAssistant mfishAssistant;
|
||||
|
||||
private final SysAssistant sysAssistant;
|
||||
|
||||
/**
|
||||
* 与摸鱼小助手聊天
|
||||
*
|
||||
* @param aiRequest AI请求参数,包含会话ID和提示词
|
||||
* @return 聊天响应流
|
||||
*/
|
||||
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ChatResponseVo> chat(@RequestBody AiRequest aiRequest) {
|
||||
return mfishAssistant.chat(aiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 与系统中心助手聊天
|
||||
*
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.com.mfish.ai.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* 数据库中的AI模型配置发生变更(新增/编辑/删除)时发布此事件,
|
||||
* 用于触发 {@code LlmModelRouter} 重新加载模型。
|
||||
*
|
||||
* @author mfish
|
||||
*/
|
||||
public class AiModelChangedEvent extends ApplicationEvent {
|
||||
public AiModelChangedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package cn.com.mfish.ai.filter;
|
||||
|
||||
import cn.com.mfish.common.core.utils.ServletUtils;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @description: 请求上下文过滤器(WebFlux模式)
|
||||
* 负责将ServerWebExchange写入ThreadLocal,
|
||||
* 线程切换时的传播由ReactorContextPropagationConfig统一处理
|
||||
* @author: mfish
|
||||
* @date: 2026/7/3
|
||||
*/
|
||||
@Component
|
||||
public class ReactiveRequestContextFilter implements WebFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public @NonNull Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
if (isExcludedPath(path)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
ServletUtils.setExchange(exchange);
|
||||
return chain.filter(exchange)
|
||||
.doFinally(signal -> ServletUtils.removeExchange());
|
||||
}
|
||||
private boolean isExcludedPath(String path) {
|
||||
return path.startsWith("/actuator") || path.startsWith("/health");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.com.mfish.ai.mapper;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
public interface AiModelConfigMapper extends BaseMapper<AiModelConfig> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.com.mfish.ai.mapper.AiModelConfigMapper">
|
||||
|
||||
</mapper>
|
||||
+5
-7
@@ -1,10 +1,9 @@
|
||||
package cn.com.mfish.common.ai.service;
|
||||
package cn.com.mfish.ai.service;
|
||||
|
||||
import cn.com.mfish.common.ai.agent.GatewayAssistant;
|
||||
import cn.com.mfish.ai.agent.GatewayAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.core.constants.ServiceConstants;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -59,7 +58,7 @@ public class AiRouteService {
|
||||
AiRouterVo cached = routeCache.getIfPresent(prompt);
|
||||
if (cached != null) {
|
||||
// 缓存结果也需校验,防止历史缓存中存在无效serviceId
|
||||
cached = validateAndFix(cached);
|
||||
validateAndFix(cached);
|
||||
log.info("[AI路由] 命中缓存, serviceId={}, path={}", cached.getServiceId(), cached.getPath());
|
||||
return Mono.just(cached);
|
||||
}
|
||||
@@ -74,7 +73,7 @@ public class AiRouteService {
|
||||
log.warn("[AI路由] LLM返回空结果,使用默认路由");
|
||||
vo = new AiRouterVo();
|
||||
}
|
||||
vo = validateAndFix(vo);
|
||||
validateAndFix(vo);
|
||||
log.info("[AI路由] LLM决策结果, serviceId={}, path={}, name={}", vo.getServiceId(), vo.getPath(), vo.getName());
|
||||
routeCache.put(finalPrompt, vo);
|
||||
return vo;
|
||||
@@ -89,12 +88,11 @@ public class AiRouteService {
|
||||
* 校验LLM返回的路由结果,修正无效的serviceId
|
||||
* LLM可能幻觉出不存在的服务名(如mf-ai),需要校验并回退到默认值
|
||||
*/
|
||||
private AiRouterVo validateAndFix(AiRouterVo vo) {
|
||||
private void validateAndFix(AiRouterVo vo) {
|
||||
if (!ServiceConstants.MfService.isValid(vo.getServiceId())) {
|
||||
log.warn("[AI路由] LLM返回无效serviceId='{}',不在有效枚举{}中,回退到默认值{}",
|
||||
vo.getServiceId(), ServiceConstants.MfService.allServiceIds(), ServiceConstants.MfService.SYS.getValue());
|
||||
vo.setServiceId(ServiceConstants.MfService.SYS.getValue());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,36 @@
|
||||
package cn.com.mfish.ai.service;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.event.AiModelChangedEvent;
|
||||
import cn.com.mfish.common.ai.service.AiModelConfigService;
|
||||
import cn.com.mfish.common.ai.service.LlmModelFactory;
|
||||
import cn.com.mfish.common.core.exception.MyRuntimeException;
|
||||
import cn.com.mfish.common.core.utils.AuthInfoUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* LLM多模型路由服务
|
||||
* <p>
|
||||
* 通过ApplicationContext自动发现所有ChatModel Bean,Bean名称自动转提供者名称:
|
||||
* - "openAiChatModel" → "openai"
|
||||
* - "ollamaChatModel" → "ollama"
|
||||
* - "zhiPuAiChatModel" → "zhipuai"
|
||||
* 按租户维度路由:数据库中 tenantId 为空的配置属于全局模型,作为未配置专属模型租户的兜底;
|
||||
* 每个租户拥有独立的ChatModel列表,按sortOrder决定fallback优先级。
|
||||
* <p>
|
||||
* 模型名→提供者的映射支持两种来源:
|
||||
* 1. 内置前缀规则(默认兜底)
|
||||
* 2. 数据库配置(动态刷新,优先级更高)
|
||||
* 创建性能优化:通过配置签名缓存已构建的ChatModel,重建时仅构建新增/变更的配置,
|
||||
* 未变更的ChatModel直接复用,避免重复创建HTTP客户端等昂贵对象。
|
||||
* <p>
|
||||
* fallback顺序:优先使用模型名对应的提供者,失败后按注册顺序依次降级
|
||||
* 线程安全:租户模型表为 volatile 不可变快照,读操作无锁;重建整体替换引用。
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2026/07/01
|
||||
@@ -33,190 +38,189 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Service
|
||||
@Slf4j
|
||||
public class LlmModelRouter {
|
||||
private final ChatModel openaiChatModel;
|
||||
private final AiModelConfigService aiModelConfigService;
|
||||
/**
|
||||
* 租户 → 该租户可用的ChatModel列表(不可变快照)。
|
||||
* 通过 volatile 发布,读操作无锁且线程安全;写操作整体替换引用。
|
||||
*/
|
||||
private volatile Map<String, List<ChatModel>> tenantProviders = Map.of();
|
||||
/**
|
||||
* 配置签名 → 已构建ChatModel 的缓存(仅在 rebuildLock 内访问)。
|
||||
* 重建时优先复用未变更配置对应的ChatModel,避免重复构建HTTP客户端等昂贵对象。
|
||||
* 被发布到快照的ChatModel对象即使后续从缓存移除,也不影响正在使用它的请求(它们持有对象引用)。
|
||||
*/
|
||||
private final Map<String, ChatModel> modelCache = new HashMap<>();
|
||||
/**
|
||||
* 串行化重建动作。读操作不获取此锁,因此重建期间读取线程仍可使用旧快照,不受阻塞。
|
||||
*/
|
||||
private final ReentrantLock rebuildLock = new ReentrantLock();
|
||||
|
||||
/** Bean名称后缀 → 提供者名称的转换规则 */
|
||||
private static final String BEAN_SUFFIX = "ChatModel";
|
||||
public LlmModelRouter(ChatModel openAiChatModel, AiModelConfigService aiModelConfigService) {
|
||||
this.openaiChatModel = openAiChatModel;
|
||||
this.aiModelConfigService = aiModelConfigService;
|
||||
}
|
||||
|
||||
/** 提供者名称 → ChatModel实例 */
|
||||
private final Map<String, ChatModel> providers = new LinkedHashMap<>();
|
||||
/** fallback顺序(按Bean注册顺序) */
|
||||
private final List<String> fallbackOrder;
|
||||
|
||||
/** 模型名前缀 → 提供者名称的映射(内置默认规则) */
|
||||
private final Map<String, String> defaultPrefixMap = new LinkedHashMap<>();
|
||||
/** 模型名前缀 → 提供者名称的映射(数据库动态配置,优先级高于默认) */
|
||||
private final Map<String, String> dynamicPrefixMap = new ConcurrentHashMap<>();
|
||||
|
||||
public LlmModelRouter(ApplicationContext applicationContext) {
|
||||
// 自动发现所有ChatModel Bean
|
||||
Map<String, ChatModel> beans = applicationContext.getBeansOfType(ChatModel.class);
|
||||
for (Map.Entry<String, ChatModel> entry : beans.entrySet()) {
|
||||
String providerName = beanNameToProvider(entry.getKey());
|
||||
this.providers.put(providerName, entry.getValue());
|
||||
log.info("[LLM路由] 注册模型提供者: {} ({})", providerName, entry.getKey());
|
||||
}
|
||||
this.fallbackOrder = List.copyOf(this.providers.keySet());
|
||||
|
||||
// 内置默认前缀规则
|
||||
initDefaultPrefixMap();
|
||||
|
||||
log.info("[LLM路由] 已注册提供者: {}, fallback顺序: {}", providers.keySet(), fallbackOrder);
|
||||
@PostConstruct
|
||||
public void initProviders() {
|
||||
doInitProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean名称转提供者名称
|
||||
* "openAiChatModel" → "openai"
|
||||
* "ollamaChatModel" → "ollama"
|
||||
* "zhiPuAiChatModel" → "zhipuai"
|
||||
* 监听数据库模型配置变更事件,重新加载模型。
|
||||
* 助手不再缓存ChatClient(每请求实时构建),因此重载后无需通知助手刷新;
|
||||
* 下次请求自动取到新的ChatModel。
|
||||
*/
|
||||
private String beanNameToProvider(String beanName) {
|
||||
if (beanName.endsWith(BEAN_SUFFIX)) {
|
||||
beanName = beanName.substring(0, beanName.length() - BEAN_SUFFIX.length());
|
||||
@EventListener
|
||||
public void onModelChanged(AiModelChangedEvent event) {
|
||||
log.info("[LLM路由] 收到模型变更事件,开始重新初始化");
|
||||
try {
|
||||
doInitProviders();
|
||||
} catch (Exception e) {
|
||||
log.error("[LLM路由] 重新初始化失败,保持现有模型配置", e);
|
||||
return;
|
||||
}
|
||||
// 驼峰转小写,首字母小写
|
||||
if (beanName.isEmpty()) {
|
||||
return "default";
|
||||
}
|
||||
// 将驼峰转为下划线再连成小写:openAi → open_ai → openai
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < beanName.length(); i++) {
|
||||
char c = beanName.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
if (i > 0) {
|
||||
sb.append('_');
|
||||
log.info("[LLM路由] 模型重新初始化完成");
|
||||
}
|
||||
|
||||
private void doInitProviders() {
|
||||
rebuildLock.lock();
|
||||
try {
|
||||
List<AiModelConfig> configs = aiModelConfigService.queryList(new ReqAiModelConfig().setEnabled(1));
|
||||
Map<String, List<ChatModel>> newMap = new HashMap<>();
|
||||
Set<String> usedSignatures = new HashSet<>();
|
||||
if (configs != null) {
|
||||
for (AiModelConfig config : configs) {
|
||||
if (1 != config.getEnabled()) {
|
||||
continue;
|
||||
}
|
||||
String signature = signatureOf(config);
|
||||
if (!usedSignatures.add(signature)) {
|
||||
continue; // 同签名去重
|
||||
}
|
||||
try {
|
||||
// 签名命中缓存则复用已构建的ChatModel,避免重复创建昂贵的HTTP客户端
|
||||
ChatModel chatModel = modelCache.computeIfAbsent(signature, k -> buildModel(config));
|
||||
newMap.computeIfAbsent(config.getTenantId(), k -> new ArrayList<>()).add(chatModel);
|
||||
log.info("[LLM路由] 注册: tenant={}, protocol={}, provider={}, model={}",
|
||||
config.getTenantId(), config.getProtocol(), config.getProvider(), config.getModelName());
|
||||
} catch (Exception e) {
|
||||
log.error("[LLM路由] 注册失败: tenant={}, protocol={}, provider={}, model={}",
|
||||
config.getTenantId(), config.getProtocol(), config.getProvider(), config.getModelName(), e);
|
||||
}
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString().replace("_", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化内置默认前缀规则
|
||||
* 这些规则在数据库没有配置时作为兜底使用
|
||||
*/
|
||||
private void initDefaultPrefixMap() {
|
||||
// Ollama常见模型
|
||||
defaultPrefixMap.put("llama", "ollama");
|
||||
defaultPrefixMap.put("mistral", "ollama");
|
||||
defaultPrefixMap.put("phi", "ollama");
|
||||
defaultPrefixMap.put("gemma", "ollama");
|
||||
// OpenAI兼容API常见模型
|
||||
defaultPrefixMap.put("gpt", "openai");
|
||||
defaultPrefixMap.put("qwen", "openai");
|
||||
defaultPrefixMap.put("deepseek", "openai");
|
||||
defaultPrefixMap.put("o1", "openai");
|
||||
defaultPrefixMap.put("o3", "openai");
|
||||
defaultPrefixMap.put("chatglm", "zhipuai");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新数据库动态映射配置
|
||||
* 由外部定时任务或配置变更事件调用
|
||||
*
|
||||
* @param prefixMap 模型名前缀 → 提供者名称的映射
|
||||
*/
|
||||
public void refreshDynamicPrefixMap(Map<String, String> prefixMap) {
|
||||
dynamicPrefixMap.clear();
|
||||
if (prefixMap != null) {
|
||||
dynamicPrefixMap.putAll(prefixMap);
|
||||
}
|
||||
log.info("[LLM路由] 刷新动态映射配置: {}", dynamicPrefixMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型名解析提供者名称
|
||||
* 优先级:动态配置 > 内置默认规则 > 首个注册的提供者
|
||||
*/
|
||||
public String resolveProvider(String model) {
|
||||
if (model == null || model.isEmpty()) {
|
||||
return fallbackOrder.getFirst();
|
||||
}
|
||||
String lower = model.toLowerCase();
|
||||
|
||||
// 1. 优先查动态配置(数据库)
|
||||
for (Map.Entry<String, String> entry : dynamicPrefixMap.entrySet()) {
|
||||
if (lower.startsWith(entry.getKey()) && providers.containsKey(entry.getValue())) {
|
||||
return entry.getValue();
|
||||
// 构建不可变快照
|
||||
Map<String, List<ChatModel>> snapshot = new HashMap<>();
|
||||
for (Map.Entry<String, List<ChatModel>> e : newMap.entrySet()) {
|
||||
snapshot.put(e.getKey(), List.copyOf(e.getValue()));
|
||||
}
|
||||
}
|
||||
// 2. 兜底查内置默认规则
|
||||
for (Map.Entry<String, String> entry : defaultPrefixMap.entrySet()) {
|
||||
if (lower.startsWith(entry.getKey()) && providers.containsKey(entry.getValue())) {
|
||||
return entry.getValue();
|
||||
// 清理不再使用的缓存项,释放已删除配置占用的资源
|
||||
modelCache.keySet().retainAll(usedSignatures);
|
||||
tenantProviders = Map.copyOf(snapshot);
|
||||
if (snapshot.isEmpty()) {
|
||||
log.warn("[LLM路由] 数据库无启用的模型配置");
|
||||
}
|
||||
} finally {
|
||||
rebuildLock.unlock();
|
||||
}
|
||||
// 3. 直接匹配提供者名称
|
||||
if (providers.containsKey(lower)) {
|
||||
return lower;
|
||||
}
|
||||
// 4. 默认返回首个提供者
|
||||
return fallbackOrder.getFirst();
|
||||
}
|
||||
|
||||
private ChatModel buildModel(AiModelConfig config) {
|
||||
return LlmModelFactory.create(
|
||||
config.getProtocol(), config.getApiKey(),
|
||||
config.getBaseUrl(), config.getModelName(),
|
||||
config.getMaxTokens(), config.getTemperature());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的提供者名称
|
||||
* 配置签名:由影响ChatModel构建的字段组成。签名不变则复用已构建的ChatModel。
|
||||
* 使用加密后的apiKey(与解密后一一对应)避免明文密钥出现在签名串中。
|
||||
*/
|
||||
public Set<String> getProviderNames() {
|
||||
return Collections.unmodifiableSet(providers.keySet());
|
||||
private String signatureOf(AiModelConfig config) {
|
||||
return config.getProtocol() + "|" + config.getModelName() + "|" + config.getBaseUrl()
|
||||
+ "|" + config.getApiKey() + "|" + config.getMaxTokens() + "|" + config.getTemperature();
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式调用,支持fallback链
|
||||
* 安全获取当前请求租户ID;无请求上下文(如启动期构造)时返回 null,回退到全局模型。
|
||||
* 注意:必须在请求线程调用,不能在reactive异步线程中调用。
|
||||
*/
|
||||
public String currentTenantId() {
|
||||
try {
|
||||
return AuthInfoUtils.getCurrentTenantId();
|
||||
} catch (Exception e) {
|
||||
return AuthInfoUtils.SUPER_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定租户的ChatModel:租户专属模型 → 全局模型 → 默认模型
|
||||
*/
|
||||
public ChatModel getChatModel(String tenantId) {
|
||||
List<ChatModel> list = resolveProviders(tenantId);
|
||||
return list.isEmpty() ? openaiChatModel : list.getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析租户可用的provider链:优先租户专属,为空则回退全局
|
||||
*/
|
||||
private List<ChatModel> resolveProviders(String tenantId) {
|
||||
Map<String, List<ChatModel>> snapshot = tenantProviders;
|
||||
List<ChatModel> list = snapshot.get(tenantId);
|
||||
if (list == null || list.isEmpty()) {
|
||||
list = snapshot.get(AuthInfoUtils.SUPER_TENANT_ID);
|
||||
}
|
||||
return list == null ? List.of() : list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式调用,支持fallback链。租户在请求线程解析后传入链路,避免在reactive异步线程中获取租户。
|
||||
*/
|
||||
public Flux<ChatResponse> streamWithFallback(Prompt prompt, String model) {
|
||||
String primaryProvider = resolveProvider(model);
|
||||
List<String> chain = buildFallbackChain(primaryProvider);
|
||||
return tryStreamChain(prompt, chain, 0, model);
|
||||
List<ChatModel> providers = resolveProviders(currentTenantId());
|
||||
return tryStreamChain(prompt, 0, model, providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步调用,支持fallback链
|
||||
* 同步调用,支持fallback链。租户在请求线程解析后传入链路。
|
||||
*/
|
||||
public Mono<ChatResponse> callWithFallback(Prompt prompt, String model) {
|
||||
String primaryProvider = resolveProvider(model);
|
||||
List<String> chain = buildFallbackChain(primaryProvider);
|
||||
return tryCallChain(prompt, chain, 0);
|
||||
List<ChatModel> providers = resolveProviders(currentTenantId());
|
||||
return tryCallChain(prompt, 0, model, providers);
|
||||
}
|
||||
|
||||
private List<String> buildFallbackChain(String primaryProvider) {
|
||||
List<String> chain = new ArrayList<>();
|
||||
chain.add(primaryProvider);
|
||||
for (String provider : fallbackOrder) {
|
||||
if (!chain.contains(provider)) {
|
||||
chain.add(provider);
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
private Flux<ChatResponse> tryStreamChain(Prompt prompt, List<String> chain, int index, String model) {
|
||||
if (index >= chain.size()) {
|
||||
return Flux.error(new RuntimeException("所有模型均不可用"));
|
||||
}
|
||||
String provider = chain.get(index);
|
||||
log.info("[LLM路由] 流式调用, provider={}, model={}", provider, model);
|
||||
return providers.get(provider).stream(prompt)
|
||||
private Flux<ChatResponse> tryStreamChain(Prompt prompt, int index, String model, List<ChatModel> providers) {
|
||||
ChatModel provider = getProvider(index, model, providers);
|
||||
return provider.stream(prompt)
|
||||
.onErrorResume(ex -> {
|
||||
log.warn("[LLM路由] 提供者{}流式调用失败,尝试fallback", provider, ex);
|
||||
return tryStreamChain(prompt, chain, index + 1, model);
|
||||
return tryStreamChain(prompt, index + 1, model, providers);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<ChatResponse> tryCallChain(Prompt prompt, List<String> chain, int index) {
|
||||
if (index >= chain.size()) {
|
||||
return Mono.error(new RuntimeException("所有模型均不可用"));
|
||||
}
|
||||
String provider = chain.get(index);
|
||||
log.info("[LLM路由] 同步调用, provider={}", provider);
|
||||
return Mono.fromCallable(() -> providers.get(provider).call(prompt))
|
||||
private Mono<ChatResponse> tryCallChain(Prompt prompt, int index, String model, List<ChatModel> providers) {
|
||||
ChatModel provider = getProvider(index, model, providers);
|
||||
return Mono.fromCallable(() -> provider.call(prompt))
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.onErrorResume(ex -> {
|
||||
log.warn("[LLM路由] 提供者{}同步调用失败,尝试fallback", provider, ex);
|
||||
return tryCallChain(prompt, chain, index + 1);
|
||||
return tryCallChain(prompt, index + 1, model, providers);
|
||||
});
|
||||
}
|
||||
|
||||
private ChatModel getProvider(int index, String model, List<ChatModel> providers) {
|
||||
if (index >= providers.size()) {
|
||||
throw new MyRuntimeException("所有模型均不可用");
|
||||
}
|
||||
ChatModel provider;
|
||||
if (model == null || model.isEmpty()) {
|
||||
provider = providers.get(index);
|
||||
} else {
|
||||
provider = providers.stream().filter(p -> Objects.equals(p.getOptions().getModel(), model)).findFirst().orElseGet(() -> providers.get(index));
|
||||
}
|
||||
log.info("[LLM路由] 调用, provider={}, model={}", provider, provider.getOptions().getModel());
|
||||
return provider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package cn.com.mfish.ai.service.impl;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.ai.event.AiModelChangedEvent;
|
||||
import cn.com.mfish.ai.mapper.AiModelConfigMapper;
|
||||
import cn.com.mfish.common.ai.service.AiModelConfigService;
|
||||
import cn.com.mfish.common.core.secret.SM4Utils;
|
||||
import cn.com.mfish.common.core.utils.AuthInfoUtils;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.core.utils.excel.ExcelUtils;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
@Service
|
||||
public class AiModelConfigServiceImpl extends ServiceImpl<AiModelConfigMapper, AiModelConfig> implements AiModelConfigService {
|
||||
|
||||
@Value("${ai.api-key.hex-key:}")
|
||||
private String hexApiKey;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public AiModelConfigServiceImpl(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型配置发生变更后发布事件,触发 LlmModelRouter 重新加载模型并通知助手重建 chatClient。
|
||||
*/
|
||||
private void publishModelChanged() {
|
||||
eventPublisher.publishEvent(new AiModelChangedEvent(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @return 返回AI模型配置信息-分页列表
|
||||
*/
|
||||
@Override
|
||||
public Result<PageResult<AiModelConfigVo>> queryPageList(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) {
|
||||
return Result.ok(new PageResult<>(queryList(reqAiModelConfig, reqPage)), "AI模型配置信息-查询成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @return 返回AI模型配置信息-分页列表
|
||||
*/
|
||||
private List<AiModelConfigVo> queryList(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) {
|
||||
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
|
||||
List<AiModelConfig> list = queryList(reqAiModelConfig);
|
||||
List<AiModelConfigVo> voList = new ArrayList<>();
|
||||
for (AiModelConfig entity : list) {
|
||||
AiModelConfigVo vo = new AiModelConfigVo();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
voList.add(vo);
|
||||
}
|
||||
return voList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AiModelConfig> queryList(ReqAiModelConfig reqAiModelConfig) {
|
||||
LambdaQueryWrapper<AiModelConfig> lambdaQueryWrapper = new LambdaQueryWrapper<AiModelConfig>()
|
||||
.eq(!StringUtils.isEmpty(reqAiModelConfig.getModelName()), AiModelConfig::getModelName, reqAiModelConfig.getModelName())
|
||||
.eq(null != reqAiModelConfig.getEnabled(), AiModelConfig::getEnabled, reqAiModelConfig.getEnabled())
|
||||
.eq(!StringUtils.isEmpty(reqAiModelConfig.getProtocol()), AiModelConfig::getProtocol, reqAiModelConfig.getProtocol())
|
||||
.orderByAsc(AiModelConfig::getSortOrder);
|
||||
List<AiModelConfig> list = list(lambdaQueryWrapper);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
AiModelConfig entity = list.get(i);
|
||||
if (StringUtils.isEmpty(entity.getApiKey())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
entity.setApiKey(SM4Utils.decryptEcb(hexApiKey, entity.getApiKey()));
|
||||
} catch (Exception e) {
|
||||
log.error("解密异常" + e.getMessage(), e);
|
||||
list.remove(i--);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-添加结果
|
||||
*/
|
||||
@Override
|
||||
public Result<AiModelConfig> add(AiModelConfig aiModelConfig) {
|
||||
aiModelConfig.setTenantId(AuthInfoUtils.getCurrentTenantId());
|
||||
if (StringUtils.isNotEmpty(aiModelConfig.getApiKey())) {
|
||||
aiModelConfig.setApiKey(SM4Utils.encryptEcb(hexApiKey, aiModelConfig.getApiKey()));
|
||||
}
|
||||
if (save(aiModelConfig)) {
|
||||
publishModelChanged();
|
||||
return Result.ok(aiModelConfig, "AI模型配置信息-添加成功!");
|
||||
}
|
||||
return Result.fail(aiModelConfig, "错误:AI模型配置信息-添加失败!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-编辑结果
|
||||
*/
|
||||
@Override
|
||||
public Result<AiModelConfig> edit(AiModelConfig aiModelConfig) {
|
||||
if (StringUtils.isNotEmpty(aiModelConfig.getApiKey())) {
|
||||
//判断是否包含****
|
||||
if (aiModelConfig.getApiKey().contains("****")) {
|
||||
aiModelConfig.setApiKey(null);
|
||||
} else {
|
||||
aiModelConfig.setApiKey(SM4Utils.encryptEcb(hexApiKey, aiModelConfig.getApiKey()));
|
||||
}
|
||||
}
|
||||
if (updateById(aiModelConfig)) {
|
||||
publishModelChanged();
|
||||
return Result.ok(aiModelConfig, "AI模型配置信息-编辑成功!");
|
||||
}
|
||||
return Result.fail(aiModelConfig, "错误:AI模型配置信息-编辑失败!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
@Override
|
||||
public Result<Boolean> delete(String id) {
|
||||
if (removeById(id)) {
|
||||
publishModelChanged();
|
||||
return Result.ok(true, "AI模型配置信息-删除成功!");
|
||||
}
|
||||
return Result.fail(false, "错误:AI模型配置信息-删除失败!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids 批量ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
@Override
|
||||
public Result<Boolean> deleteBatch(String ids) {
|
||||
if (removeByIds(Arrays.asList(ids.split(",")))) {
|
||||
publishModelChanged();
|
||||
return Result.ok(true, "AI模型配置信息-批量删除成功!");
|
||||
}
|
||||
return Result.fail(false, "错误:AI模型配置信息-批量删除失败!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息对象
|
||||
*/
|
||||
@Override
|
||||
public Result<AiModelConfig> queryById(String id) {
|
||||
AiModelConfig aiModelConfig = getById(id);
|
||||
return Result.ok(aiModelConfig, "AI模型配置信息-查询成功!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<AiModelConfigVo> queryByIdVo(String id) {
|
||||
AiModelConfig entity = getById(id);
|
||||
if (entity == null) {
|
||||
return Result.fail(null, "错误:AI模型配置信息-未找到对应数据!");
|
||||
}
|
||||
AiModelConfigVo vo = new AiModelConfigVo();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
return Result.ok(vo, "AI模型配置信息-查询成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
@Override
|
||||
public void export(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) throws IOException {
|
||||
//swagger调用会用问题,使用postman测试
|
||||
ExcelUtils.write("AI模型配置信息_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()), queryList(reqAiModelConfig, reqPage));
|
||||
}
|
||||
}
|
||||
+8
-11
@@ -1,12 +1,11 @@
|
||||
package cn.com.mfish.sys.tools;
|
||||
package cn.com.mfish.ai.tools;
|
||||
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.constants.RPCConstants;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.sys.service.DictItemService;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.req.ReqDict;
|
||||
import cn.com.mfish.sys.service.DictService;
|
||||
import cn.com.mfish.sys.api.remote.RemoteDictService;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
@@ -25,9 +24,7 @@ import java.util.List;
|
||||
@Component
|
||||
public class DictTools {
|
||||
@Resource
|
||||
private DictService dictService;
|
||||
@Resource
|
||||
private DictItemService dictItemService;
|
||||
private RemoteDictService remoteDictService;
|
||||
|
||||
/**
|
||||
* 根据字典名称查询字典编码
|
||||
@@ -37,7 +34,7 @@ public class DictTools {
|
||||
*/
|
||||
@Tool(description = "查询字典编码", returnDirect = true)
|
||||
public List<Dict> queryCodeByName(@ToolParam(description = "字典名称") String dictName) {
|
||||
return dictService.queryList(new ReqDict().setDictName(dictName), new ReqPage(1, 100));
|
||||
return remoteDictService.queryList(RPCConstants.INNER, new ReqDict().setDictName(dictName)).getData();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +45,7 @@ public class DictTools {
|
||||
*/
|
||||
@Tool(description = "查询字典项", returnDirect = true)
|
||||
public List<DictItem> queryByCode(@ToolParam(description = "字典编码") String dictCode) {
|
||||
Result<List<DictItem>> result = dictItemService.queryByCode(dictCode);
|
||||
Result<List<DictItem>> result = remoteDictService.queryByCode(RPCConstants.INNER, dictCode);
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
+7
-6
@@ -1,9 +1,10 @@
|
||||
package cn.com.mfish.oauth.tools;
|
||||
package cn.com.mfish.ai.tools;
|
||||
|
||||
import cn.com.mfish.common.core.constants.RPCConstants;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.service.SsoMenuService;
|
||||
import cn.com.mfish.common.oauth.api.remote.RemoteMenuService;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
@@ -21,15 +22,15 @@ import java.util.List;
|
||||
@Component
|
||||
public class MenuTools {
|
||||
@Resource
|
||||
private SsoMenuService ssoMenuService;
|
||||
private RemoteMenuService remoteMenuService;
|
||||
|
||||
@Tool(description = "查询菜单树结构")
|
||||
public Result<List<SsoMenu>> queryList() {
|
||||
return ssoMenuService.queryMenuTree(new ReqSsoMenu().setMenuType(1), null);
|
||||
return remoteMenuService.queryMenuTree(RPCConstants.INNER, new ReqSsoMenu().setMenuType(1));
|
||||
}
|
||||
|
||||
@Tool(description = "查询子菜单信息")
|
||||
public Result<List<SsoMenu>> queryChild(@ToolParam(description = "菜单名称") String menuName) {
|
||||
return ssoMenuService.queryMenuTree(new ReqSsoMenu().setMenuName(menuName), null);
|
||||
return remoteMenuService.queryMenuTree(RPCConstants.INNER, new ReqSsoMenu().setMenuName(menuName));
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -1,9 +1,9 @@
|
||||
package cn.com.mfish.oauth.tools;
|
||||
package cn.com.mfish.ai.tools;
|
||||
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.core.constants.RPCConstants;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoOrg;
|
||||
import cn.com.mfish.common.oauth.api.entity.UserRole;
|
||||
import cn.com.mfish.common.oauth.service.SsoUserService;
|
||||
import cn.com.mfish.common.oauth.api.remote.RemoteUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.context.annotation.Description;
|
||||
@@ -21,21 +21,21 @@ import java.util.Set;
|
||||
public class UserTools {
|
||||
private final String userId;
|
||||
private final String tenantId;
|
||||
private final SsoUserService ssoUserService;
|
||||
private final RemoteUserService remoteUserService;
|
||||
|
||||
@Tool(description = "获取当前用户角色信息")
|
||||
public List<UserRole> getUserRoles() {
|
||||
return ssoUserService.getUserRoles(userId, tenantId);
|
||||
return remoteUserService.getRoles(RPCConstants.INNER, userId, tenantId).getData();
|
||||
}
|
||||
|
||||
@Tool(description = "获取当前用户组织树")
|
||||
public Result<List<SsoOrg>> getOrgs() {
|
||||
return ssoUserService.getOrgs(userId, "all");
|
||||
public List<SsoOrg> getOrgs() {
|
||||
return remoteUserService.getOrgs(RPCConstants.INNER, userId, "all").getData();
|
||||
}
|
||||
|
||||
@Tool(description = "获取当前用户权限")
|
||||
public Result<Set<String>> getPermissions() {
|
||||
return Result.ok(ssoUserService.getUserPermissions(userId, tenantId));
|
||||
public Set<String> getPermissions() {
|
||||
return remoteUserService.getPermissions(RPCConstants.INNER, userId, tenantId).getData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,9 +32,5 @@
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-sys</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package cn.com.mfish.sys.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.agent.BaseAssistant;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 摸鱼小助手配置
|
||||
* @author: mfish
|
||||
* @date: 2025/8/21
|
||||
*/
|
||||
@Component
|
||||
public class MfishAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下摸鱼低代码";
|
||||
private final ChatClient chatClient;
|
||||
|
||||
public MfishAssistant(ChatModel openAiChatModel, ChatMemory chatMemory) {
|
||||
this.chatClient = ChatClient.builder(openAiChatModel)
|
||||
.defaultSystem("""
|
||||
你是“摸鱼低代码”的小助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
摸鱼低代码平台,是一款致力于让开发像摸鱼一样轻松的低代码/无代码平台。
|
||||
我们希望打破技术门槛,让程序员和非程序员都能快速构建业务系统,提升效率,释放创造力。
|
||||
这不仅是程序员偷闲时的效率神器,更是职场小白的建站利器,甚至是领导画原型的秘密武器!
|
||||
我可以扮演以下角色:
|
||||
1. 低代码开发人员
|
||||
2. 非技术人员(如产品经理、业务分析师等)
|
||||
3. 技术支持人员
|
||||
4. 客户服务
|
||||
""")
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
return this.chatClient.prompt().user(prompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.stream().chatResponse();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package cn.com.mfish.sys.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.agent.BaseAssistant;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.sys.tools.DictTools;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 系统中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SysAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下系统中心助手";
|
||||
private final ChatClient chatClient;
|
||||
@Resource
|
||||
private DictTools dictTools;
|
||||
|
||||
public SysAssistant(ChatModel openAiChatModel, ChatMemory chatMemory) {
|
||||
this.chatClient = ChatClient.builder(openAiChatModel)
|
||||
.defaultSystem("""
|
||||
你是“摸鱼低代码”的系统中心助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
你主要辅助用户完成系统中心的一些基础操作
|
||||
你能够通过调用工具来完成系统中心基础操作,包括查询、新增、修改、删除等
|
||||
你主要辅助用户完成系统中心相关模块的信息检索、执行操作
|
||||
系统中心主要包含 字典信息 分类目录信息 日志信息 代码生成功能 在线用户信息 数据库连接信息 数据源信息
|
||||
|
||||
注意:
|
||||
1. 我需要你调用工具来帮我回答问题,不要自己编造答案
|
||||
2. 不要随意修改查询结果,只返回工具返回的内容
|
||||
3. 如果未找到相应工具,告诉用户你执行不了这个操作
|
||||
4. 如果工具参数缺失,提示用户输入缺失参数,引导用户输入有用信息
|
||||
5. 请使用中文回答
|
||||
""")
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String finalPrompt = prompt;
|
||||
ChatClient.ChatClientRequestSpec requestSpec = this.chatClient.prompt()
|
||||
.system("你必须先调用工具,再基于工具结果回答问题")
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.tools(dictTools);
|
||||
return
|
||||
requestSpec.stream().chatResponse()
|
||||
.onErrorResume(e -> {
|
||||
log.warn("流式调用失败,降级为非流式调用: {}", e.getMessage());
|
||||
return Mono.fromCallable(() -> requestSpec.call().chatResponse())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flux();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,9 @@ import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.log.annotation.Log;
|
||||
import cn.com.mfish.common.oauth.annotation.RequiresPermissions;
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.req.ReqDict;
|
||||
import cn.com.mfish.sys.service.DictService;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import cn.com.mfish.common.sys.service.DictService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 字典
|
||||
@@ -48,6 +49,19 @@ public class DictController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 字典列表查询
|
||||
*
|
||||
* @param reqDict 请求参数
|
||||
* @return 返回字典列表
|
||||
*/
|
||||
@Operation(summary = "字典-列表查询", description = "字典-列表查询")
|
||||
@GetMapping("/list")
|
||||
@RequiresPermissions("sys:dict:query")
|
||||
public Result<List<Dict>> queryList(ReqDict reqDict) {
|
||||
return Result.ok(dictService.queryList(reqDict, new ReqPage(1, 10000)), "字典列表-查询成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import cn.com.mfish.common.sys.req.ReqDictItem;
|
||||
import cn.com.mfish.common.sys.service.DictItemService;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.cache.DictCache;
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.mapper.DictMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package cn.com.mfish.sys.mapper;
|
||||
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import cn.com.mfish.common.sys.req.ReqDictItem;
|
||||
import cn.com.mfish.common.sys.service.DictItemService;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.cache.DictCache;
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.mapper.DictMapper;
|
||||
import cn.com.mfish.sys.req.ReqDict;
|
||||
import cn.com.mfish.sys.service.DictService;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import cn.com.mfish.common.sys.service.DictService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
|
||||
@@ -25,13 +25,37 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-model</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-ollama</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-deepseek</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-anthropic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-client-chat</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-ai-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,33 +0,0 @@
|
||||
package cn.com.mfish.common.ai.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.client.IClientAssistant;
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.ChatResponseVo;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description: 系统中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
public abstract class BaseAssistant implements IClientAssistant {
|
||||
|
||||
/**
|
||||
* 聊天返回id
|
||||
*
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponseVo> chat(AiRequest aiRequest) {
|
||||
return chat(aiRequest.getSessionId(), aiRequest.getMessage().getContent())
|
||||
.filter(resp -> "STOP".equals(Objects.requireNonNull(resp.getResult()).getMetadata().getFinishReason())
|
||||
|| StringUtils.isNotEmpty(resp.getResult().getOutput().getText()))
|
||||
.map(resp -> new ChatResponseVo().setId(aiRequest.getId())
|
||||
.setContent(Objects.requireNonNull(resp.getResult()).getOutput().getText())
|
||||
.setFinishReason(resp.getResult().getMetadata().getFinishReason())
|
||||
);
|
||||
}
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
package cn.com.mfish.common.ai.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.ResponseEntity;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @description: 摸鱼小助手配置
|
||||
* @author: mfish
|
||||
* @date: 2025/8/21
|
||||
*/
|
||||
@Component
|
||||
public class GatewayAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下摸鱼低代码";
|
||||
private final ChatClient chatClient;
|
||||
|
||||
public GatewayAssistant(ChatModel openAiChatModel, ChatMemory chatMemory) {
|
||||
this.chatClient = ChatClient.builder(openAiChatModel)
|
||||
.defaultSystem("""
|
||||
你是"摸鱼低代码"的路由小助手,你会根据用户的问题,判断用户需要访问的路由路径!
|
||||
当有人问"摸鱼低代码"相关信息时,实际是在问我们整个平台的信息
|
||||
你只能返回路由路径,不能返回其他内容!
|
||||
结果以json格式返回:
|
||||
返回格式:{"path":"/sys/ai/chat","name":"摸鱼小助手","serviceId":"mf-sys"}
|
||||
|
||||
# 路由路径
|
||||
1. /sys/ai/chat : 摸鱼小助手 默认路由 对应微服务mf-sys
|
||||
2. /sys/ai/assist : 系统中心助手 对应微服务mf-sys 系统中心主要包括(字典信息、分类目录信息、日志信息、代码生成功能、在线用户信息、数据库连接信息、数据源信息 等)
|
||||
3. /oauth2/ai/assist : 认证中心助手 对应微服务mf-oauth 认证中心主要包括(菜单信息、组织信息、角色信息、帐号信息、租户信息 等)
|
||||
4. /nocode/ai/assist : 低代码中心助手 对应微服务mf-nocode 低代码中心主要包括(自助大屏、自助API、组件管理、公式信息 等)
|
||||
|
||||
```
|
||||
如果用户需要查询字典相关信息、分类目录相关信息、日志相关信息等系统信息,返回系统中心助手的路由路径
|
||||
如果用户需要查询菜单相关信息、组织相关信息、角色相关信息、帐号相关信息、租户相关信息等认证中心信息,返回认证中心助手的路由路径
|
||||
如果用户需要查询自助大屏、自助API、数据集、可视化等低代码中心信息,返回低代码中心助手的路由路径
|
||||
```
|
||||
|
||||
注意:
|
||||
返回的路由路径必须上失眠列出来的路由路径
|
||||
如果当前问题与路由路径无关,分析下之前的问题是否是之前问题的补充,如果是则返回上一次的路由路径
|
||||
如果不知道用户需要访问的路由路径,默认返回:{"path":"/sys/ai/chat","name":"摸鱼小助手","serviceId":"mf-sys"}
|
||||
路由路径不能包含空格
|
||||
请使用中文回答
|
||||
""")
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
public Mono<Result<AiRouterVo>> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String finalPrompt = prompt;
|
||||
return Mono.fromCallable(() -> {
|
||||
ResponseEntity<ChatResponse, AiRouterVo> responseEntity = this.chatClient.prompt()
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.call()
|
||||
.responseEntity(AiRouterVo.class);
|
||||
AiRouterVo vo = Objects.requireNonNullElseGet(responseEntity.entity(), AiRouterVo::new);
|
||||
return Result.ok(vo);
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -25,4 +25,12 @@ public interface IClientAssistant {
|
||||
* @return 聊天结果
|
||||
*/
|
||||
Flux<ChatResponseVo> chat(AiRequest aiRequest);
|
||||
|
||||
/**
|
||||
* 获取助手对应的网关路由路径
|
||||
* 用于AssistantRegistry按路径查找助手,替代Servlet forward实现真正的SSE流式传输
|
||||
*
|
||||
* @return 路由路径,如/ai/agent/chat、/ai/sys/assist、/ai/oauth2/assist
|
||||
*/
|
||||
String getPath();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.com.mfish.common.ai.config;
|
||||
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.ChatMemoryRepository;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
|
||||
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -13,6 +14,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
*/
|
||||
@Configuration
|
||||
public class AiConfig {
|
||||
@Bean
|
||||
public ChatMemoryRepository chatMemoryRepository() {
|
||||
return new InMemoryChatMemoryRepository();
|
||||
}
|
||||
@Bean
|
||||
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
|
||||
return MessageWindowChatMemory.builder()
|
||||
|
||||
@@ -15,7 +15,7 @@ import lombok.experimental.Accessors;
|
||||
@Accessors(chain = true)
|
||||
public class AiRouterVo {
|
||||
@Schema(description = "路由路径")
|
||||
private String path = "/sys/ai/chat";
|
||||
private String path = "/ai/agent/chat";
|
||||
@Schema(description = "路由名称")
|
||||
private String name = "摸鱼小助手";
|
||||
@Schema(description = "目标微服务ID,如mf-sys、mf-oauth,用于Gateway动态路由")
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package cn.com.mfish.common.ai.enums;
|
||||
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
||||
import org.springframework.ai.ollama.api.OllamaModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* LLM接入协议
|
||||
* @author mfish
|
||||
* @date 2026/07/10
|
||||
*/
|
||||
public enum LlmProtocol {
|
||||
|
||||
OPENAI("openai") {
|
||||
@Override
|
||||
public ChatModel create(String apiKey, String baseUrl, String model, Integer maxTokens, Double temperature) {
|
||||
return OpenAiChatModel.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.apiKey(apiKey)
|
||||
.model(model)
|
||||
.temperature(temperature)
|
||||
.maxTokens(maxTokens)
|
||||
.baseUrl(baseUrl)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
},
|
||||
OLLAMA("ollama") {
|
||||
@Override
|
||||
public ChatModel create(String apiKey, String baseUrl, String model, Integer maxTokens, Double temperature) {
|
||||
var api = OllamaApi.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.build();
|
||||
var options = OllamaChatOptions.builder()
|
||||
.model(OllamaModel.valueOf(model))
|
||||
.numPredict(maxTokens)
|
||||
.temperature(temperature)
|
||||
.build();
|
||||
return OllamaChatModel.builder().ollamaApi(api).options(options).build();
|
||||
}
|
||||
},
|
||||
DEEPSEEK("deepseek") {
|
||||
@Override
|
||||
public ChatModel create(String apiKey, String baseUrl, String model, Integer maxTokens, Double temperature) {
|
||||
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
|
||||
.apiKey(apiKey)
|
||||
.baseUrl(baseUrl)
|
||||
.build();
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model(model)
|
||||
.temperature(temperature)
|
||||
.maxTokens(maxTokens)
|
||||
.build();
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(deepSeekApi)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
},
|
||||
ANTHROPIC("anthropic") {
|
||||
@Override
|
||||
public ChatModel create(String apiKey, String baseUrl, String model, Integer maxTokens, Double temperature) {
|
||||
return AnthropicChatModel.builder()
|
||||
.options(AnthropicChatOptions.builder()
|
||||
.model(model)
|
||||
.maxTokens(maxTokens)
|
||||
.temperature(temperature)
|
||||
.apiKey(apiKey)
|
||||
.baseUrl(baseUrl)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
private final String value;
|
||||
|
||||
LlmProtocol(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public abstract ChatModel create(String apiKey, String baseUrl, String model, Integer maxTokens, Double temperature);
|
||||
|
||||
public static LlmProtocol fromValue(String value) {
|
||||
return Arrays.stream(values())
|
||||
.filter(p -> p.value.equalsIgnoreCase(value))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("不支持的接入协议: " + value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package cn.com.mfish.common.ai.service;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: AI模型配置信息
|
||||
* @author: mfish
|
||||
* @date: 2026-07-03
|
||||
* @version: V2.4.0
|
||||
*/
|
||||
public interface AiModelConfigService extends IService<AiModelConfig> {
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @return 返回AI模型配置信息-分页列表
|
||||
*/
|
||||
Result<PageResult<AiModelConfigVo>> queryPageList(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage);
|
||||
|
||||
/**
|
||||
* 列表查询
|
||||
*
|
||||
* @return 返回AI模型配置信息-列表
|
||||
*/
|
||||
List<AiModelConfig> queryList(ReqAiModelConfig reqAiModelConfig);
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-添加结果
|
||||
*/
|
||||
Result<AiModelConfig> add(AiModelConfig aiModelConfig);
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param aiModelConfig AI模型配置信息对象
|
||||
* @return 返回AI模型配置信息-编辑结果
|
||||
*/
|
||||
Result<AiModelConfig> edit(AiModelConfig aiModelConfig);
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
Result<Boolean> delete(String id);
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids 批量ID
|
||||
* @return 返回AI模型配置信息-删除结果
|
||||
*/
|
||||
Result<Boolean> deleteBatch(String ids);
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息对象
|
||||
*/
|
||||
Result<AiModelConfig> queryById(String id);
|
||||
|
||||
/**
|
||||
* 通过id查询VO
|
||||
*
|
||||
* @param id 唯一ID
|
||||
* @return 返回AI模型配置信息VO对象
|
||||
*/
|
||||
Result<AiModelConfigVo> queryByIdVo(String id);
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param reqAiModelConfig AI模型配置信息请求参数
|
||||
* @param reqPage 分页参数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void export(ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) throws IOException;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package cn.com.mfish.common.ai.service;
|
||||
|
||||
import cn.com.mfish.common.ai.enums.LlmProtocol;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
|
||||
/**
|
||||
* LLM模型工厂
|
||||
* @author mfish
|
||||
* @date 2026/07/10
|
||||
*/
|
||||
public class LlmModelFactory {
|
||||
|
||||
/**
|
||||
* 创建LLM模型
|
||||
* @param protocol 接入协议
|
||||
* @param apiKey API密钥
|
||||
* @param baseUrl 基础URL
|
||||
* @param modelName 模型名称
|
||||
* @param maxTokens 最大令牌数
|
||||
* @param temperature 温度
|
||||
* @return LLM模型
|
||||
*/
|
||||
public static ChatModel create(String protocol, String apiKey,
|
||||
String baseUrl, String modelName,
|
||||
Integer maxTokens, Double temperature) {
|
||||
return LlmProtocol.fromValue(protocol).create(apiKey, baseUrl, modelName, maxTokens, temperature);
|
||||
}
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
cn.com.mfish.common.ai.agent.GatewayAssistant
|
||||
cn.com.mfish.common.ai.service.AiRouteService
|
||||
@@ -69,5 +69,9 @@
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-demo-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.com.mfish.common.api;
|
||||
|
||||
import cn.com.mfish.ai.api.entity.AiModelConfig;
|
||||
import cn.com.mfish.ai.api.remote.RemoteAiModelConfigService;
|
||||
import cn.com.mfish.ai.api.req.ReqAiModelConfig;
|
||||
import cn.com.mfish.ai.api.vo.AiModelConfigVo;
|
||||
import cn.com.mfish.common.core.web.PageResult;
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.ai.service.AiModelConfigService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: ai模型配置服务
|
||||
* @author: mfish
|
||||
* @date: 2026/07/03
|
||||
*/
|
||||
@Service("remoteAiModelConfigService")
|
||||
public class BootAiModelConfigService implements RemoteAiModelConfigService {
|
||||
|
||||
@Resource
|
||||
private AiModelConfigService aiModelConfigService;
|
||||
|
||||
|
||||
@Override
|
||||
public Result<PageResult<AiModelConfigVo>> queryPageList(String origin, ReqAiModelConfig reqAiModelConfig, ReqPage reqPage) {
|
||||
return aiModelConfigService.queryPageList(reqAiModelConfig, reqPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<AiModelConfig>> queryList(String origin, ReqAiModelConfig reqAiModelConfig) {
|
||||
return Result.ok(aiModelConfigService.queryList(reqAiModelConfig), "查询模型配置列表成功");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package cn.com.mfish.common.api;
|
||||
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.sys.service.DictItemService;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.entity.DictItem;
|
||||
import cn.com.mfish.sys.api.remote.RemoteDictService;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import org.springframework.stereotype.Service;
|
||||
import cn.com.mfish.common.sys.service.DictService;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@@ -21,9 +25,16 @@ public class BootDictService implements RemoteDictService {
|
||||
|
||||
@Resource
|
||||
DictItemService dictItemService;
|
||||
@Resource
|
||||
DictService dictService;
|
||||
|
||||
@Override
|
||||
public Result<List<DictItem>> queryByCode(String origin, String dictCode) {
|
||||
return dictItemService.queryByCode(dictCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<Dict>> queryList(String origin, ReqDict reqDict) {
|
||||
return Result.ok(dictService.queryList(reqDict, new ReqPage(1, 10000)), "查询字典列表成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ package cn.com.mfish.common.api;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.remote.RemoteMenuService;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.service.SsoMenuService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 菜单远程接口的本地实现
|
||||
* @author: mfish
|
||||
@@ -17,6 +20,11 @@ public class BootMenuService implements RemoteMenuService {
|
||||
@Resource
|
||||
SsoMenuService ssoMenuService;
|
||||
|
||||
@Override
|
||||
public Result<List<SsoMenu>> queryMenuTree(String origin, ReqSsoMenu reqSsoMenu) {
|
||||
return ssoMenuService.queryMenuTree(reqSsoMenu, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SsoMenu> add(String origin, SsoMenu ssoMenu) {
|
||||
return ssoMenuService.insertMenu(ssoMenu);
|
||||
|
||||
+5
-10
@@ -1,5 +1,7 @@
|
||||
package cn.com.mfish.common.core.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -36,6 +38,7 @@ public class ServiceConstants {
|
||||
* 微服务枚举,引用ServiceConstants中的编译期常量,保持单一定义源
|
||||
* gatewayPrefix: 网关路由前缀(如/sys),用于将网关路径转换为服务实际路径
|
||||
*/
|
||||
@Getter
|
||||
public enum MfService {
|
||||
OAUTH(OAUTH_SERVICE, "/oauth2"),
|
||||
SYS(SYS_SERVICE, "/sys"),
|
||||
@@ -54,14 +57,6 @@ public class ServiceConstants {
|
||||
this.gatewayPrefix = gatewayPrefix;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getGatewayPrefix() {
|
||||
return gatewayPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有微服务ID集合
|
||||
*/
|
||||
@@ -85,9 +80,9 @@ public class ServiceConstants {
|
||||
|
||||
/**
|
||||
* 将网关路径转换为服务实际路径
|
||||
* 例如:/sys/ai/assist → /ai/assist(去掉/sys前缀)
|
||||
* 例如:/ai/sys/assist → /sys/assist(去掉/sys前缀)
|
||||
*
|
||||
* @param gatewayPath 网关路径(如/sys/ai/assist)
|
||||
* @param gatewayPath 网关路径(如/ai/sys/assist)
|
||||
* @return 服务路径(如/ai/assist),如果路径不以该服务前缀开头则原样返回
|
||||
*/
|
||||
public String toServicePath(String gatewayPath) {
|
||||
|
||||
+5
-10
@@ -1,5 +1,6 @@
|
||||
package cn.com.mfish.common.core.secret;
|
||||
|
||||
import cn.com.mfish.common.core.exception.MyRuntimeException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
@@ -84,7 +85,6 @@ public class SM4Utils {
|
||||
*/
|
||||
public static String encryptEcb(String hexKey, String paramStr) {
|
||||
try {
|
||||
String cipherText = "";
|
||||
// 16进制字符串-->byte[]
|
||||
org.bouncycastle.util.Arrays.fill(new byte[hexKey.length() / 2], (byte) 0);
|
||||
|
||||
@@ -94,11 +94,10 @@ public class SM4Utils {
|
||||
// 加密后的数组
|
||||
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
|
||||
// byte[]-->hexString
|
||||
cipherText = Hex.toHexString(cipherArray);
|
||||
return cipherText;
|
||||
return Hex.toHexString(cipherArray);
|
||||
} catch (Exception e) {
|
||||
log.error("ECB加密异常" + e.getMessage(), e);
|
||||
return paramStr;
|
||||
throw new MyRuntimeException("ECB加密异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,10 +126,6 @@ public class SM4Utils {
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decryptEcb(String hexKey, String cipherText) {
|
||||
// 用于接收解密后的字符串
|
||||
String decryptStr = "";
|
||||
// hexString-->byte[]
|
||||
|
||||
byte[] keyData = Hex.decode(hexKey);
|
||||
// hexString-->byte[]
|
||||
byte[] cipherData = Hex.decode(cipherText);
|
||||
@@ -139,11 +134,11 @@ public class SM4Utils {
|
||||
try {
|
||||
srcData = decrypt_Ecb_Padding(keyData, cipherData);
|
||||
// byte[]-->String
|
||||
decryptStr = new String(srcData, ENCODING);
|
||||
return new String(srcData, ENCODING);
|
||||
} catch (Exception e) {
|
||||
log.error("ECB解密异常" + e.getMessage(), e);
|
||||
throw new MyRuntimeException("ECB解密异常", e);
|
||||
}
|
||||
return decryptStr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+44
-25
@@ -3,9 +3,11 @@ package cn.com.mfish.common.core.utils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -16,6 +18,41 @@ import java.util.*;
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
private static final ThreadLocal<ServerWebExchange> EXCHANGE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
public static void setExchange(ServerWebExchange exchange) {
|
||||
EXCHANGE_HOLDER.set(exchange);
|
||||
}
|
||||
|
||||
public static void removeExchange() {
|
||||
EXCHANGE_HOLDER.remove();
|
||||
}
|
||||
|
||||
public static ServerWebExchange getExchange() {
|
||||
return EXCHANGE_HOLDER.get();
|
||||
}
|
||||
|
||||
public static ServerHttpRequest getServerHttpRequest() {
|
||||
ServerWebExchange exchange = EXCHANGE_HOLDER.get();
|
||||
return exchange != null ? exchange.getRequest() : null;
|
||||
}
|
||||
/**
|
||||
* 获取String参数
|
||||
*
|
||||
* @return 返回与header名称相对应的header值如果请求对象为null或指定的header不存在,则返回null
|
||||
*/
|
||||
public static String getHeader(String header) {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request != null) {
|
||||
return request.getHeader(header);
|
||||
}
|
||||
ServerHttpRequest serverHttpRequest = getServerHttpRequest();
|
||||
if (serverHttpRequest != null) {
|
||||
return serverHttpRequest.getHeaders().getFirst(header);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取String参数
|
||||
*/
|
||||
@@ -33,38 +70,20 @@ public class ServletUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取header信息
|
||||
* 该方法用于从HTTP请求中提取指定的header信息
|
||||
*
|
||||
* @param header 指定的header名称,不能为空
|
||||
* @return 返回与header名称相对应的header值如果请求对象为null或指定的header不存在,则返回null
|
||||
*/
|
||||
public static String getHeader(String header) {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return request.getHeader(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求属性
|
||||
* <p>
|
||||
* 本方法旨在从HTTP请求中获取特定的属性值通过提供属性名称,它首先确保有一个有效的请求对象存在
|
||||
* 如果请求对象不存在,方法直接返回null表明在没有有效请求的情况下,无法获取属性
|
||||
* 当请求对象存在时,方法则根据提供的属性名从请求中提取相应的属性值
|
||||
*
|
||||
* @param attr 请求属性的名称,用于标识所需的属性
|
||||
* @return 如果请求存在且指定的属性存在,则返回该属性值;否则返回null
|
||||
*/
|
||||
public static Object getAttribute(String attr) {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
if (request != null) {
|
||||
return request.getAttribute(attr);
|
||||
}
|
||||
return request.getAttribute(attr);
|
||||
ServerWebExchange exchange = getExchange();
|
||||
if (exchange != null) {
|
||||
return exchange.getAttributes().get(attr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
@@ -72,4 +72,10 @@ public @interface DataScope {
|
||||
* @return 跳过条件
|
||||
*/
|
||||
String[] ignores() default {};
|
||||
|
||||
/**
|
||||
* 超户是否忽略数据权限(默认 超户也受数据权限限制,如果超户不需要受数据权限限制,将属性设置为true)
|
||||
* @return 是否忽略
|
||||
*/
|
||||
boolean superIgnore() default false;
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
package cn.com.mfish.common.oauth.scope;
|
||||
|
||||
import cn.com.mfish.common.core.exception.MyRuntimeException;
|
||||
import cn.com.mfish.common.core.utils.AuthInfoUtils;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.oauth.annotation.DataScope;
|
||||
import cn.com.mfish.common.oauth.common.DataScopeUtils;
|
||||
@@ -74,6 +75,9 @@ public class SelectInterceptor implements Interceptor {
|
||||
private Map<String, List<DataScopeValue>> buildParamsMap(List<DataScope> scopes) {
|
||||
Map<String, List<DataScopeValue>> map = new HashMap<>();
|
||||
for (DataScope scope : scopes) {
|
||||
if (scope.superIgnore() && AuthInfoUtils.isSuper()) {
|
||||
continue;
|
||||
}
|
||||
String tableName = scope.table();
|
||||
List<DataScopeValue> list;
|
||||
if (map.containsKey(tableName)) {
|
||||
@@ -93,7 +97,8 @@ public class SelectInterceptor implements Interceptor {
|
||||
private Map<String, List<String>> buildIgnoreMap(List<DataScope> scopes) {
|
||||
Map<String, List<String>> map = new HashMap<>();
|
||||
for (DataScope scope : scopes) {
|
||||
if (scope.ignores() == null || scope.ignores().length == 0) {
|
||||
if (scope.ignores() == null || scope.ignores().length == 0
|
||||
|| (scope.superIgnore() && AuthInfoUtils.isSuper())) {
|
||||
continue;
|
||||
}
|
||||
String tableName = scope.table();
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package cn.com.mfish.common.oauth.service;
|
||||
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package cn.com.mfish.sys.service;
|
||||
package cn.com.mfish.common.sys.service;
|
||||
|
||||
import cn.com.mfish.common.core.web.ReqPage;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.sys.entity.Dict;
|
||||
import cn.com.mfish.sys.req.ReqDict;
|
||||
import cn.com.mfish.sys.api.entity.Dict;
|
||||
import cn.com.mfish.sys.api.req.ReqDict;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
@@ -81,10 +81,6 @@
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-captcha</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
@@ -26,17 +26,6 @@ public class RouteFunctionConfig {
|
||||
@Resource
|
||||
CheckCodeService checkCodeService;
|
||||
|
||||
/**
|
||||
* AI智能路由入口,仅用于让/aiRouter请求进入Gateway Filter链
|
||||
* 实际路由目标由AiRouteFilter动态覆盖,此处uri仅作占位
|
||||
*/
|
||||
@Bean
|
||||
public RouteLocator aiRouterRoute(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("aiRouter", r -> r.path("/aiRouter").uri("lb://mf-sys"))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM代理路由,将/v1开头的请求路由到mf-ai微服务
|
||||
* LLM慢速长连接由mf-ai处理,避免占用网关资源
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
package cn.com.mfish.gateway.filter;
|
||||
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.ai.service.AiRouteService;
|
||||
import cn.com.mfish.common.core.constants.ServiceConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
|
||||
/**
|
||||
* AI智能路由过滤器(微服务网关模式)
|
||||
* 拦截 /aiRouter POST请求,读取请求体,
|
||||
* 通过AiRouteService决策后动态路由到目标微服务,
|
||||
* 请求体原样透传到目标接口,后续AiRequest增加字段无需修改此处
|
||||
* <p>
|
||||
* 过滤器顺序说明:
|
||||
* 必须在 RouteToRequestUrlFilter(10000) 之后、ReactiveLoadBalancerClientFilter(10150) 之前执行,
|
||||
* 这样才能覆盖RouteToRequestUrlFilter设置的GATEWAY_REQUEST_URL_ATTR,使路由指向AI决策的目标服务
|
||||
*
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AiRouteFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private static final String AI_ROUTE_PATH = "/aiRouter";
|
||||
|
||||
private final AiRouteService aiRouteService;
|
||||
|
||||
public AiRouteFilter(AiRouteService aiRouteService) {
|
||||
this.aiRouteService = aiRouteService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Mono<Void> filter(ServerWebExchange exchange, @NonNull GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
|
||||
// 只拦截AI路由POST请求
|
||||
if (!AI_ROUTE_PATH.equals(path) || request.getMethod() != HttpMethod.POST) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 读取请求体,缓存后用于解析参数和透传到下游
|
||||
return DataBufferUtils.join(request.getBody())
|
||||
.map(dataBuffer -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return bytes;
|
||||
})
|
||||
.defaultIfEmpty(new byte[0])
|
||||
.flatMap(bytes -> {
|
||||
// 从请求体解析sessionId和prompt用于AI路由决策
|
||||
String sessionId = null;
|
||||
String prompt = null;
|
||||
if (bytes.length > 0) {
|
||||
String body = new String(bytes, StandardCharsets.UTF_8);
|
||||
sessionId = extractJsonValue(body, "sessionId");
|
||||
String content = extractJsonValue(body, "content");
|
||||
if (content != null) {
|
||||
prompt = content;
|
||||
}
|
||||
}
|
||||
log.info("[AI路由] 接收到智能路由请求, sessionId={}, prompt={}", sessionId, prompt);
|
||||
|
||||
return aiRouteService.resolve(sessionId, prompt)
|
||||
.flatMap(vo -> forwardTo(exchange, chain, vo, bytes));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态路由到目标微服务
|
||||
* 1. 将网关路径(如/sys/ai/assist)转换为服务实际路径(如/ai/assist)
|
||||
* 2. 通过 GATEWAY_REQUEST_URL_ATTR 设置目标URL,lb:// 协议触发 LoadBalancer 解析
|
||||
* 3. 同时修改请求URI路径,确保下游服务接收到正确的路径(而非原始/aiRouter)
|
||||
* 4. 请求体原样透传(用装饰器包装缓存后的byte数组供下游重复读取)
|
||||
*/
|
||||
private Mono<Void> forwardTo(ServerWebExchange exchange, GatewayFilterChain chain, AiRouterVo vo, byte[] bodyBytes) {
|
||||
String serviceId = vo.getServiceId();
|
||||
String gatewayPath = vo.getPath();
|
||||
|
||||
// 将网关路径转换为服务实际路径(去掉网关前缀,如/sys/ai/assist → /ai/assist)
|
||||
ServiceConstants.MfService mfService = ServiceConstants.MfService.fromValue(serviceId);
|
||||
String servicePath = mfService != null ? mfService.toServicePath(gatewayPath) : gatewayPath;
|
||||
|
||||
// 构建目标URI,使用服务实际路径
|
||||
URI targetUri = URI.create("lb://" + serviceId + servicePath);
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, targetUri);
|
||||
|
||||
// 修改请求URI,将原始路径/aiRouter替换为服务实际路径
|
||||
URI originalUri = exchange.getRequest().getURI();
|
||||
URI newUri = rebuildUri(originalUri, servicePath);
|
||||
|
||||
DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
|
||||
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||
@Override
|
||||
public @NonNull URI getURI() {
|
||||
return newUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Flux<DataBuffer> getBody() {
|
||||
if (bodyBytes.length > 0) {
|
||||
return Flux.just(dataBufferFactory.wrap(bodyBytes));
|
||||
}
|
||||
return Flux.empty();
|
||||
}
|
||||
};
|
||||
|
||||
log.info("[AI路由] 动态路由到, targetUri={}, 网关路径={}, 服务路径={}", targetUri, gatewayPath, servicePath);
|
||||
return chain.filter(exchange.mutate().request(decorator).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建URI,将原始路径替换为新路径,保留原始的query和fragment
|
||||
*/
|
||||
private URI rebuildUri(URI originalUri, String newPath) {
|
||||
return URI.create(originalUri.getScheme() + "://"
|
||||
+ originalUri.getRawAuthority()
|
||||
+ newPath
|
||||
+ (originalUri.getRawQuery() != null ? "?" + originalUri.getRawQuery() : "")
|
||||
+ (originalUri.getRawFragment() != null ? "#" + originalUri.getRawFragment() : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON字符串中简单提取字段值(避免引入额外JSON库依赖)
|
||||
*/
|
||||
private String extractJsonValue(String json, String key) {
|
||||
String pattern = "\"" + key + "\"";
|
||||
int keyIndex = json.indexOf(pattern);
|
||||
if (keyIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
int colonIndex = json.indexOf(':', keyIndex + pattern.length());
|
||||
if (colonIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
// 找值的起始位置
|
||||
int valueStart = colonIndex + 1;
|
||||
while (valueStart < json.length() && json.charAt(valueStart) == ' ') {
|
||||
valueStart++;
|
||||
}
|
||||
if (valueStart >= json.length()) {
|
||||
return null;
|
||||
}
|
||||
if (json.charAt(valueStart) == '"') {
|
||||
// 字符串值
|
||||
int valueEnd = json.indexOf('"', valueStart + 1);
|
||||
if (valueEnd < 0) {
|
||||
return null;
|
||||
}
|
||||
return json.substring(valueStart + 1, valueEnd);
|
||||
}
|
||||
// 非字符串值(数字、布尔等),取到逗号或}为止
|
||||
int valueEnd = valueStart;
|
||||
while (valueEnd < json.length() && json.charAt(valueEnd) != ',' && json.charAt(valueEnd) != '}') {
|
||||
valueEnd++;
|
||||
}
|
||||
return json.substring(valueStart, valueEnd).trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// 必须在 RouteToRequestUrlFilter(10000) 之后,ReactiveLoadBalancerClientFilter(10150) 之前
|
||||
return 10100;
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,5 @@
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-captcha</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,92 +0,0 @@
|
||||
package cn.com.mfish.oauth.agent;
|
||||
|
||||
import cn.com.mfish.common.ai.agent.BaseAssistant;
|
||||
import cn.com.mfish.common.core.utils.AuthInfoUtils;
|
||||
import cn.com.mfish.common.core.utils.StringUtils;
|
||||
import cn.com.mfish.common.oauth.service.SsoUserService;
|
||||
import cn.com.mfish.oauth.tools.MenuTools;
|
||||
import cn.com.mfish.oauth.tools.UserTools;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
|
||||
|
||||
/**
|
||||
* @description: 认证中心助手
|
||||
* @author: mfish
|
||||
* @date: 2025/8/22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OauthAssistant extends BaseAssistant {
|
||||
private static final String DEFAULT_PROMPT = "你好,简单介绍下认证中心助手";
|
||||
private final ChatClient chatClient;
|
||||
@Resource
|
||||
MenuTools menuTools;
|
||||
@Resource
|
||||
SsoUserService ssoUserService;
|
||||
|
||||
public OauthAssistant(ChatModel openAiChatModel, ChatMemory chatMemory) {
|
||||
this.chatClient = ChatClient.builder(openAiChatModel)
|
||||
.defaultSystem("""
|
||||
你是“摸鱼低代码”的认证中心助手,是一个可爱的傻白甜萝莉,你会用可爱的语言和我聊天解决问题!
|
||||
当有人问“摸鱼低代码”相关信息时,实际是在问我们整个平台的信息
|
||||
你主要辅助用户完成认证中心的一些基础操作
|
||||
你能够通过调用工具来完成认证中心基础操作,包括查询、新增、修改、删除、导入、导出等
|
||||
你主要辅助用户完成认证中心相关模块的信息检索、执行操作
|
||||
认证中心主要包含 (菜单信息、组织信息、角色信息、帐号信息、租户信息 等)
|
||||
|
||||
注意:
|
||||
1. 我需要你调用工具来帮我回答问题,不要自己编造答案
|
||||
2. 不要随意修改查询结果,只返回工具返回的内容
|
||||
3. 如果未找到相应工具,告诉用户你执行不了这个操作
|
||||
4. 如果工具参数缺失,提示用户输入缺失参数,引导用户输入有用信息
|
||||
5. 请使用中文回答
|
||||
""")
|
||||
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||
MessageChatMemoryAdvisor.builder(chatMemory).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @param prompt 提示词
|
||||
* @return 聊天信息
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> chat(String sessionId, String prompt) {
|
||||
if (StringUtils.isEmpty(prompt.trim())) {
|
||||
prompt = DEFAULT_PROMPT;
|
||||
}
|
||||
String userId = AuthInfoUtils.getCurrentUserId();
|
||||
String tenantId = AuthInfoUtils.getCurrentTenantId();
|
||||
String finalPrompt = prompt;
|
||||
ChatClient.ChatClientRequestSpec requestSpec = this.chatClient.prompt()
|
||||
.system("你必须先调用工具,再基于工具结果回答问题")
|
||||
.user(finalPrompt)
|
||||
.advisors(a -> a.param(CONVERSATION_ID, sessionId))
|
||||
.tools(menuTools, new UserTools(userId, tenantId, ssoUserService));
|
||||
return requestSpec
|
||||
.stream()
|
||||
.chatResponse()
|
||||
.onErrorResume(e -> {
|
||||
log.warn("流式调用失败,降级为非流式调用: {}", e.getMessage());
|
||||
return Mono.fromCallable(() -> requestSpec.call().chatResponse())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flux();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.log.annotation.Log;
|
||||
import cn.com.mfish.common.oauth.annotation.RequiresPermissions;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.service.SsoMenuService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
@@ -22,7 +22,7 @@ import cn.com.mfish.common.oauth.common.OauthUtils;
|
||||
import cn.com.mfish.common.oauth.entity.RedisAccessToken;
|
||||
import cn.com.mfish.common.oauth.entity.SsoRole;
|
||||
import cn.com.mfish.common.oauth.entity.WeChatToken;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoOrg;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoUser;
|
||||
import cn.com.mfish.common.oauth.service.SsoMenuService;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.com.mfish.oauth.mapper;
|
||||
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -10,7 +10,7 @@ import cn.com.mfish.common.oauth.common.OauthUtils;
|
||||
import cn.com.mfish.oauth.cache.common.ClearCache;
|
||||
import cn.com.mfish.common.oauth.api.entity.SsoMenu;
|
||||
import cn.com.mfish.oauth.mapper.SsoMenuMapper;
|
||||
import cn.com.mfish.common.oauth.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.api.req.ReqSsoMenu;
|
||||
import cn.com.mfish.common.oauth.service.SsoMenuService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
@@ -39,6 +39,17 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<!-- WebFlux替代Servlet,LLM长连接不占用线程 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<!-- WebFlux版Swagger -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
<version>${swagger.doc.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>mf-ai</finalName>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -49,6 +49,16 @@
|
||||
<systemPath>${project.basedir}/lib/mf-nocode-${mfish.version}.jar</systemPath>
|
||||
<version>${mfish.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-workflow</artifactId>
|
||||
<version>${mfish.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-ai</artifactId>
|
||||
<version>${mfish.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-bi</artifactId>
|
||||
@@ -62,15 +72,6 @@
|
||||
<artifactId>mf-common-api</artifactId>
|
||||
<version>${mfish.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-prometheus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-workflow</artifactId>
|
||||
<version>${mfish.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
+1
-118
@@ -1,30 +1,14 @@
|
||||
package cn.com.mfish.boot.controller;
|
||||
|
||||
import cn.com.mfish.common.ai.entity.AiRequest;
|
||||
import cn.com.mfish.common.ai.entity.AiRouterVo;
|
||||
import cn.com.mfish.common.ai.service.AiRouteService;
|
||||
import cn.com.mfish.common.captcha.service.CheckCodeService;
|
||||
import cn.com.mfish.common.core.web.Result;
|
||||
import cn.com.mfish.common.prom.annotation.MetricsMonitor;
|
||||
import cn.com.mfish.common.prom.annotation.MetricsMonitors;
|
||||
import cn.com.mfish.common.prom.enums.MetricEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -38,10 +22,6 @@ import java.util.Map;
|
||||
public class BootController {
|
||||
@Resource
|
||||
CheckCodeService checkCodeService;
|
||||
@Resource
|
||||
AiRouteService aiRouteService;
|
||||
@Resource
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Operation(summary = "获取验证码", description = "获取验证码")
|
||||
@GetMapping("/captcha")
|
||||
@@ -55,101 +35,4 @@ public class BootController {
|
||||
return Result.fail("错误:未授权,请联系管理员");
|
||||
}
|
||||
|
||||
/**
|
||||
* AI智能路由(单实例模式)
|
||||
* 同步阻塞等待AI路由决策完成后,在原始请求线程上forward到目标Controller,
|
||||
* 避免异步处理上下文冲突(BootController的Mono异步与目标Controller的Flux SSE异步冲突)
|
||||
* forward使用缓存body的CachingRequestWrapper,确保目标@RequestBody能读取到完整请求体
|
||||
*/
|
||||
@Operation(summary = "Ai路由", description = "Ai路由")
|
||||
@PostMapping("/aiRouter")
|
||||
@MetricsMonitors({
|
||||
@MetricsMonitor(metricEnum = MetricEnum.MFISH_REQUEST_COUNT, tagValues = {"POST", "/aiRouter"}),
|
||||
@MetricsMonitor(metricEnum = MetricEnum.MFISH_REQUEST_DURATION, tagValues = {"POST", "/aiRouter"})
|
||||
})
|
||||
public void aiRouter(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||
// 读取原始请求体并缓存
|
||||
byte[] bodyBytes = request.getInputStream().readAllBytes();
|
||||
AiRequest aiRequest = bodyBytes.length > 0
|
||||
? objectMapper.readValue(bodyBytes, AiRequest.class)
|
||||
: new AiRequest();
|
||||
|
||||
String sessionId = aiRequest.getSessionId();
|
||||
String prompt = aiRequest.getMessage() != null ? aiRequest.getMessage().getContent() : null;
|
||||
log.info("[AI路由-单实例] 接收到智能路由请求, sessionId={}, prompt={}", sessionId, prompt);
|
||||
|
||||
// 同步阻塞等待路由决策完成
|
||||
AiRouterVo vo = aiRouteService.resolve(sessionId, prompt).block();
|
||||
if (vo == null) {
|
||||
vo = new AiRouterVo();
|
||||
}
|
||||
|
||||
String targetPath = vo.getPath();
|
||||
log.info("[AI路由-单实例] 内部转发到, path={}", targetPath);
|
||||
|
||||
// 用缓存body的wrapper包装request,forward到目标Controller
|
||||
CachingRequestWrapper cachedRequest = new CachingRequestWrapper(request, bodyBytes);
|
||||
request.getRequestDispatcher(targetPath).forward(cachedRequest, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存请求体的Request包装器
|
||||
* 解决Servlet输入流只能读取一次的问题
|
||||
*/
|
||||
static class CachingRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final byte[] cachedBody;
|
||||
|
||||
CachingRequestWrapper(HttpServletRequest request, byte[] cachedBody) {
|
||||
super(request);
|
||||
this.cachedBody = cachedBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
return new CachedServletInputStream(cachedBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
return cachedBody.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return cachedBody.length;
|
||||
}
|
||||
}
|
||||
|
||||
static class CachedServletInputStream extends ServletInputStream {
|
||||
private final ByteArrayInputStream inputStream;
|
||||
|
||||
CachedServletInputStream(byte[] data) {
|
||||
this.inputStream = new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return inputStream.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException("不支持异步读取");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) {
|
||||
return inputStream.read(b, off, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ spring:
|
||||
api-key: sk-or-v1-b49*****************************************135dae3b
|
||||
base-url: https://openrouter.ai/api
|
||||
chat:
|
||||
options:
|
||||
max-tokens: 1000
|
||||
temperature: 0.7
|
||||
max-tokens: 1000
|
||||
temperature: 0.7
|
||||
ollama:
|
||||
base-url: http://localhost:11434
|
||||
chat:
|
||||
model: qwen3:8b
|
||||
model: qwen3:8b
|
||||
ai:
|
||||
api-key:
|
||||
hex-key: 8ca90bd312db42aac5a8313ce9a0eac3
|
||||
|
||||
@@ -27,12 +27,12 @@ wechat:
|
||||
appId: wx*************4a3
|
||||
secret: a6***************efa227
|
||||
gitee:
|
||||
clientId: a83ea66522fef5f382c6aaffa875c7e87b21d3933277c44a326cd3407fc0b651
|
||||
clientSecret: 3ebdce2afcb4a7df9b16355f60d898d452f4d9cf8ad1ab8279bb3bb465e614db
|
||||
clientId: a83ea6***********************************44a326cd3407fc0b651
|
||||
clientSecret: 3eb****************************************9bb3bb465e614db
|
||||
redirectUri: http://localhost:5186/giteeOauth2
|
||||
bindUri: http://localhost:5186/tenant/info/1?callback=gitee
|
||||
github:
|
||||
clientId: Ov23li7oH0Djw3ZEJCoJ
|
||||
clientSecret: ada9a510b3bde542e7510142f07d32a5f6bed131
|
||||
clientId: Ov2******************************3ZEJCoJ
|
||||
clientSecret: ada9a************************32a5f6bed131
|
||||
redirectUri: http://localhost:5186/githubOauth2
|
||||
bindUri: http://localhost:5186/tenant/info/1?callback=github
|
||||
Binary file not shown.
Binary file not shown.
@@ -45,10 +45,6 @@
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.mfish</groupId>
|
||||
<artifactId>mf-common-workflow</artifactId>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user