mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-13 20:53:42 +08:00
Merge branch 'main' into main
This commit is contained in:
@@ -23,6 +23,9 @@
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-wechat</module>
|
||||
<module>ruoyi-graph</module>
|
||||
<!-- 新添加的数字人模块-->
|
||||
<module>ruoyi-aihuman</module>
|
||||
<module>ruoyi-workflow</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
74
ruoyi-modules/ruoyi-aihuman/pom.xml
Normal file
74
ruoyi-modules/ruoyi-aihuman/pom.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
|
||||
<description>
|
||||
aihuman模块
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<easyexcel.version>3.2.1</easyexcel.version>
|
||||
</properties>
|
||||
|
||||
<!-- 按照用户要求,不添加任何依赖 -->
|
||||
<dependencies>
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--velocity代码生成使用模板 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba</groupId>-->
|
||||
<!-- <artifactId>easyexcel</artifactId>-->
|
||||
<!-- <version>${easyexcel.version}</version>-->
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>org.apache.poi</groupId>-->
|
||||
<!-- <artifactId>poi-ooxml-schemas</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-excel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.aihuman.service.AihumanConfigService;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* 交互数字人配置
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
|
||||
//临时免登录
|
||||
@SaIgnore
|
||||
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/aihumanConfig")
|
||||
public class AihumanConfigController extends BaseController {
|
||||
|
||||
private final AihumanConfigService aihumanConfigService;
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AihumanConfigVo> list(AihumanConfigBo bo, PageQuery pageQuery) {
|
||||
return aihumanConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出交互数字人配置列表
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:export")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(AihumanConfigBo bo, HttpServletResponse response) {
|
||||
List<AihumanConfigVo> list = aihumanConfigService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "交互数字人配置", AihumanConfigVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取交互数字人配置详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AihumanConfigVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Integer id) {
|
||||
return R.ok(aihumanConfigService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:add")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody AihumanConfigBo bo) {
|
||||
return toAjax(aihumanConfigService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:edit")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody AihumanConfigBo bo) {
|
||||
return toAjax(aihumanConfigService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除交互数字人配置
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("aihuman:aihumanConfig:remove")
|
||||
@Log(title = "交互数字人配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Integer[] ids) {
|
||||
return toAjax(aihumanConfigService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已发布的交互数字人配置列表
|
||||
* 只返回 publish = 1 的数据
|
||||
*/
|
||||
@GetMapping("/publishedList")
|
||||
public TableDataInfo<AihumanConfigVo> publishedList(PageQuery pageQuery) {
|
||||
// 创建查询条件对象并设置publish=1
|
||||
AihumanConfigBo bo = new AihumanConfigBo();
|
||||
bo.setPublish(1);
|
||||
// 调用现有的查询方法,传入预设了publish=1条件的bo对象
|
||||
return aihumanConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.ruoyi.aihuman.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.aihuman.service.IAihumanInfoService;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Controller
|
||||
* 免登录接口,方便验证
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@SaIgnore
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/aihuman/info")
|
||||
public class AihumanInfoController {
|
||||
|
||||
private final IAihumanInfoService aihumanInfoService;
|
||||
|
||||
/**
|
||||
* 获取AI人类交互信息详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R<AihumanInfoVo> getInfo(@PathVariable Long id) {
|
||||
return R.ok(aihumanInfoService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<TableDataInfo<AihumanInfoVo>> list(AihumanInfo aihumanInfo, PageQuery pageQuery) {
|
||||
TableDataInfo<AihumanInfoVo> tableDataInfo = aihumanInfoService.queryPageList(aihumanInfo, pageQuery);
|
||||
return R.ok(tableDataInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
@PostMapping
|
||||
public R<Integer> add(@Validated @RequestBody AihumanInfo aihumanInfo) {
|
||||
return R.ok(aihumanInfoService.insert(aihumanInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
@PutMapping
|
||||
public R<Integer> edit(@Validated @RequestBody AihumanInfo aihumanInfo) {
|
||||
return R.ok(aihumanInfoService.update(aihumanInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除AI人类交互信息
|
||||
*/
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Integer> remove(@PathVariable Long[] ids) {
|
||||
return R.ok(aihumanInfoService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试接口
|
||||
* 提供一个简单的GET接口用于快速验证控制器是否正常工作
|
||||
*/
|
||||
@GetMapping("/test")
|
||||
public R<String> test() {
|
||||
return R.ok("AI Human Controller is working!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 交互数字人配置对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@TableName("aihuman_config")
|
||||
public class AihumanConfig implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
private String modelParams;
|
||||
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
private String agentParams;
|
||||
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
private Integer publish;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.ruoyi.aihuman.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI人类交互信息实体类
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Data
|
||||
@TableName("aihuman_info")
|
||||
public class AihumanInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 交互名称 */
|
||||
private String name;
|
||||
|
||||
/** 交互内容 */
|
||||
private String content;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/** 删除标志(0代表存在 2代表删除) */
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.ruoyi.aihuman.domain.bo;
|
||||
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.core.domain.BaseEntity;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.io.Serializable;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import java.io.Serializable;
|
||||
import java.io.Serializable;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
|
||||
/**
|
||||
* 交互数字人配置业务对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
|
||||
@AutoMapper(target = AihumanConfig.class, reverseConvertGenerate = false)
|
||||
public class AihumanConfigBo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
private String modelName;
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
private String modelPath;
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
private String modelParams;
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
private String agentParams;
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
@NotNull(message = "status不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Integer status;
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
@NotNull(message = "publish不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Integer publish;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.ruoyi.aihuman.domain.bo;
|
||||
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.core.domain.BaseEntity;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.io.Serializable;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import java.io.Serializable;
|
||||
import java.io.Serializable;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
|
||||
/**
|
||||
* 数字人信息管理业务对象 aihuman_info
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 20:03:06 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
|
||||
@AutoMapper(target = AihumanInfo.class, reverseConvertGenerate = false)
|
||||
public class AihumanInfoBo implements Serializable {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 交互名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 交互内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.ruoyi.aihuman.domain.vo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.io.Serializable;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 交互数字人配置视图对象 aihuman_config
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = AihumanConfig.class)
|
||||
public class AihumanConfigVo implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
@ExcelProperty(value = "name")
|
||||
private String name;
|
||||
/**
|
||||
* modelName
|
||||
*/
|
||||
@ExcelProperty(value = "modelName")
|
||||
private String modelName;
|
||||
/**
|
||||
* modelPath
|
||||
*/
|
||||
@ExcelProperty(value = "modelPath")
|
||||
private String modelPath;
|
||||
/**
|
||||
* modelParams
|
||||
*/
|
||||
@ExcelProperty(value = "modelParams")
|
||||
private String modelParams;
|
||||
/**
|
||||
* agentParams
|
||||
*/
|
||||
@ExcelProperty(value = "agentParams")
|
||||
private String agentParams;
|
||||
/**
|
||||
* createTime
|
||||
*/
|
||||
@ExcelProperty(value = "createTime")
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* updateTime
|
||||
*/
|
||||
@ExcelProperty(value = "updateTime")
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* status
|
||||
*/
|
||||
@ExcelProperty(value = "status", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_common_status")
|
||||
private Integer status;
|
||||
/**
|
||||
* publish
|
||||
*/
|
||||
@ExcelProperty(value = "publish", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_common_status")
|
||||
private Integer publish;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.ruoyi.aihuman.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI人类交互信息视图对象
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = AihumanInfo.class)
|
||||
public class AihumanInfoVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
private Long id;
|
||||
|
||||
/** 交互名称 */
|
||||
private String name;
|
||||
|
||||
/** 交互内容 */
|
||||
private String content;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.ruoyi.aihuman.mapper;
|
||||
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Mapper接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@Mapper
|
||||
public interface AihumanConfigMapper extends BaseMapperPlus<AihumanConfig, AihumanConfigVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.ruoyi.aihuman.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.ruoyi.core.mapper.BaseMapperPlus;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Mapper接口
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@Mapper
|
||||
public interface AihumanInfoMapper extends BaseMapperPlus<AihumanInfo, AihumanInfoVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
public interface AihumanConfigService {
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置
|
||||
*/
|
||||
AihumanConfigVo queryById(Integer id);
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
TableDataInfo<AihumanConfigVo> queryPageList(AihumanConfigBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
List<AihumanConfigVo> queryList(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
Boolean insertByBo(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
Boolean updateByBo(AihumanConfigBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除交互数字人配置信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.ruoyi.aihuman.service;
|
||||
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Service接口
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
public interface IAihumanInfoService {
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息
|
||||
*/
|
||||
AihumanInfoVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
TableDataInfo<AihumanInfoVo> queryPageList(AihumanInfo record, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
List<AihumanInfoVo> queryList(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
int insert(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
int update(AihumanInfo record);
|
||||
|
||||
/**
|
||||
* 批量删除AI人类交互信息
|
||||
*/
|
||||
int deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.ruoyi.aihuman.domain.bo.AihumanConfigBo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanConfigVo;
|
||||
import org.ruoyi.aihuman.domain.AihumanConfig;
|
||||
import org.ruoyi.aihuman.mapper.AihumanConfigMapper;
|
||||
import org.ruoyi.aihuman.service.AihumanConfigService;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 交互数字人配置Service业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Fri Sep 26 22:27:00 GMT+08:00 2025
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AihumanConfigServiceImpl implements AihumanConfigService {
|
||||
|
||||
private final AihumanConfigMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public AihumanConfigVo queryById(Integer id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AihumanConfigVo> queryPageList(AihumanConfigBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = buildQueryWrapper(bo);
|
||||
Page<AihumanConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询交互数字人配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<AihumanConfigVo> queryList(AihumanConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<AihumanConfig> buildQueryWrapper(AihumanConfigBo bo) {
|
||||
LambdaQueryWrapper<AihumanConfig> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getName()), AihumanConfig::getName, bo.getName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelName()), AihumanConfig::getModelName, bo.getModelName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelPath()), AihumanConfig::getModelPath, bo.getModelPath());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getModelParams()), AihumanConfig::getModelParams, bo.getModelParams());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAgentParams()), AihumanConfig::getAgentParams, bo.getAgentParams());
|
||||
lqw.eq(bo.getCreateTime() != null, AihumanConfig::getCreateTime, bo.getCreateTime());
|
||||
lqw.eq(bo.getUpdateTime() != null, AihumanConfig::getUpdateTime, bo.getUpdateTime());
|
||||
lqw.eq(bo.getStatus() != null, AihumanConfig::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getPublish() != null, AihumanConfig::getPublish, bo.getPublish());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(AihumanConfigBo bo) {
|
||||
AihumanConfig add = MapstructUtils.convert(bo, AihumanConfig. class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(AihumanConfigBo bo) {
|
||||
AihumanConfig update = MapstructUtils.convert(bo, AihumanConfig. class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(AihumanConfig entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除交互数字人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.ruoyi.aihuman.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.ruoyi.aihuman.domain.AihumanInfo;
|
||||
import org.ruoyi.aihuman.domain.vo.AihumanInfoVo;
|
||||
import org.ruoyi.aihuman.mapper.AihumanInfoMapper;
|
||||
import org.ruoyi.aihuman.service.IAihumanInfoService;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI人类交互信息Service业务层处理
|
||||
*
|
||||
* @author QingYunAI
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AihumanInfoServiceImpl implements IAihumanInfoService {
|
||||
|
||||
private final AihumanInfoMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public AihumanInfoVo queryById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<AihumanInfoVo> queryPageList(AihumanInfo record, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = buildQueryWrapper(record);
|
||||
Page<AihumanInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询AI人类交互信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<AihumanInfoVo> queryList(AihumanInfo record) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = buildQueryWrapper(record);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private LambdaQueryWrapper<AihumanInfo> buildQueryWrapper(AihumanInfo record) {
|
||||
LambdaQueryWrapper<AihumanInfo> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(record.getId() != null, AihumanInfo::getId, record.getId());
|
||||
lqw.like(StringUtils.isNotBlank(record.getName()), AihumanInfo::getName, record.getName());
|
||||
lqw.like(StringUtils.isNotBlank(record.getContent()), AihumanInfo::getContent, record.getContent());
|
||||
lqw.orderByDesc(AihumanInfo::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int insert(AihumanInfo record) {
|
||||
return baseMapper.insert(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int update(AihumanInfo record) {
|
||||
return baseMapper.updateById(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI人类交互信息
|
||||
*/
|
||||
@Override
|
||||
public int deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// 如果需要逻辑删除,MyBatis-Plus会自动处理
|
||||
// 这里的@TableLogic注解已经在实体类中配置
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666752, '交互数字人配置', '2000', '1', 'aihumanConfig', 'aihuman/aihumanConfig/index', 1, 0, 'C', '0', '0', 'aihuman:aihumanConfig:list', '#', 103, 1, sysdate(), null, null, '交互数字人配置菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666753, '交互数字人配置查询', 1971582278942666752, '1', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanConfig:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666754, '交互数字人配置新增', 1971582278942666752, '2', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanConfig:add', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666755, '交互数字人配置修改', 1971582278942666752, '3', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanConfig:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666756, '交互数字人配置删除', 1971582278942666752, '4', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanConfig:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971582278942666757, '交互数字人配置导出', 1971582278942666752, '5', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanConfig:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
@@ -0,0 +1,19 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597696, '数字人信息管理', '2000', '1', 'aihumanInfo', 'aihuman/aihumanInfo/index', 1, 0, 'C', '0', '0', 'aihuman:aihumanInfo:list', '#', 103, 1, sysdate(), null, null, '数字人信息管理菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597697, '数字人信息管理查询', 1971546066781597696, '1', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597698, '数字人信息管理新增', 1971546066781597696, '2', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:add', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597699, '数字人信息管理修改', 1971546066781597696, '3', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597700, '数字人信息管理删除', 1971546066781597696, '4', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(1971546066781597701, '数字人信息管理导出', 1971546066781597696, '5', '#', '', 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
@@ -0,0 +1,9 @@
|
||||
<?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="org.ruoyi.aihuman.mapper.AihumanInfoMapper">
|
||||
|
||||
<!-- 可在此添加自定义SQL语句 -->
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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="org.ruoyi.aihuman.mapper.AihumanConfigMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -45,6 +45,18 @@ public class ChatMessageController extends BaseController {
|
||||
return chatMessageService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会话ID查询聊天消息列表
|
||||
*/
|
||||
@GetMapping("/listBySession/{sessionId}")
|
||||
public TableDataInfo<ChatMessageVo> listBySession(@NotNull(message = "会话ID不能为空")
|
||||
@PathVariable Long sessionId,
|
||||
PageQuery pageQuery) {
|
||||
ChatMessageBo bo = new ChatMessageBo();
|
||||
bo.setSessionId(sessionId);
|
||||
return chatMessageService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出聊天消息列表
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package org.ruoyi.chat.service.chat;
|
||||
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 对话Service接口
|
||||
*
|
||||
@@ -13,9 +21,36 @@ public interface IChatService {
|
||||
|
||||
/**
|
||||
* 客户端发送消息到服务端
|
||||
*
|
||||
* @param chatRequest 请求对象
|
||||
*/
|
||||
SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter);
|
||||
|
||||
/**
|
||||
* 工作流场景:支持 langchain4j 的 StreamingChatResponseHandler
|
||||
*
|
||||
* @param chatRequest ruoyi-ai 的请求对象
|
||||
* @param handler langchain4j 的流式响应处理器
|
||||
*/
|
||||
default void chat(ChatRequest chatRequest, StreamingChatResponseHandler handler) {
|
||||
throw new UnsupportedOperationException("此服务暂不支持工作流场景");
|
||||
}
|
||||
|
||||
default dev.langchain4j.model.chat.request.ChatRequest convertToLangchainRequest(ChatRequest request) {
|
||||
List<ChatMessage> messages = new ArrayList<>();
|
||||
for (org.ruoyi.common.chat.entity.chat.Message msg : request.getMessages()) {
|
||||
// 简单转换,您可以根据实际需求调整
|
||||
if ("user".equals(msg.getRole())) {
|
||||
messages.add(UserMessage.from(msg.getContent().toString()));
|
||||
} else if ("system".equals(msg.getRole())) {
|
||||
messages.add(SystemMessage.from(msg.getContent().toString()));
|
||||
} else if ("assistant".equals(msg.getRole())) {
|
||||
messages.add(AiMessage.from(msg.getContent().toString()));
|
||||
}
|
||||
}
|
||||
return dev.langchain4j.model.chat.request.ChatRequest.builder().messages(messages).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此服务支持的模型类别
|
||||
*/
|
||||
|
||||
@@ -110,7 +110,7 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
BigDecimal numberCost = unitPrice
|
||||
.multiply(BigDecimal.valueOf(batches))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
log.debug("deductToken->按token扣费,结算token数量: {},批次数: {},单价: {},费用: {}",
|
||||
log.debug("deductToken->按token扣费,结算token数量: {},批次数: {},单价: {},费用: {}",
|
||||
billable, batches, unitPrice, numberCost);
|
||||
|
||||
try {
|
||||
@@ -137,7 +137,7 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
chatToken.setUserId(chatRequest.getUserId());
|
||||
chatToken.setToken(totalTokens);
|
||||
chatTokenService.editToken(chatToken);
|
||||
|
||||
|
||||
// 虽未扣费,但要更新消息的基本信息(实际token数、计费类型等)
|
||||
updateMessageWithoutBilling(chatRequest, tokens, chatModelVo.getModelType());
|
||||
}
|
||||
@@ -167,12 +167,6 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
chatMessageBo.setContent(chatRequest.getPrompt().trim());
|
||||
chatMessageBo.setModelName(chatRequest.getModel());
|
||||
|
||||
// // 基础消息信息,计费相关数据(tokens、费用、计费类型等)在扣费时统一设置
|
||||
// chatMessageBo.setTotalTokens(0); // 初始设为0,扣费时更新
|
||||
// chatMessageBo.setDeductCost(null);
|
||||
// chatMessageBo.setBillingType(null);
|
||||
// chatMessageBo.setRemark("用户消息");
|
||||
|
||||
try {
|
||||
chatMessageService.insertByBo(chatMessageBo);
|
||||
// 保存成功后,将生成的消息ID设置到ChatRequest中
|
||||
@@ -444,11 +438,11 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
preCheckBalance(chatRequest);
|
||||
return true; // 预检查通过,余额充足
|
||||
} catch (ServiceException e) {
|
||||
log.debug("checkBalanceSufficient->余额不足,用户ID: {}, 模型: {}, 错误: {}",
|
||||
log.debug("checkBalanceSufficient->余额不足,用户ID: {}, 模型: {}, 错误: {}",
|
||||
chatRequest.getUserId(), chatRequest.getModel(), e.getMessage());
|
||||
return false; // 预检查失败,余额不足
|
||||
} catch (Exception e) {
|
||||
log.error("checkBalanceSufficient->检查余额时发生异常,用户ID: {}, 模型: {}",
|
||||
log.error("checkBalanceSufficient->检查余额时发生异常,用户ID: {}, 模型: {}",
|
||||
chatRequest.getUserId(), chatRequest.getModel(), e);
|
||||
return false; // 异常情况视为余额不足,保守处理
|
||||
}
|
||||
|
||||
@@ -1,21 +1,46 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import okhttp3.Response;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.chat.support.ChatServiceHelper;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
/**
|
||||
* deepseek
|
||||
*/
|
||||
@@ -26,9 +51,24 @@ public class DeepSeekChatImpl implements IChatService {
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
|
||||
// 创建一个用于直接API调用的OkHttpClient
|
||||
private final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
|
||||
// 检查是否启用深度思考且是deepseek模型
|
||||
if (Boolean.TRUE.equals(chatRequest.getEnableThinking())) {
|
||||
return handleDeepSeekWithThinking(chatRequest, emitter, chatModelVo);
|
||||
}
|
||||
|
||||
StreamingChatModel chatModel = OpenAiStreamingChatModel.builder()
|
||||
.baseUrl(chatModelVo.getApiHost())
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
@@ -39,7 +79,31 @@ public class DeepSeekChatImpl implements IChatService {
|
||||
.build();
|
||||
// 发送流式消息
|
||||
try {
|
||||
chatModel.chat(chatRequest.getPrompt(), new StreamingChatResponseHandler() {
|
||||
// 构建消息列表,包含历史对话消息和当前用户消息
|
||||
List<ChatMessage> messages = new ArrayList<>();
|
||||
|
||||
// 添加历史对话消息
|
||||
if (chatRequest.getMessages() != null) {
|
||||
for (Message message : chatRequest.getMessages()) {
|
||||
// 检查消息内容是否有效
|
||||
if (message.getContent() == null || String.valueOf(message.getContent()).trim().isEmpty()) {
|
||||
continue; // 跳过空消息
|
||||
}
|
||||
|
||||
if (Message.Role.SYSTEM.getName().equals(message.getRole())) {
|
||||
messages.add(new SystemMessage(String.valueOf(message.getContent())));
|
||||
} else if (Message.Role.USER.getName().equals(message.getRole())) {
|
||||
messages.add(new UserMessage(String.valueOf(message.getContent())));
|
||||
} else if (Message.Role.ASSISTANT.getName().equals(message.getRole())) {
|
||||
messages.add(new dev.langchain4j.data.message.AiMessage(String.valueOf(message.getContent())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加当前用户消息
|
||||
messages.add(new UserMessage(chatRequest.getPrompt()));
|
||||
|
||||
chatModel.chat(messages, new StreamingChatResponseHandler() {
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void onPartialResponse(String partialResponse) {
|
||||
@@ -70,8 +134,236 @@ public class DeepSeekChatImpl implements IChatService {
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流场景:支持 langchain4j handler
|
||||
*/
|
||||
@Override
|
||||
public void chat(ChatRequest request, StreamingChatResponseHandler handler) {
|
||||
log.info("workflow chat, model: {}", request.getModel());
|
||||
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(request.getModel());
|
||||
|
||||
StreamingChatModel chatModel = OpenAiStreamingChatModel.builder()
|
||||
.baseUrl(chatModelVo.getApiHost())
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.temperature(0.7)
|
||||
.build();
|
||||
|
||||
try {
|
||||
// 将 ruoyi-ai 的 ChatRequest 转换为 langchain4j 的格式
|
||||
dev.langchain4j.model.chat.request.ChatRequest chatRequest = convertToLangchainRequest(request);
|
||||
chatModel.chat(chatRequest, handler);
|
||||
} catch (Exception e) {
|
||||
log.error("workflow deepseek请求失败:{}", e.getMessage(), e);
|
||||
throw new RuntimeException("DeepSeek workflow chat failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理启用深度思考的deepseek模型请求
|
||||
*/
|
||||
private SseEmitter handleDeepSeekWithThinking(ChatRequest chatRequest, SseEmitter emitter, ChatModelVo chatModelVo) {
|
||||
try {
|
||||
// 构建请求到外部API
|
||||
String url = chatModelVo.getApiHost() + "/v1/chat/completions";
|
||||
String apiKey = chatModelVo.getApiKey();
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", chatModelVo.getModelName());
|
||||
requestBody.put("response_format", Map.of("type", "text"));
|
||||
requestBody.put("max_tokens", 4000); // 修复:将max_tokens从81920改为4000,符合API要求
|
||||
requestBody.put("temperature", 1);
|
||||
requestBody.put("top_p", 1);
|
||||
requestBody.put("top_k", 50);
|
||||
requestBody.put("enable_thinking", chatRequest.getEnableThinking());
|
||||
requestBody.put("stream", chatRequest.getStream());
|
||||
|
||||
// 构建消息 - DeepSeek模型不需要系统提示词
|
||||
List<Map<String, Object>> messages = new ArrayList<>();
|
||||
|
||||
// 添加历史对话消息 (只添加用户和助手消息)
|
||||
if (chatRequest.getMessages() != null) {
|
||||
for (Message message : chatRequest.getMessages()) {
|
||||
// 检查消息内容是否有效
|
||||
if (message.getContent() == null || String.valueOf(message.getContent()).trim().isEmpty()) {
|
||||
continue; // 跳过空消息
|
||||
}
|
||||
|
||||
// DeepSeek模型在深度思考模式下只接受user和assistant角色的消息
|
||||
if (Message.Role.SYSTEM.getName().equals(message.getRole())) {
|
||||
// 跳过系统消息
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> historyMessage = new HashMap<>();
|
||||
historyMessage.put("role", message.getRole());
|
||||
historyMessage.put("content", String.valueOf(message.getContent()));
|
||||
messages.add(historyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加当前用户消息
|
||||
Map<String, Object> userMessage = new HashMap<>();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", String.valueOf(chatRequest.getPrompt()));
|
||||
messages.add(userMessage);
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
|
||||
// 创建ObjectMapper实例
|
||||
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
String requestBodyStr = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
// 打印请求体用于调试
|
||||
log.info("打印请求体: {}", requestBodyStr);
|
||||
|
||||
// 创建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.header("Content-Type", "application/json")
|
||||
.post(RequestBody.create(requestBodyStr, JSON))
|
||||
.build();
|
||||
|
||||
// 执行异步请求
|
||||
this.client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
try {
|
||||
log.error("深度思考请求失败: {}", e.getMessage(), e);
|
||||
emitter.send("深度思考请求失败: " + e.getMessage());
|
||||
emitter.complete();
|
||||
} catch (IOException ioException) {
|
||||
log.error("发送错误消息失败: {}", ioException.getMessage(), ioException);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
// 打印完整的错误响应体
|
||||
String errorBody = "";
|
||||
if (response.body() != null) {
|
||||
errorBody = response.body().string();
|
||||
}
|
||||
log.error("深度思考请求失败,状态码: {},响应体: {}", response.code(), errorBody);
|
||||
try {
|
||||
emitter.send("深度思考请求失败,状态码: " + response.code() + ",响应体: " + errorBody);
|
||||
emitter.complete();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
log.error("发送错误消息失败: {}", e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
if (responseBody == null) {
|
||||
log.error("响应体为空");
|
||||
emitter.send("响应体为空");
|
||||
emitter.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// 流式读取响应
|
||||
processThinkingResponse(responseBody, emitter);
|
||||
} catch (Exception e) {
|
||||
log.error("处理响应时出错: {}", e.getMessage(), e);
|
||||
try {
|
||||
emitter.send("处理响应时出错: " + e.getMessage());
|
||||
emitter.complete();
|
||||
} catch (IOException ioException) {
|
||||
log.error("发送错误消息失败: {}", ioException.getMessage(), ioException);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理深度思考请求时出错: {}", e.getMessage(), e);
|
||||
ChatServiceHelper.onStreamError(emitter, e.getMessage());
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理深度思考的流式响应(边解析边推送)
|
||||
*/
|
||||
private void processThinkingResponse(ResponseBody responseBody, SseEmitter emitter) throws IOException {
|
||||
// 标记是否进入正式回答阶段
|
||||
boolean thinkingComplete = false;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(responseBody.charStream())) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.startsWith("data: ")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String jsonData = line.substring(6).trim();
|
||||
if ("[DONE]".equals(jsonData)) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map<String, Object> chunk = mapper.readValue(jsonData, Map.class);
|
||||
|
||||
if (chunk.containsKey("choices") && chunk.get("choices") instanceof List) {
|
||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) chunk.get("choices");
|
||||
if (!choices.isEmpty()) {
|
||||
Map<String, Object> choice = choices.get(0);
|
||||
if (choice.containsKey("delta") && choice.get("delta") instanceof Map) {
|
||||
Map<String, Object> delta = (Map<String, Object>) choice.get("delta");
|
||||
|
||||
// 推送思考过程
|
||||
if (delta.containsKey("reasoning_content") && delta.get("reasoning_content") != null) {
|
||||
String reasoningChunk = delta.get("reasoning_content").toString();
|
||||
emitter.send(SseEmitter.event().data(reasoningChunk).name("thinking"));
|
||||
log.debug("Reasoning Chunk: {}", reasoningChunk);
|
||||
}
|
||||
|
||||
// 推送正式回答
|
||||
if (delta.containsKey("content") && delta.get("content") != null) {
|
||||
String content = delta.get("content").toString();
|
||||
|
||||
// 第一次进入回答阶段时,加个提示头
|
||||
if (!thinkingComplete) {
|
||||
emitter.send(SseEmitter.event().data("\n\n回答内容:\n").name("answer-header"));
|
||||
thinkingComplete = true;
|
||||
}
|
||||
|
||||
emitter.send(SseEmitter.event().data(content).name("answer"));
|
||||
log.debug("Answer Chunk:{}", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析JSON数据失败,忽略本行: {}", jsonData, e);
|
||||
}
|
||||
}
|
||||
|
||||
emitter.complete();
|
||||
log.info("深度思考流式响应完成");
|
||||
} catch (IOException e) {
|
||||
log.error("读取响应流时出错: {}", e.getMessage(), e);
|
||||
try {
|
||||
emitter.send(SseEmitter.event().data("读取响应流时出错: " + e.getMessage()).name("error"));
|
||||
emitter.complete();
|
||||
} catch (IOException ioException) {
|
||||
log.error("发送错误消息失败: {}", ioException.getMessage(), ioException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.DEEPSEEK.getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
this.chatClient = chatClientBuilder
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder().model("gpt-4o-mini").build())
|
||||
.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClients))
|
||||
.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.chat.support.ChatServiceHelper;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.ruoyi.chat.support.ChatServiceHelper;
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ import org.ruoyi.chat.support.ChatServiceHelper;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class QianWenAiChatServiceImpl implements IChatService {
|
||||
public class QianWenAiChatServiceImpl implements IChatService {
|
||||
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
@@ -37,7 +37,6 @@ public class QianWenAiChatServiceImpl implements IChatService {
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
// 发送流式消息
|
||||
try {
|
||||
model.chat(chatRequest.getPrompt(), new StreamingChatResponseHandler() {
|
||||
@@ -70,11 +69,34 @@ public class QianWenAiChatServiceImpl implements IChatService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流场景:支持 langchain4j handler
|
||||
*/
|
||||
@Override
|
||||
public void chat(ChatRequest request, StreamingChatResponseHandler handler) {
|
||||
log.info("workflow chat, model: {}", request.getModel());
|
||||
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(request.getModel());
|
||||
|
||||
StreamingChatModel model = QwenStreamingChatModel.builder()
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
try {
|
||||
// 将 ruoyi-ai 的 ChatRequest 转换为 langchain4j 的格式
|
||||
dev.langchain4j.model.chat.request.ChatRequest chatRequest = convertToLangchainRequest(request);
|
||||
model.chat(chatRequest, handler);
|
||||
} catch (Exception e) {
|
||||
log.error("workflow 千问请求失败:{}", e.getMessage(), e);
|
||||
throw new RuntimeException("QianWen workflow chat failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.QIANWEN.getCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -100,24 +100,7 @@ public class SseServiceImpl implements ISseService {
|
||||
// 设置用户id
|
||||
chatRequest.setUserId(LoginHelper.getUserId());
|
||||
|
||||
|
||||
//待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId)
|
||||
//待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId)
|
||||
//待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId)
|
||||
{
|
||||
// 设置会话id
|
||||
if (chatRequest.getUuid() == null) {
|
||||
//暂时随机生成会话id
|
||||
chatRequest.setSessionId(System.currentTimeMillis());
|
||||
} else {
|
||||
//这里或许需要修改一下,这里应该用uuid 或者 前端传递 sessionId
|
||||
chatRequest.setSessionId(chatRequest.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
chatRequest.setUserId(chatCostService.getUserId());
|
||||
// 设置会话id
|
||||
if (chatRequest.getSessionId() == null) {
|
||||
ChatSessionBo chatSessionBo = new ChatSessionBo();
|
||||
chatSessionBo.setUserId(chatCostService.getUserId());
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.chain.loader.ResourceLoader;
|
||||
import org.ruoyi.chain.loader.ResourceLoaderFactory;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
@@ -91,45 +92,66 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
public TableDataInfo<KnowledgeInfoVo> queryPageListByRole(KnowledgeInfoBo bo, PageQuery pageQuery) {
|
||||
// 查询用户关联角色
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (StringUtils.isEmpty(loginUser.getKroleGroupIds()) || StringUtils.isEmpty(loginUser.getKroleGroupType())) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
|
||||
// 角色/角色组id列表
|
||||
List<String> groupIdList = Arrays.stream(loginUser.getKroleGroupIds().split(","))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.toList();
|
||||
List<KnowledgeRole> knowledgeRoles;
|
||||
LambdaQueryWrapper<KnowledgeRole> roleLqw = Wrappers.lambdaQuery();
|
||||
if ("role".equals(loginUser.getKroleGroupType())) {
|
||||
roleLqw.in(KnowledgeRole::getId, groupIdList);
|
||||
} else {
|
||||
roleLqw.in(KnowledgeRole::getGroupId, groupIdList);
|
||||
}
|
||||
knowledgeRoles = knowledgeRoleMapper.selectList(roleLqw);
|
||||
|
||||
if (CollectionUtils.isEmpty(knowledgeRoles)) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
|
||||
// 查询知识库id列表
|
||||
LambdaQueryWrapper<KnowledgeRoleRelation> relationLqw = Wrappers.lambdaQuery();
|
||||
relationLqw.in(KnowledgeRoleRelation::getKnowledgeRoleId, knowledgeRoles.stream().map(KnowledgeRole::getId).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
List<KnowledgeRoleRelation> knowledgeRoleRelations = knowledgeRoleRelationMapper.selectList(relationLqw);
|
||||
|
||||
if (CollectionUtils.isEmpty(knowledgeRoleRelations)) {
|
||||
return new TableDataInfo<>();
|
||||
}
|
||||
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
|
||||
// 在查询用户创建的知识库条件下,拼接角色分配知识库
|
||||
lqw.or(q -> q.in(
|
||||
KnowledgeInfo::getId,
|
||||
knowledgeRoleRelations.stream()
|
||||
.map(KnowledgeRoleRelation::getKnowledgeId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
));
|
||||
|
||||
// 管理员用户直接查询所有数据
|
||||
if (Objects.equals(loginUser.getUserId(), 1L)) {
|
||||
Page<KnowledgeInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
// 检查用户是否配置了角色信息
|
||||
if (StringUtils.isNotEmpty(loginUser.getKroleGroupIds()) && StringUtils.isNotEmpty(loginUser.getKroleGroupType())) {
|
||||
// 角色/角色组id列表
|
||||
List<String> groupIdList = Arrays.stream(loginUser.getKroleGroupIds().split(","))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.toList();
|
||||
|
||||
// 查询用户关联的角色
|
||||
List<KnowledgeRole> knowledgeRoles = new ArrayList<>();
|
||||
LambdaQueryWrapper<KnowledgeRole> roleLqw = Wrappers.lambdaQuery();
|
||||
if ("role".equals(loginUser.getKroleGroupType())) {
|
||||
roleLqw.in(KnowledgeRole::getId, groupIdList);
|
||||
} else {
|
||||
roleLqw.in(KnowledgeRole::getGroupId, groupIdList);
|
||||
}
|
||||
knowledgeRoles = knowledgeRoleMapper.selectList(roleLqw);
|
||||
|
||||
// 如果用户有关联角色
|
||||
if (!CollectionUtils.isEmpty(knowledgeRoles)) {
|
||||
// 查询这些角色关联的知识库
|
||||
LambdaQueryWrapper<KnowledgeRoleRelation> relationLqw = Wrappers.lambdaQuery();
|
||||
relationLqw.in(KnowledgeRoleRelation::getKnowledgeRoleId,
|
||||
knowledgeRoles.stream().map(KnowledgeRole::getId).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
List<KnowledgeRoleRelation> knowledgeRoleRelations = knowledgeRoleRelationMapper.selectList(relationLqw);
|
||||
|
||||
// 如果角色关联了知识库
|
||||
if (!CollectionUtils.isEmpty(knowledgeRoleRelations)) {
|
||||
// 查询用户自己的知识库和角色分配的知识库
|
||||
lqw.and(q -> q.eq(KnowledgeInfo::getUid, loginUser.getUserId())
|
||||
.or()
|
||||
.in(KnowledgeInfo::getId,
|
||||
knowledgeRoleRelations.stream()
|
||||
.map(KnowledgeRoleRelation::getKnowledgeId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 用户没有关联任何知识库,只显示自己的
|
||||
lqw.eq(KnowledgeInfo::getUid, loginUser.getUserId());
|
||||
}
|
||||
} else {
|
||||
// 用户没有关联角色,只显示自己的
|
||||
lqw.eq(KnowledgeInfo::getUid, loginUser.getUserId());
|
||||
}
|
||||
} else {
|
||||
// 用户没有配置角色信息,只显示自己的
|
||||
lqw.eq(KnowledgeInfo::getUid, loginUser.getUserId());
|
||||
}
|
||||
|
||||
Page<KnowledgeInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
@@ -216,8 +238,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
}
|
||||
baseMapper.insert(knowledgeInfo);
|
||||
if (knowledgeInfo != null) {
|
||||
vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()),
|
||||
bo.getVectorModelName());
|
||||
vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()), bo.getEmbeddingModelName());
|
||||
}
|
||||
} else {
|
||||
baseMapper.updateById(knowledgeInfo);
|
||||
@@ -258,6 +279,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".") + 1));
|
||||
String content = "";
|
||||
ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(knowledgeAttach.getDocType());
|
||||
// 文档分段入库
|
||||
List<String> fids = new ArrayList<>();
|
||||
try {
|
||||
content = resourceLoader.getContent(file.getInputStream());
|
||||
@@ -265,6 +287,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(chunkList)) {
|
||||
for (int i = 0; i < chunkList.size(); i++) {
|
||||
// 生成知识片段ID
|
||||
String fid = RandomUtil.randomString(10);
|
||||
fids.add(fid);
|
||||
KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
|
||||
@@ -291,13 +314,16 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
|
||||
// 通过向量模型查询模型信息
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getEmbeddingModelName());
|
||||
|
||||
// 未查到指定模型时,回退为向量分类最高优先级模型
|
||||
if (chatModelVo == null) {
|
||||
chatModelVo = chatModelService.selectModelByCategoryWithHighestPriority(ChatModeType.VECTOR.getCode());
|
||||
}
|
||||
StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
|
||||
storeEmbeddingBo.setKid(kid);
|
||||
storeEmbeddingBo.setDocId(docId);
|
||||
storeEmbeddingBo.setFids(fids);
|
||||
storeEmbeddingBo.setChunkList(chunkList);
|
||||
storeEmbeddingBo.setVectorModelName(knowledgeInfoVo.getVectorModelName());
|
||||
storeEmbeddingBo.setVectorStoreName(knowledgeInfoVo.getVectorModelName());
|
||||
storeEmbeddingBo.setEmbeddingModelName(knowledgeInfoVo.getEmbeddingModelName());
|
||||
storeEmbeddingBo.setApiKey(chatModelVo.getApiKey());
|
||||
storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost());
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ChatClientConfig {
|
||||
|
||||
@Autowired
|
||||
private DynamicMcpToolCallbackProvider dynamicMcpToolCallbackProvider;
|
||||
|
||||
@Bean
|
||||
public ChatClient chatClient(ChatClient.Builder builder) {
|
||||
return builder
|
||||
.defaultTools(java.util.List.of(dynamicMcpToolCallbackProvider.createToolCallbackProvider().getToolCallbacks()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.ruoyi.mcp.service.McpInfoService;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.*;
|
||||
/**
|
||||
* 动态MCP工具回调提供者
|
||||
*
|
||||
* 这个类有大问题 ,没有测试!!!!!!!
|
||||
*/
|
||||
@Component
|
||||
public class DynamicMcpToolCallbackProvider {
|
||||
|
||||
@Autowired
|
||||
private McpInfoService mcpInfoService;
|
||||
|
||||
@Autowired
|
||||
private McpProcessManager mcpProcessManager;
|
||||
|
||||
@Autowired
|
||||
private McpToolInvoker mcpToolInvoker;
|
||||
|
||||
/**
|
||||
* 创建工具回调提供者
|
||||
*/
|
||||
public ToolCallbackProvider createToolCallbackProvider() {
|
||||
List<FunctionCallback> callbacks = new ArrayList<>();
|
||||
List<String> activeServerNames = mcpInfoService.getActiveServerNames();
|
||||
|
||||
for (String serverName : activeServerNames) {
|
||||
FunctionCallback callback = createMcpToolCallback(serverName);
|
||||
callbacks.add(callback);
|
||||
}
|
||||
|
||||
return ToolCallbackProvider.from(callbacks);
|
||||
}
|
||||
|
||||
private FunctionCallback createMcpToolCallback(String serverName) {
|
||||
return new ToolCallback() {
|
||||
@Override
|
||||
public ToolDefinition getToolDefinition() {
|
||||
// 获取工具配置
|
||||
McpServerConfig config = mcpInfoService.getToolConfigByName(serverName);
|
||||
if (config == null) {
|
||||
// 返回一个默认的ToolDefinition
|
||||
return ToolDefinition.builder()
|
||||
.name(serverName)
|
||||
.description("MCP tool for " + serverName)
|
||||
.build();
|
||||
}
|
||||
// 根据config创建ToolDefinition
|
||||
return ToolDefinition.builder()
|
||||
.name(serverName)
|
||||
.description(config.getDescription()) // 假设McpServerConfig有getDescription方法
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String toolInput) {
|
||||
try {
|
||||
// 获取工具配置
|
||||
McpServerConfig config = mcpInfoService.getToolConfigByName(serverName);
|
||||
if (config == null) {
|
||||
return "{\"error\": \"MCP tool not found: " + serverName + "\", \"serverName\": \"" + serverName + "\"}";
|
||||
}
|
||||
|
||||
// 确保 MCP 服务器正在运行
|
||||
ensureMcpServerRunning(serverName, config);
|
||||
|
||||
// 调用 MCP 工具
|
||||
Object result = mcpToolInvoker.invokeTool(serverName, toolInput);
|
||||
|
||||
return "{\"result\": \"" + result.toString() + "\", \"serverName\": \"" + serverName + "\"}";
|
||||
} catch (Exception e) {
|
||||
return "{\"error\": \"MCP tool execution failed: " + e.getMessage() + "\", \"serverName\": \"" + serverName + "\"}";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void ensureMcpServerRunning(String serverName, McpServerConfig config) {
|
||||
if (!mcpProcessManager.isMcpServerRunning(serverName)) {
|
||||
boolean started = mcpProcessManager.startMcpServer(
|
||||
serverName,
|
||||
config
|
||||
);
|
||||
if (!started) {
|
||||
throw new RuntimeException("Failed to start MCP server: " + serverName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
public class McpConfig {
|
||||
@JsonProperty("mcpServers")
|
||||
private Map<String, McpServerConfig> mcpServers;
|
||||
|
||||
// getters and setters
|
||||
public Map<String, McpServerConfig> getMcpServers() {
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
public void setMcpServers(Map<String, McpServerConfig> mcpServers) {
|
||||
this.mcpServers = mcpServers;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.mcp.service.McpInfoService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.io.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class McpProcessManager {
|
||||
|
||||
private final Map<String, Process> runningProcesses = new ConcurrentHashMap<>();
|
||||
private final Map<String, McpServerProcess> mcpServerProcesses = new ConcurrentHashMap<>();
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final Map<String, BufferedWriter> processWriters = new ConcurrentHashMap<>();
|
||||
private final Map<String, BufferedReader> processReaders = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private McpInfoService mcpInfoService;
|
||||
/**
|
||||
* 启动 MCP 服务器进程(支持环境变量)
|
||||
*/
|
||||
public boolean startMcpServer(String serverName, McpServerConfig serverConfig) {
|
||||
try {
|
||||
log.info("启动MCP服务器进程: {}", serverName);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
||||
// 构建命令
|
||||
List<String> commandList = buildCommandListWithFullPaths(serverConfig.getCommand(), serverConfig.getArgs());
|
||||
|
||||
|
||||
processBuilder.command(commandList);
|
||||
|
||||
// 设置工作目录
|
||||
if (serverConfig.getWorkingDirectory() != null) {
|
||||
processBuilder.directory(new File(serverConfig.getWorkingDirectory()));
|
||||
} else {
|
||||
processBuilder.directory(new File(System.getProperty("user.dir")));
|
||||
}
|
||||
|
||||
// 设置环境变量
|
||||
if (serverConfig.getEnv() != null) {
|
||||
processBuilder.environment().putAll(serverConfig.getEnv());
|
||||
}
|
||||
// ===== 关键:在 start 之前打印完整的调试信息 =====
|
||||
System.out.println("=== ProcessBuilder 调试信息 ===");
|
||||
System.out.println("完整命令列表: " + commandList);
|
||||
System.out.println("命令字符串: " + String.join(" ", commandList));
|
||||
System.out.println("工作目录: " + processBuilder.directory());
|
||||
System.out.println("================================");
|
||||
//https://www.modelscope.cn/mcp/servers/@worryzyy/howtocook-mcp
|
||||
|
||||
// 启动进程
|
||||
Process process = processBuilder.start();
|
||||
// 获取输入输出流
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
|
||||
processWriters.put(serverName, writer);
|
||||
processReaders.put(serverName, reader);
|
||||
|
||||
// 存储进程引用
|
||||
McpServerProcess serverProcess = new McpServerProcess(serverName, process, serverConfig);
|
||||
mcpServerProcesses.put(serverName, serverProcess);
|
||||
// 启动日志读取线程
|
||||
executorService.submit(() -> readProcessOutput(serverName, process));
|
||||
// 启动 MCP 通信监听线程
|
||||
executorService.submit(() -> listenMcpMessages(serverName, reader));
|
||||
|
||||
// 更新服务器状态
|
||||
mcpInfoService.enableTool(serverName);
|
||||
boolean isAlive = process.isAlive();
|
||||
|
||||
if (isAlive) {
|
||||
log.info("成功启动MCP服务器: {} 命令: {}", serverName, commandList);
|
||||
} else {
|
||||
System.err.println("✗ MCP server [" + serverName + "] failed to start");
|
||||
// 读取错误输出
|
||||
readErrorOutput(process);
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("启动MCP服务器进程失败: " + serverName, e);
|
||||
|
||||
// 更新服务器状态为禁用
|
||||
//mcpInfoService.disableTool(serverName);
|
||||
|
||||
throw new RuntimeException("Failed to start MCP server process: " + e.getMessage(), e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 发送 MCP 消息
|
||||
*/
|
||||
public boolean sendMcpMessage(String serverName, Map<String, Object> message) {
|
||||
try {
|
||||
BufferedWriter writer = processWriters.get(serverName);
|
||||
if (writer == null) {
|
||||
System.err.println("未找到服务器 [" + serverName + "] 的输出流");
|
||||
return false;
|
||||
}
|
||||
|
||||
String jsonMessage = objectMapper.writeValueAsString(message);
|
||||
System.out.println("发送消息到 [" + serverName + "]: " + jsonMessage);
|
||||
|
||||
writer.write(jsonMessage);
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
System.err.println("发送消息到 [" + serverName + "] 失败: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听 MCP 消息
|
||||
*/
|
||||
private void listenMcpMessages(String serverName, BufferedReader reader) {
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
try {
|
||||
// 解析收到的 JSON 消息
|
||||
Map<String, Object> message = objectMapper.readValue(line, Map.class);
|
||||
System.out.println("收到来自 [" + serverName + "] 的消息: " + message);
|
||||
|
||||
// 处理不同类型的 MCP 消息
|
||||
handleMessage(serverName, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("解析消息失败: " + line + ", 错误: " + e.getMessage());
|
||||
// 如果不是 JSON,当作普通日志输出
|
||||
System.out.println("[" + serverName + "] 日志: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (isMcpServerRunning(serverName)) {
|
||||
System.err.println("监听 [" + serverName + "] 消息时出错: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理 MCP 消息(更新版本)
|
||||
*/
|
||||
private void handleMessage(String serverName, Map<String, Object> message) {
|
||||
String type = (String) message.get("type");
|
||||
if (type == null) return;
|
||||
|
||||
switch (type) {
|
||||
case "ready":
|
||||
System.out.println("MCP 服务器 [" + serverName + "] 准备就绪");
|
||||
break;
|
||||
case "response":
|
||||
System.out.println("MCP 服务器 [" + serverName + "] 响应: " + message.get("data"));
|
||||
break;
|
||||
case "error":
|
||||
System.err.println("MCP 服务器 [" + serverName + "] 错误: " + message.get("message"));
|
||||
break;
|
||||
default:
|
||||
System.out.println("MCP 服务器 [" + serverName + "] 未知消息类型: " + type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建命令列表
|
||||
*/
|
||||
private List<String> buildCommandListWithFullPaths(String command, List<String> args) {
|
||||
List<String> commandList = new ArrayList<>();
|
||||
|
||||
if (isWindows() && "npx".equalsIgnoreCase(command)) {
|
||||
// 在 Windows 上使用 cmd.exe 包装以确保兼容性
|
||||
commandList.add("cmd.exe");
|
||||
commandList.add("/c");
|
||||
commandList.add("npx");
|
||||
commandList.addAll(args);
|
||||
} else {
|
||||
commandList.add(command);
|
||||
commandList.addAll(args);
|
||||
}
|
||||
|
||||
return commandList;
|
||||
}
|
||||
/**
|
||||
* 检查是否为 Windows 系统
|
||||
*/
|
||||
private boolean isWindows() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取错误输出
|
||||
*/
|
||||
private void readErrorOutput(Process process) {
|
||||
try {
|
||||
InputStream errorStream = process.getErrorStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.err.println("ERROR: " + line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to read error output: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 停止 MCP 服务器进程
|
||||
*/
|
||||
public boolean stopMcpServer(String serverName) {
|
||||
Process process = runningProcesses.remove(serverName);
|
||||
BufferedWriter writer = processWriters.remove(serverName);
|
||||
BufferedReader reader = processReaders.remove(serverName);
|
||||
try {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("关闭流时出错: " + e.getMessage());
|
||||
}
|
||||
// 更新服务器状态为禁用
|
||||
mcpInfoService.disableTool(serverName);
|
||||
|
||||
if (process != null && process.isAlive()) {
|
||||
process.destroy();
|
||||
try {
|
||||
if (!process.waitFor(5, TimeUnit.SECONDS)) {
|
||||
process.destroyForcibly();
|
||||
process.waitFor(1, TimeUnit.SECONDS);
|
||||
}
|
||||
System.out.println("MCP server [" + serverName + "] stopped");
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
process.destroyForcibly();
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启 MCP 服务器进程
|
||||
*/
|
||||
public boolean restartMcpServer(String serverName, String command, List<String> args, Map<String, String> env) {
|
||||
stopMcpServer(serverName);
|
||||
McpServerConfig mcpServerConfig = new McpServerConfig();
|
||||
mcpServerConfig.setCommand(command);
|
||||
mcpServerConfig.setArgs(args);
|
||||
mcpServerConfig.setEnv(env);
|
||||
return startMcpServer(serverName, mcpServerConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 MCP 服务器是否运行
|
||||
*/
|
||||
public boolean isMcpServerRunning(String serverName) {
|
||||
Process process = runningProcesses.get(serverName);
|
||||
return process != null && process.isAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有运行中的 MCP 服务器
|
||||
*/
|
||||
public Set<String> getRunningMcpServers() {
|
||||
Set<String> running = new HashSet<>();
|
||||
for (Map.Entry<String, Process> entry : runningProcesses.entrySet()) {
|
||||
if (entry.getValue().isAlive()) {
|
||||
running.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程信息
|
||||
*/
|
||||
public McpServerProcess getProcessInfo(String serverName) {
|
||||
return mcpServerProcesses.get(serverName);
|
||||
}
|
||||
|
||||
private void readProcessOutput(String serverName, Process process) {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null && process.isAlive()) {
|
||||
System.out.println("[" + serverName + "] " + line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error reading output from " + serverName + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getProcessId(Process process) {
|
||||
try {
|
||||
// Java 9+ 可以直接获取 PID
|
||||
return String.valueOf(process.pid());
|
||||
} catch (Exception e) {
|
||||
// Java 8 兼容处理
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP服务器进程信息
|
||||
*/
|
||||
public static class McpServerProcess {
|
||||
private final String name;
|
||||
private final Process process;
|
||||
private final McpServerConfig config;
|
||||
private final LocalDateTime startTime;
|
||||
|
||||
public McpServerProcess(String name, Process process, McpServerConfig config) {
|
||||
this.name = name;
|
||||
this.process = process;
|
||||
this.config = config;
|
||||
this.startTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getName() { return name; }
|
||||
public Process getProcess() { return process; }
|
||||
public McpServerConfig getConfig() { return config; }
|
||||
public LocalDateTime getStartTime() { return startTime; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.info.ProcessInfo;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.Disposable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
|
||||
@Component
|
||||
public class McpProcessSSEManager {
|
||||
|
||||
private final Map<String, Process> runningProcesses = new ConcurrentHashMap<>();
|
||||
private final Map<String, ProcessInfo> processInfos = new ConcurrentHashMap<>();
|
||||
private final Map<String, WebClient> sseClients = new ConcurrentHashMap<>();
|
||||
private final Map<String, Disposable> sseSubscriptions = new ConcurrentHashMap<>();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
private McpSSEToolInvoker mcpToolInvoker;
|
||||
|
||||
/**
|
||||
* 启动 MCP 服务器进程(SSE 模式)
|
||||
*/
|
||||
public boolean startMcpServer(String serverName, String command, List<String> args, Map<String, String> env) {
|
||||
try {
|
||||
System.out.println("准备启动 MCP 服务器 (SSE 模式): " + serverName);
|
||||
|
||||
// 如果已经运行,先停止
|
||||
if (isMcpServerRunning(serverName)) {
|
||||
stopMcpServer(serverName);
|
||||
}
|
||||
|
||||
// 构建命令
|
||||
List<String> commandList = buildCommandList(command, args);
|
||||
|
||||
// 创建 ProcessBuilder
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(commandList);
|
||||
processBuilder.redirectErrorStream(true);
|
||||
|
||||
// 设置工作目录
|
||||
String workingDir = System.getProperty("user.dir");
|
||||
processBuilder.directory(new File(workingDir));
|
||||
|
||||
// 打印调试信息
|
||||
System.out.println("=== ProcessBuilder 调试信息 ===");
|
||||
System.out.println("完整命令列表: " + commandList);
|
||||
System.out.println("================================");
|
||||
|
||||
// 执行命令
|
||||
Process process = processBuilder.start();
|
||||
runningProcesses.put(serverName, process);
|
||||
|
||||
ProcessInfo processInfo = new ProcessInfo();
|
||||
processInfo.setStartTime(System.currentTimeMillis());
|
||||
processInfo.setPid(getProcessId(process));
|
||||
processInfos.put(serverName, processInfo);
|
||||
|
||||
// 启动日志读取线程
|
||||
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
executorService.submit(() -> readProcessOutput(serverName, process));
|
||||
|
||||
// 等待进程启动
|
||||
Thread.sleep(3000);
|
||||
boolean isAlive = process.isAlive();
|
||||
|
||||
if (isAlive) {
|
||||
System.out.println("✓ MCP 服务器 [" + serverName + "] 启动成功");
|
||||
// 初始化 SSE 连接
|
||||
initializeSseConnection(serverName);
|
||||
} else {
|
||||
System.err.println("✗ MCP 服务器 [" + serverName + "] 启动失败");
|
||||
readErrorOutput(process);
|
||||
}
|
||||
|
||||
return isAlive;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 启动 MCP 服务器 [" + serverName + "] 失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private String getProcessId(Process process) {
|
||||
try {
|
||||
return String.valueOf(process.pid());
|
||||
} catch (Exception e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
private void readProcessOutput(String serverName, Process process) {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null && process.isAlive()) {
|
||||
System.out.println("[" + serverName + "] " + line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error reading output from " + serverName + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 读取错误输出
|
||||
*/
|
||||
private void readErrorOutput(Process process) {
|
||||
try {
|
||||
InputStream errorStream = process.getErrorStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.err.println("ERROR: " + line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to read error output: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化 SSE 连接
|
||||
*/
|
||||
private void initializeSseConnection(String serverName) {
|
||||
try {
|
||||
// 创建 WebClient 用于 SSE 连接
|
||||
WebClient webClient = WebClient.builder()
|
||||
.baseUrl("http://localhost:3000") // 假设默认端口 3000
|
||||
.build();
|
||||
|
||||
sseClients.put(serverName, webClient);
|
||||
|
||||
// 建立 SSE 连接
|
||||
String sseUrl = "/sse/" + serverName; // SSE 端点
|
||||
|
||||
Disposable subscription = webClient.get()
|
||||
.uri(sseUrl)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.subscribe(
|
||||
event -> handleSseEvent(serverName, event),
|
||||
error -> System.err.println("SSE 连接错误 [" + serverName + "]: " + error.getMessage()),
|
||||
() -> System.out.println("SSE 连接完成 [" + serverName + "]")
|
||||
);
|
||||
|
||||
sseSubscriptions.put(serverName, subscription);
|
||||
System.out.println("✓ SSE 连接建立成功 [" + serverName + "]");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 建立 SSE 连接失败 [" + serverName + "]: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SSE 事件
|
||||
*/
|
||||
private void handleSseEvent(String serverName, String event) {
|
||||
try {
|
||||
System.out.println("收到来自 [" + serverName + "] 的 SSE 事件: " + event);
|
||||
|
||||
// 解析 SSE 事件
|
||||
if (event.startsWith("data: ")) {
|
||||
String jsonData = event.substring(6); // 移除 "data: " 前缀
|
||||
Map<String, Object> message = objectMapper.readValue(jsonData, Map.class);
|
||||
|
||||
// 处理不同类型的事件
|
||||
String type = (String) message.get("type");
|
||||
if ("tool_response".equals(type)) {
|
||||
mcpToolInvoker.handleSseResponse(serverName, message);
|
||||
} else if ("tool_error".equals(type)) {
|
||||
mcpToolInvoker.handleSseError(serverName, message);
|
||||
} else if ("progress".equals(type)) {
|
||||
handleProgressEvent(serverName, message);
|
||||
} else {
|
||||
System.out.println("[" + serverName + "] 未知事件类型: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("处理 SSE 事件失败 [" + serverName + "]: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理进度事件
|
||||
*/
|
||||
private void handleProgressEvent(String serverName, Map<String, Object> message) {
|
||||
Object progress = message.get("progress");
|
||||
Object messageText = message.get("message");
|
||||
System.out.println("[" + serverName + "] 进度: " + progress + " - " + messageText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构建命令列表
|
||||
*/
|
||||
private List<String> buildCommandList(String command, List<String> args) {
|
||||
List<String> commandList = new ArrayList<>();
|
||||
|
||||
if (isWindows() && "npx".equalsIgnoreCase(command)) {
|
||||
commandList.add("cmd.exe");
|
||||
commandList.add("/c");
|
||||
commandList.add("npx");
|
||||
commandList.addAll(args);
|
||||
} else {
|
||||
commandList.add(command);
|
||||
commandList.addAll(args);
|
||||
}
|
||||
|
||||
return commandList;
|
||||
}
|
||||
/**
|
||||
* 检查是否为 Windows 系统
|
||||
*/
|
||||
private boolean isWindows() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows");
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止 MCP 服务器进程
|
||||
*/
|
||||
public boolean stopMcpServer(String serverName) {
|
||||
// 停止 SSE 连接
|
||||
Disposable subscription = sseSubscriptions.remove(serverName);
|
||||
if (subscription != null && !subscription.isDisposed()) {
|
||||
subscription.dispose();
|
||||
}
|
||||
|
||||
sseClients.remove(serverName);
|
||||
|
||||
// 停止进程
|
||||
Process process = runningProcesses.remove(serverName);
|
||||
ProcessInfo processInfo = processInfos.remove(serverName);
|
||||
|
||||
if (process != null && process.isAlive()) {
|
||||
process.destroy();
|
||||
try {
|
||||
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
||||
process.destroyForcibly();
|
||||
process.waitFor(2, TimeUnit.SECONDS);
|
||||
}
|
||||
System.out.println("MCP 服务器 [" + serverName + "] 已停止");
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
process.destroyForcibly();
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 检查 MCP 服务器是否运行
|
||||
*/
|
||||
public boolean isMcpServerRunning(String serverName) {
|
||||
Process process = runningProcesses.get(serverName);
|
||||
return process != null && process.isAlive();
|
||||
}
|
||||
/**
|
||||
* 进程信息类
|
||||
*/
|
||||
public static class ProcessInfo {
|
||||
private String pid;
|
||||
private long startTime;
|
||||
|
||||
public String getPid() { return pid; }
|
||||
public void setPid(String pid) { this.pid = pid; }
|
||||
|
||||
public long getStartTime() { return startTime; }
|
||||
public void setStartTime(long startTime) { this.startTime = startTime; }
|
||||
|
||||
public long getUptime() {
|
||||
return System.currentTimeMillis() - startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Component
|
||||
public class McpSSEToolInvoker {
|
||||
|
||||
|
||||
private final Map<String, CompletableFuture<Object>> pendingRequests = new ConcurrentHashMap<>();
|
||||
private final AtomicLong requestIdCounter = new AtomicLong(0);
|
||||
|
||||
/**
|
||||
* 调用 MCP 工具(SSE 模式)
|
||||
*/
|
||||
public Object invokeTool(String serverName, Object parameters) {
|
||||
try {
|
||||
// 生成请求ID
|
||||
String requestId = "req_" + requestIdCounter.incrementAndGet();
|
||||
|
||||
// 创建 CompletableFuture 等待响应
|
||||
CompletableFuture<Object> future = new CompletableFuture<>();
|
||||
pendingRequests.put(requestId, future);
|
||||
|
||||
// 构造 MCP 调用请求
|
||||
Map<String, Object> callRequest = new HashMap<>();
|
||||
callRequest.put("requestId", requestId);
|
||||
callRequest.put("serverName", serverName);
|
||||
callRequest.put("parameters", convertToMap(parameters));
|
||||
callRequest.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
System.out.println("通过 SSE 调用 MCP 工具 [" + serverName + "] 参数: " + parameters);
|
||||
|
||||
// 发送请求到 MCP 服务器(通过 HTTP POST)
|
||||
sendSseToolCall(serverName, callRequest);
|
||||
|
||||
// 等待响应(超时 30 秒)
|
||||
Object result = future.get(30, TimeUnit.SECONDS);
|
||||
|
||||
System.out.println("MCP 工具 [" + serverName + "] 调用成功,响应: " + result);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用 MCP 服务器 [" + serverName + "] 失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
return Map.of(
|
||||
"serverName", serverName,
|
||||
"status", "failed",
|
||||
"message", "Tool invocation failed: " + e.getMessage(),
|
||||
"parameters", parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 SSE 工具调用请求
|
||||
*/
|
||||
private void sendSseToolCall(String serverName, Map<String, Object> callRequest) {
|
||||
try {
|
||||
// 通过 HTTP POST 发送工具调用请求
|
||||
WebClient webClient = WebClient.builder()
|
||||
.baseUrl("http://localhost:3000")
|
||||
.build();
|
||||
|
||||
String toolCallUrl = "/tool/" + serverName;
|
||||
|
||||
webClient.post()
|
||||
.uri(toolCallUrl)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(callRequest)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.subscribe(
|
||||
response -> System.out.println("工具调用请求发送成功: " + response),
|
||||
error -> System.err.println("工具调用请求发送失败: " + error.getMessage())
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("发送 SSE 工具调用请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SSE 响应
|
||||
*/
|
||||
public void handleSseResponse(String serverName, Map<String, Object> message) {
|
||||
String requestId = (String) message.get("requestId");
|
||||
if (requestId != null) {
|
||||
CompletableFuture<Object> future = pendingRequests.remove(requestId);
|
||||
if (future != null) {
|
||||
Object data = message.get("data");
|
||||
future.complete(data != null ? data : message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SSE 错误
|
||||
*/
|
||||
public void handleSseError(String serverName, Map<String, Object> message) {
|
||||
String requestId = (String) message.get("requestId");
|
||||
if (requestId != null) {
|
||||
CompletableFuture<Object> future = pendingRequests.remove(requestId);
|
||||
if (future != null) {
|
||||
String errorMessage = (String) message.get("message");
|
||||
future.completeExceptionally(new RuntimeException(errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式调用 MCP 工具(支持实时进度)
|
||||
*/
|
||||
public Flux<Object> invokeToolStream(String serverName, Object parameters) {
|
||||
return Flux.create(emitter -> {
|
||||
try {
|
||||
// 生成请求ID
|
||||
String requestId = "req_" + requestIdCounter.incrementAndGet();
|
||||
|
||||
// 构造 MCP 调用请求
|
||||
Map<String, Object> callRequest = new HashMap<>();
|
||||
callRequest.put("requestId", requestId);
|
||||
callRequest.put("serverName", serverName);
|
||||
callRequest.put("parameters", convertToMap(parameters));
|
||||
callRequest.put("stream", true); // 标记为流式调用
|
||||
callRequest.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 创建流式处理器
|
||||
StreamHandler streamHandler = new StreamHandler(emitter);
|
||||
pendingRequests.put(requestId + "_stream", null); // 占位符
|
||||
|
||||
// 发送流式调用请求
|
||||
sendSseToolCall(serverName, callRequest);
|
||||
|
||||
// 注册流式处理器
|
||||
registerStreamHandler(requestId, streamHandler);
|
||||
|
||||
emitter.onDispose(() -> {
|
||||
// 清理资源
|
||||
pendingRequests.remove(requestId + "_stream");
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
emitter.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式处理器
|
||||
*/
|
||||
private static class StreamHandler {
|
||||
private final FluxSink<Object> emitter;
|
||||
|
||||
public StreamHandler(FluxSink<Object> emitter) {
|
||||
this.emitter = emitter;
|
||||
}
|
||||
|
||||
public void onNext(Object data) {
|
||||
emitter.next(data);
|
||||
}
|
||||
|
||||
public void onComplete() {
|
||||
emitter.complete();
|
||||
}
|
||||
|
||||
public void onError(Throwable error) {
|
||||
emitter.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> convertToMap(Object parameters) {
|
||||
if (parameters instanceof Map) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<?, ?> paramMap = (Map<?, ?>) parameters;
|
||||
for (Map.Entry<?, ?> entry : paramMap.entrySet()) {
|
||||
if (entry.getKey() instanceof String) {
|
||||
result.put((String) entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
private void registerStreamHandler(String requestId, StreamHandler streamHandler) {
|
||||
// 实现流式处理器注册逻辑
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class McpServerConfig {
|
||||
private String command;
|
||||
private List<String> args;
|
||||
private Map<String, String> env;
|
||||
private String Description;
|
||||
private String workingDirectory;
|
||||
// getters and setters
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setArgs(List<String> args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Map<String, String> getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
public void setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return Description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
Description = description;
|
||||
}
|
||||
public String getWorkingDirectory() {
|
||||
return workingDirectory;
|
||||
}
|
||||
|
||||
public void setWorkingDirectory(String workingDirectory) {
|
||||
this.workingDirectory = workingDirectory;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "McpServerConfig{" +
|
||||
"command='" + command + '\'' +
|
||||
", args=" + args +
|
||||
", env=" + env +
|
||||
", Description='" + Description + '\'' +
|
||||
", workingDirectory='" + workingDirectory + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.ruoyi.mcp.service.McpToolManagementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class McpStartupConfig {
|
||||
|
||||
@Autowired
|
||||
private McpToolManagementService mcpToolManagementService;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationReady() {
|
||||
// 应用启动时自动初始化 MCP 工具
|
||||
try {
|
||||
System.out.println("Starting MCP tools initialization...");
|
||||
mcpToolManagementService.initializeMcpTools();
|
||||
System.out.println("MCP tools initialization completed successfully");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to initialize MCP tools: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Component
|
||||
public class McpToolInvoker {
|
||||
|
||||
private final Map<String, CompletableFuture<Object>> pendingRequests = new ConcurrentHashMap<>();
|
||||
private final AtomicLong requestIdCounter = new AtomicLong(0);
|
||||
@Autowired
|
||||
private McpProcessManager mcpProcessManager;
|
||||
|
||||
|
||||
/**
|
||||
* 调用 MCP 工具(Studio 模式)
|
||||
*/
|
||||
public Object invokeTool(String serverName, Object parameters) {
|
||||
try {
|
||||
// 生成请求ID
|
||||
String requestId = "req_" + requestIdCounter.incrementAndGet();
|
||||
|
||||
// 创建 CompletableFuture 等待响应
|
||||
CompletableFuture<Object> future = new CompletableFuture<>();
|
||||
pendingRequests.put(requestId, future);
|
||||
|
||||
// 构造 MCP 调用消息
|
||||
Map<String, Object> callMessage = new HashMap<>();
|
||||
callMessage.put("type", "tool_call");
|
||||
callMessage.put("requestId", requestId);
|
||||
callMessage.put("serverName", serverName);
|
||||
callMessage.put("parameters", convertToMap(parameters));
|
||||
callMessage.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
System.out.println("调用 MCP 工具 [" + serverName + "] 参数: " + parameters);
|
||||
|
||||
// 发送消息到 MCP 服务器
|
||||
boolean sent = mcpProcessManager.sendMcpMessage(serverName, callMessage);
|
||||
if (!sent) {
|
||||
pendingRequests.remove(requestId);
|
||||
throw new RuntimeException("无法发送消息到 MCP 服务器: " + serverName);
|
||||
}
|
||||
|
||||
// 等待响应(超时 30 秒)
|
||||
Object result = future.get(30, TimeUnit.SECONDS);
|
||||
|
||||
System.out.println("MCP 工具 [" + serverName + "] 调用成功,响应: " + result);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("调用 MCP 服务器 [" + serverName + "] 失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
return Map.of(
|
||||
"serverName", serverName,
|
||||
"status", "failed",
|
||||
"message", "Tool invocation failed: " + e.getMessage(),
|
||||
"parameters", parameters
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理 MCP 服务器的响应消息
|
||||
*/
|
||||
public void handleMcpResponse(String serverName, Map<String, Object> message) {
|
||||
String type = (String) message.get("type");
|
||||
if ("tool_response".equals(type)) {
|
||||
String requestId = (String) message.get("requestId");
|
||||
if (requestId != null) {
|
||||
CompletableFuture<Object> future = pendingRequests.remove(requestId);
|
||||
if (future != null) {
|
||||
Object data = message.get("data");
|
||||
future.complete(data != null ? data : message);
|
||||
}
|
||||
}
|
||||
} else if ("tool_error".equals(type)) {
|
||||
String requestId = (String) message.get("requestId");
|
||||
if (requestId != null) {
|
||||
CompletableFuture<Object> future = pendingRequests.remove(requestId);
|
||||
if (future != null) {
|
||||
String errorMessage = (String) message.get("message");
|
||||
future.completeExceptionally(new RuntimeException(errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> convertToMap(Object parameters) {
|
||||
if (parameters instanceof Map) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<?, ?> paramMap = (Map<?, ?>) parameters;
|
||||
for (Map.Entry<?, ?> entry : paramMap.entrySet()) {
|
||||
if (entry.getKey() instanceof String) {
|
||||
result.put((String) entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.ruoyi.mcp.controller;
|
||||
|
||||
import org.ruoyi.mcp.config.McpSSEToolInvoker;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sse")
|
||||
public class MCPSseController {
|
||||
|
||||
@Autowired
|
||||
private McpSSEToolInvoker mcpToolInvoker;
|
||||
|
||||
/**
|
||||
* SSE 流式响应端点
|
||||
*/
|
||||
@GetMapping(value = "/{serverName}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter streamMcpResponse(@PathVariable String serverName) {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
|
||||
try {
|
||||
// 发送连接建立消息
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("connected")
|
||||
.data(Map.of("serverName", serverName, "status", "connected")));
|
||||
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 MCP 工具(流式)
|
||||
*/
|
||||
@PostMapping("/tool/{serverName}")
|
||||
public ResponseEntity<?> callMcpTool(
|
||||
@PathVariable String serverName,
|
||||
@RequestBody Map<String, Object> request) {
|
||||
|
||||
try {
|
||||
boolean isStream = (Boolean) request.getOrDefault("stream", false);
|
||||
Object parameters = request.get("parameters");
|
||||
|
||||
if (isStream) {
|
||||
// 流式调用
|
||||
return ResponseEntity.ok(Map.of("status", "streaming_started"));
|
||||
} else {
|
||||
// 普通调用
|
||||
Object result = mcpToolInvoker.invokeTool(serverName, parameters);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package org.ruoyi.mcp.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.ruoyi.domain.McpInfo;
|
||||
import org.ruoyi.domain.bo.McpInfoBo;
|
||||
import org.ruoyi.domain.vo.McpInfoVo;
|
||||
import org.ruoyi.mcp.config.McpConfig;
|
||||
import org.ruoyi.mcp.config.McpServerConfig;
|
||||
import org.ruoyi.mcp.domain.McpInfoRequest;
|
||||
import org.ruoyi.mcp.service.McpInfoService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* MCP
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Sat Aug 09 16:50:58 CST 2025
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/operator/mcpInfo")
|
||||
public class McpInfoController extends BaseController {
|
||||
|
||||
private final McpInfoService mcpInfoService;
|
||||
|
||||
/**
|
||||
* 查询MCP列表
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<McpInfoVo> list(McpInfoBo bo, PageQuery pageQuery) {
|
||||
return mcpInfoService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出MCP列表
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:export")
|
||||
@Log(title = "MCP", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(McpInfoBo bo, HttpServletResponse response) {
|
||||
List<McpInfoVo> list = mcpInfoService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "MCP", McpInfoVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MCP详细信息
|
||||
*
|
||||
* @param mcpId 主键
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:query")
|
||||
@GetMapping("/{mcpId}")
|
||||
public R<McpInfoVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Integer mcpId) {
|
||||
return R.ok(mcpInfoService.queryById(mcpId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增MCP
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:add")
|
||||
@Log(title = "MCP", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody McpInfoBo bo) {
|
||||
return toAjax(mcpInfoService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改MCP
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:edit")
|
||||
@Log(title = "MCP", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody McpInfoBo bo) {
|
||||
return toAjax(mcpInfoService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除MCP
|
||||
*
|
||||
* @param mcpIds 主键串
|
||||
*/
|
||||
@SaCheckPermission("operator:mcpInfo:remove")
|
||||
@Log(title = "MCP", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{mcpIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Integer[] mcpIds) {
|
||||
return toAjax(mcpInfoService.deleteWithValidByIds(List.of(mcpIds), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加或更新 MCP 工具
|
||||
*/
|
||||
@PostMapping("/tools")
|
||||
public R<McpInfo> saveToolConfig(@RequestBody McpInfoRequest request) {
|
||||
return R.ok(mcpInfoService.saveToolConfig(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有活跃服务器名称
|
||||
*/
|
||||
@GetMapping("/tools/names")
|
||||
public R<List<String>> getActiveServerNames() {
|
||||
return R.ok(mcpInfoService.getActiveServerNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取工具配置
|
||||
*/
|
||||
@GetMapping("/tools/{serverName}")
|
||||
public R<McpServerConfig> getToolConfig(@PathVariable String serverName) {
|
||||
return R.ok(mcpInfoService.getToolConfigByName(serverName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用工具
|
||||
*/
|
||||
@PostMapping("/tools/{serverName}/enable")
|
||||
public Map<String, Object> enableTool(@PathVariable String serverName) {
|
||||
boolean success = mcpInfoService.enableTool(serverName);
|
||||
return Map.of("success", success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用工具
|
||||
*/
|
||||
@PostMapping("/tools/{serverName}/disable")
|
||||
public Map<String, Object> disableTool(@PathVariable String serverName) {
|
||||
boolean success = mcpInfoService.disableTool(serverName);
|
||||
return Map.of("success", success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除工具
|
||||
*/
|
||||
@DeleteMapping("/tools/{serverName}")
|
||||
public Map<String, Object> deleteTool(@PathVariable String serverName) {
|
||||
boolean success = mcpInfoService.deleteToolConfig(serverName);
|
||||
return Map.of("success", success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.ruoyi.mcp.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class McpInfoRequest {
|
||||
private String serverName;
|
||||
private String command;
|
||||
private List<String> args;
|
||||
private Map<String, String> env;
|
||||
private String description;
|
||||
|
||||
// getters and setters
|
||||
public String getServerName() { return serverName; }
|
||||
public void setServerName(String serverName) { this.serverName = serverName; }
|
||||
|
||||
public String getCommand() { return command; }
|
||||
public void setCommand(String command) { this.command = command; }
|
||||
|
||||
public List<String> getArgs() { return args; }
|
||||
public void setArgs(List<String> args) { this.args = args; }
|
||||
|
||||
public Map<String, String> getEnv() { return env; }
|
||||
public void setEnv(Map<String, String> env) { this.env = env; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.ruoyi.mcp.service;
|
||||
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.domain.McpInfo;
|
||||
import org.ruoyi.domain.bo.McpInfoBo;
|
||||
import org.ruoyi.domain.vo.McpInfoVo;
|
||||
import org.ruoyi.mcp.config.McpConfig;
|
||||
import org.ruoyi.mcp.config.McpServerConfig;
|
||||
import org.ruoyi.mcp.domain.McpInfoRequest;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MCPService接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Sat Aug 09 16:50:58 CST 2025
|
||||
*/
|
||||
public interface McpInfoService {
|
||||
|
||||
/**
|
||||
* 查询MCP
|
||||
*/
|
||||
McpInfoVo queryById(Integer mcpId);
|
||||
|
||||
/**
|
||||
* 查询MCP列表
|
||||
*/
|
||||
TableDataInfo<McpInfoVo> queryPageList(McpInfoBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询MCP列表
|
||||
*/
|
||||
List<McpInfoVo> queryList(McpInfoBo bo);
|
||||
|
||||
/**
|
||||
* 新增MCP
|
||||
*/
|
||||
Boolean insertByBo(McpInfoBo bo);
|
||||
|
||||
/**
|
||||
* 修改MCP
|
||||
*/
|
||||
Boolean updateByBo(McpInfoBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除MCP信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid);
|
||||
|
||||
McpServerConfig getToolConfigByName(String serverName);
|
||||
|
||||
McpConfig getAllActiveMcpConfig();
|
||||
|
||||
List<String> getActiveServerNames();
|
||||
|
||||
McpInfo saveToolConfig(McpInfoRequest request);
|
||||
|
||||
boolean deleteToolConfig(String serverName);
|
||||
|
||||
boolean updateToolStatus(String serverName, Boolean status);
|
||||
|
||||
boolean enableTool(String serverName);
|
||||
|
||||
boolean disableTool(String serverName);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.ruoyi.mcp.service;
|
||||
|
||||
import org.ruoyi.domain.McpInfo;
|
||||
import org.ruoyi.mcp.config.McpConfig;
|
||||
import org.ruoyi.mcp.config.McpProcessManager;
|
||||
import org.ruoyi.mcp.config.McpServerConfig;
|
||||
import org.ruoyi.mcp.domain.McpInfoRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class McpToolManagementService {
|
||||
|
||||
@Autowired
|
||||
private McpInfoService mcpInfoService;
|
||||
|
||||
@Autowired
|
||||
private McpProcessManager mcpProcessManager;
|
||||
|
||||
/**
|
||||
* 初始化所有 MCP 工具(应用启动时调用)
|
||||
*/
|
||||
public void initializeMcpTools() {
|
||||
System.out.println("Initializing MCP tools...");
|
||||
|
||||
McpConfig config = mcpInfoService.getAllActiveMcpConfig();
|
||||
if (config.getMcpServers() != null) {
|
||||
int successCount = 0;
|
||||
int totalCount = config.getMcpServers().size();
|
||||
|
||||
for (Map.Entry<String, McpServerConfig> entry : config.getMcpServers().entrySet()) {
|
||||
String serverName = entry.getKey();
|
||||
McpServerConfig serverConfig = entry.getValue();
|
||||
|
||||
System.out.println("Starting MCP server: " + serverName);
|
||||
System.out.println("Starting MCP serverConfig: " + serverConfig);
|
||||
// 启动 MCP 服务器进程
|
||||
boolean started = mcpProcessManager.startMcpServer(serverName,serverConfig);
|
||||
|
||||
if (started) {
|
||||
successCount++;
|
||||
System.out.println("✓ MCP server [" + serverName + "] started successfully");
|
||||
} else {
|
||||
System.err.println("✗ Failed to start MCP server [" + serverName + "]");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("MCP tools initialization completed. " +
|
||||
successCount + "/" + totalCount + " tools started.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的 MCP 工具并启动
|
||||
*/
|
||||
public boolean addMcpTool(McpInfoRequest request) {
|
||||
try {
|
||||
McpInfo tool = mcpInfoService.saveToolConfig(request);
|
||||
|
||||
// 启动新添加的工具
|
||||
McpServerConfig config = new McpServerConfig();
|
||||
config.setCommand(request.getCommand());
|
||||
config.setArgs(request.getArgs());
|
||||
config.setEnv(request.getEnv());
|
||||
|
||||
boolean started = mcpProcessManager.startMcpServer(
|
||||
request.getServerName(),
|
||||
config
|
||||
);
|
||||
|
||||
return started;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to add MCP tool: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MCP 工具状态
|
||||
*/
|
||||
public Map<String, Object> getMcpToolStatus() {
|
||||
List<String> activeTools = mcpInfoService.getActiveServerNames();
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
|
||||
for (String serverName : activeTools) {
|
||||
boolean isRunning = mcpProcessManager.isMcpServerRunning(serverName);
|
||||
McpProcessManager.McpServerProcess processInfo = mcpProcessManager.getProcessInfo(serverName);
|
||||
|
||||
Map<String, Object> toolStatus = new HashMap<>();
|
||||
toolStatus.put("running", isRunning);
|
||||
toolStatus.put("processInfo", processInfo);
|
||||
|
||||
status.put(serverName, toolStatus);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启指定的 MCP 工具
|
||||
*/
|
||||
public boolean restartMcpTool(String serverName) {
|
||||
McpServerConfig config = mcpInfoService.getToolConfigByName(serverName);
|
||||
if (config == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mcpProcessManager.restartMcpServer(
|
||||
serverName,
|
||||
config.getCommand(),
|
||||
config.getArgs(),
|
||||
config.getEnv()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定的 MCP 工具
|
||||
*/
|
||||
public boolean stopMcpTool(String serverName) {
|
||||
return mcpProcessManager.stopMcpServer(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有运行中的工具
|
||||
*/
|
||||
public Set<String> getRunningTools() {
|
||||
return mcpProcessManager.getRunningMcpServers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package org.ruoyi.mcp.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.domain.McpInfo;
|
||||
import org.ruoyi.domain.bo.McpInfoBo;
|
||||
import org.ruoyi.domain.vo.McpInfoVo;
|
||||
import org.ruoyi.mapper.McpInfoMapper;
|
||||
import org.ruoyi.mcp.config.McpConfig;
|
||||
import org.ruoyi.mcp.config.McpServerConfig;
|
||||
import org.ruoyi.mcp.domain.McpInfoRequest;
|
||||
import org.ruoyi.mcp.service.McpInfoService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* MCPService业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date Sat Aug 09 16:50:58 CST 2025
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class McpInfoServiceImpl implements McpInfoService {
|
||||
|
||||
private final McpInfoMapper baseMapper;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
/**
|
||||
* 查询MCP
|
||||
*/
|
||||
@Override
|
||||
public McpInfoVo queryById(Integer mcpId) {
|
||||
return baseMapper.selectVoById(mcpId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询MCP列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<McpInfoVo> queryPageList(McpInfoBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<McpInfo> lqw = buildQueryWrapper(bo);
|
||||
Page<McpInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询MCP列表
|
||||
*/
|
||||
@Override
|
||||
public List<McpInfoVo> queryList(McpInfoBo bo) {
|
||||
LambdaQueryWrapper<McpInfo> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<McpInfo> buildQueryWrapper(McpInfoBo bo) {
|
||||
LambdaQueryWrapper<McpInfo> lqw = Wrappers.lambdaQuery();
|
||||
lqw.like(StringUtils.isNotBlank(bo.getServerName()), McpInfo::getServerName, bo.getServerName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getTransportType()), McpInfo::getTransportType, bo.getTransportType());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getCommand()), McpInfo::getCommand, bo.getCommand());
|
||||
lqw.eq(bo.getStatus() != null, McpInfo::getStatus, bo.getStatus());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增MCP
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(McpInfoBo bo) {
|
||||
McpInfo add = MapstructUtils.convert(bo, McpInfo. class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setMcpId(add.getMcpId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改MCP
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(McpInfoBo bo) {
|
||||
McpInfo update = MapstructUtils.convert(bo, McpInfo. class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(McpInfo entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除MCP
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Integer> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据服务器名称获取工具配置
|
||||
*/
|
||||
@Override
|
||||
public McpServerConfig getToolConfigByName(String serverName) {
|
||||
McpInfo tool = baseMapper.selectByServerName(serverName);
|
||||
if (tool != null) {
|
||||
return convertToMcpServerConfig(tool);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有活跃的 MCP 工具配置
|
||||
*/
|
||||
@Override
|
||||
public McpConfig getAllActiveMcpConfig() {
|
||||
List<McpInfo> activeTools = baseMapper.selectActiveServers();
|
||||
Map<String, McpServerConfig> servers = new HashMap<>();
|
||||
|
||||
for (McpInfo tool : activeTools) {
|
||||
McpServerConfig serverConfig = convertToMcpServerConfig(tool);
|
||||
servers.put(tool.getServerName(), serverConfig);
|
||||
}
|
||||
|
||||
McpConfig config = new McpConfig();
|
||||
config.setMcpServers(servers);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有活跃服务器名称
|
||||
*/
|
||||
@Override
|
||||
public List<String> getActiveServerNames() {
|
||||
return baseMapper.selectActiveServerNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或更新 MCP 工具配置
|
||||
*/
|
||||
@Override
|
||||
public McpInfo saveToolConfig(McpInfoRequest request) {
|
||||
McpInfo existingTool = baseMapper.selectByServerName(request.getServerName());
|
||||
|
||||
McpInfo tool;
|
||||
if (existingTool != null) {
|
||||
tool = existingTool;
|
||||
} else {
|
||||
tool = new McpInfo();
|
||||
}
|
||||
|
||||
tool.setServerName(request.getServerName());
|
||||
tool.setCommand(request.getCommand());
|
||||
|
||||
try {
|
||||
tool.setArguments(objectMapper.writeValueAsString(request.getArgs()));
|
||||
if (request.getEnv() != null) {
|
||||
tool.setEnv(objectMapper.writeValueAsString(request.getEnv()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to serialize JSON data", e);
|
||||
}
|
||||
|
||||
tool.setDescription(request.getDescription());
|
||||
tool.setStatus(true); // 默认启用
|
||||
|
||||
if (existingTool != null) {
|
||||
baseMapper.updateById(tool);
|
||||
} else {
|
||||
baseMapper.insert(tool);
|
||||
}
|
||||
|
||||
return tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除工具配置
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteToolConfig(String serverName) {
|
||||
return baseMapper.deleteByServerName(serverName) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新工具状态
|
||||
*/
|
||||
@Override
|
||||
public boolean updateToolStatus(String serverName, Boolean status) {
|
||||
return baseMapper.updateActiveStatus(serverName, status) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用工具
|
||||
*/
|
||||
@Override
|
||||
public boolean enableTool(String serverName) {
|
||||
return updateToolStatus(serverName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用工具
|
||||
*/
|
||||
@Override
|
||||
public boolean disableTool(String serverName) {
|
||||
return updateToolStatus(serverName, false);
|
||||
}
|
||||
|
||||
private McpServerConfig convertToMcpServerConfig(McpInfo tool) {
|
||||
McpServerConfig config = new McpServerConfig();
|
||||
config.setCommand(tool.getCommand());
|
||||
|
||||
try {
|
||||
// 解析 args
|
||||
if (tool.getArguments() != null && !tool.getArguments().isEmpty()) {
|
||||
List<String> args = objectMapper.readValue(tool.getArguments(), new TypeReference<List<String>>() {});
|
||||
config.setArgs(args);
|
||||
} else {
|
||||
config.setArgs(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 解析 env
|
||||
if (tool.getEnv() != null && !tool.getEnv().isEmpty()) {
|
||||
Map<String, String> env = objectMapper.readValue(tool.getEnv(), new TypeReference<Map<String, String>>() {});
|
||||
config.setEnv(env);
|
||||
} else {
|
||||
config.setEnv(new HashMap<>());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
config.setArgs(new ArrayList<>());
|
||||
config.setEnv(new HashMap<>());
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
84
ruoyi-modules/ruoyi-workflow/pom.xml
Normal file
84
ruoyi-modules/ruoyi-workflow/pom.xml
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
|
||||
<description>
|
||||
工作流模块
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-workflow-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.talanlabs</groupId>
|
||||
<artifactId>avatar-generator</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.talanlabs</groupId>
|
||||
<artifactId>avatar-generator-cat</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.ruoyi.workflow.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.workflow.dto.workflow.*;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.service.WorkflowComponentService;
|
||||
import org.ruoyi.workflow.service.WorkflowService;
|
||||
import org.ruoyi.workflow.workflow.WorkflowStarter;
|
||||
import org.ruoyi.workflow.workflow.node.switcher.OperatorEnum;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/workflow")
|
||||
@Validated
|
||||
public class WorkflowController {
|
||||
|
||||
@Resource
|
||||
private WorkflowStarter workflowStarter;
|
||||
|
||||
@Resource
|
||||
private WorkflowService workflowService;
|
||||
|
||||
@Resource
|
||||
private WorkflowComponentService workflowComponentService;
|
||||
|
||||
@PostMapping("/add")
|
||||
public R<WorkflowResp> add(@RequestBody @Validated WfAddReq addReq) {
|
||||
return R.ok(workflowService.add(addReq.getTitle(), addReq.getRemark(), addReq.getIsPublic()));
|
||||
}
|
||||
|
||||
@PostMapping("/set-public/{wfUuid}")
|
||||
public R setPublic(@PathVariable String wfUuid, @RequestParam(defaultValue = "true") Boolean isPublic) {
|
||||
workflowService.setPublic(wfUuid, isPublic);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
public R<WorkflowResp> update(@RequestBody @Validated WorkflowUpdateReq req) {
|
||||
return R.ok(workflowService.update(req));
|
||||
}
|
||||
|
||||
@PostMapping("/del/{uuid}")
|
||||
public R delete(@PathVariable String uuid) {
|
||||
workflowService.softDelete(uuid);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/enable/{uuid}")
|
||||
public R enable(@PathVariable String uuid, @RequestParam Boolean enable) {
|
||||
workflowService.enable(uuid, enable);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/base-info/update")
|
||||
public R<WorkflowResp> updateBaseInfo(@RequestBody @Validated WfBaseInfoUpdateReq req) {
|
||||
return R.ok(workflowService.updateBaseInfo(req.getUuid(), req.getTitle(), req.getRemark(), req.getIsPublic()));
|
||||
}
|
||||
|
||||
@Operation(summary = "流式响应")
|
||||
@PostMapping(value = "/run", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter sseAsk(@RequestBody WorkflowRunReq runReq) {
|
||||
return workflowStarter.streaming(ThreadContext.getCurrentUser(), runReq.getUuid(), runReq.getInputs());
|
||||
}
|
||||
|
||||
@GetMapping("/mine/search")
|
||||
public R<Page<WorkflowResp>> searchMine(@RequestParam(defaultValue = "") String keyword,
|
||||
@RequestParam(required = false) Boolean isPublic,
|
||||
@NotNull @Min(1) Integer currentPage,
|
||||
@NotNull @Min(10) Integer pageSize) {
|
||||
return R.ok(workflowService.search(keyword, isPublic, null, currentPage, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索公开工作流
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param currentPage 当前页数
|
||||
* @param pageSize 每页数量
|
||||
* @return 工作流列表
|
||||
*/
|
||||
@GetMapping("/public/search")
|
||||
public R<Page<WorkflowResp>> searchPublic(@RequestParam(defaultValue = "") String keyword,
|
||||
@NotNull @Min(1) Integer currentPage,
|
||||
@NotNull @Min(10) Integer pageSize) {
|
||||
return R.ok(workflowService.searchPublic(keyword, currentPage, pageSize));
|
||||
}
|
||||
|
||||
@GetMapping("/public/operators")
|
||||
public R<List<Map<String, String>>> searchPublic() {
|
||||
List<Map<String, String>> result = new ArrayList<>();
|
||||
for (OperatorEnum operator : OperatorEnum.values()) {
|
||||
result.add(Map.of("name", operator.getName(), "desc", operator.getDesc()));
|
||||
}
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/public/component/list")
|
||||
public R<List<WorkflowComponent>> component() {
|
||||
return R.ok(workflowComponentService.getAllEnable());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.ruoyi.workflow.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeResp;
|
||||
import org.ruoyi.workflow.dto.workflow.WorkflowResumeReq;
|
||||
import org.ruoyi.workflow.service.WorkflowRuntimeService;
|
||||
import org.ruoyi.workflow.workflow.WorkflowStarter;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/workflow/runtime")
|
||||
@Validated
|
||||
public class WorkflowRuntimeController {
|
||||
|
||||
@Resource
|
||||
private WorkflowRuntimeService workflowRuntimeService;
|
||||
|
||||
@Resource
|
||||
private WorkflowStarter workflowStarter;
|
||||
|
||||
@Operation(summary = "接收用户输入以继续执行剩余流程")
|
||||
@PostMapping(value = "/resume/{runtimeUuid}")
|
||||
public R resume(@PathVariable String runtimeUuid, @RequestBody WorkflowResumeReq resumeReq) {
|
||||
workflowStarter.resumeFlow(runtimeUuid, resumeReq.getFeedbackContent());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
public R<Page<WfRuntimeResp>> search(@RequestParam String wfUuid,
|
||||
@NotNull @Min(1) Integer currentPage,
|
||||
@NotNull @Min(10) Integer pageSize) {
|
||||
return R.ok(workflowRuntimeService.page(wfUuid, currentPage, pageSize));
|
||||
}
|
||||
|
||||
@GetMapping("/nodes/{runtimeUuid}")
|
||||
public R<List<WfRuntimeNodeDto>> listByRuntimeId(@PathVariable String runtimeUuid) {
|
||||
return R.ok(workflowRuntimeService.listByRuntimeUuid(runtimeUuid));
|
||||
}
|
||||
|
||||
@PostMapping("/clear")
|
||||
public R<Boolean> clear(@RequestParam(defaultValue = "") String wfUuid) {
|
||||
return R.ok(workflowRuntimeService.deleteAll(wfUuid));
|
||||
}
|
||||
|
||||
@PostMapping("/del/{wfRuntimeUuid}")
|
||||
public R<Boolean> delete(@PathVariable String wfRuntimeUuid) {
|
||||
return R.ok(workflowRuntimeService.softDelete(wfRuntimeUuid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.workflow.controller.admin;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.workflow.dto.workflow.WfComponentReq;
|
||||
import org.ruoyi.workflow.dto.workflow.WfComponentSearchReq;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.service.WorkflowComponentService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/workflow/component")
|
||||
@Validated
|
||||
public class AdminWorkflowComponentController {
|
||||
@Resource
|
||||
private WorkflowComponentService workflowComponentService;
|
||||
|
||||
@PostMapping("/search")
|
||||
public R<Page<WorkflowComponent>> search(@RequestBody WfComponentSearchReq searchReq, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
|
||||
return R.ok(workflowComponentService.search(searchReq, currentPage, pageSize));
|
||||
}
|
||||
|
||||
@PostMapping("/enable")
|
||||
public R enable(@RequestParam String uuid, @RequestParam Boolean isEnable) {
|
||||
workflowComponentService.enable(uuid, isEnable);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/del/{uuid}")
|
||||
public R del(@PathVariable String uuid) {
|
||||
workflowComponentService.deleteByUuid(uuid);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/addOrUpdate")
|
||||
public R<WorkflowComponent> addOrUpdate(@Validated @RequestBody WfComponentReq req) {
|
||||
return R.ok(workflowComponentService.addOrUpdate(req));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.ruoyi.workflow.controller.admin;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.workflow.dto.workflow.WfSearchReq;
|
||||
import org.ruoyi.workflow.dto.workflow.WorkflowResp;
|
||||
import org.ruoyi.workflow.service.WorkflowService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/workflow")
|
||||
@Validated
|
||||
public class AdminWorkflowController {
|
||||
|
||||
@Resource
|
||||
private WorkflowService workflowService;
|
||||
|
||||
@PostMapping("/search")
|
||||
public R<Page<WorkflowResp>> search(@RequestBody WfSearchReq req,
|
||||
@RequestParam @NotNull @Min(1) Integer currentPage,
|
||||
@RequestParam @NotNull @Min(10) Integer pageSize) {
|
||||
return R.ok(workflowService.search(req.getTitle(), req.getIsPublic(),
|
||||
req.getIsEnable(), currentPage, pageSize));
|
||||
}
|
||||
|
||||
@PostMapping("/enable")
|
||||
public R enable(@RequestParam String uuid, @RequestParam Boolean isEnable) {
|
||||
workflowService.enable(uuid, isEnable);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user