同步最新代码到简化分支

This commit is contained in:
YunaiV 2024-10-07 16:09:49 +08:00
parent 4faedee4dc
commit d4983752b9
164 changed files with 0 additions and 9824 deletions

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.ai.enums.knowledge;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* AI 知识库-文档状态的枚举
*
* @author xiaoxin
*/
@AllArgsConstructor
@Getter
public enum AiKnowledgeDocumentStatusEnum implements IntArrayValuable {
IN_PROGRESS(10, "索引中"),
SUCCESS(20, "可用"),
FAIL(30, "失败");
/**
* 状态
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiKnowledgeDocumentStatusEnum::getStatus).toArray();
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
@Data
public class AiImagePublicPageReqVO extends PageParam {
@Schema(description = "提示词")
private String prompt;
}

View File

@ -1,50 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - AI 知识库")
@RestController
@RequestMapping("/ai/knowledge")
@Validated
public class AiKnowledgeController {
@Resource
private AiKnowledgeService knowledgeService;
@GetMapping("/page")
@Operation(summary = "获取知识库分页")
public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) {
PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO);
return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
}
@PostMapping("/create")
@Operation(summary = "创建知识库")
public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) {
return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新知识库")
public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) {
knowledgeService.updateKnowledge(updateReqVO, getLoginUserId());
return success(true);
}
}

View File

@ -1,51 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - AI 知识库文档")
@RestController
@RequestMapping("/ai/knowledge/document")
@Validated
public class AiKnowledgeDocumentController {
@Resource
private AiKnowledgeDocumentService documentService;
@PostMapping("/create")
@Operation(summary = "新建文档")
public CommonResult<Long> createKnowledgeDocument(@Valid AiKnowledgeDocumentCreateReqVO reqVO) {
Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
return success(knowledgeDocumentId);
}
@GetMapping("/page")
@Operation(summary = "获取文档分页")
public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
}
@PutMapping("/update")
@Operation(summary = "更新文档")
public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
documentService.updateKnowledgeDocument(reqVO);
return success(true);
}
}

View File

@ -1,51 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - AI 知识库段落")
@RestController
@RequestMapping("/ai/knowledge/segment")
@Validated
public class AiKnowledgeSegmentController {
@Resource
private AiKnowledgeSegmentService segmentService;
@GetMapping("/page")
@Operation(summary = "获取段落分页")
public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
}
@PutMapping("/update")
@Operation(summary = "更新段落内容")
public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) {
segmentService.updateKnowledgeSegment(reqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "启禁用段落内容")
public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
segmentService.updateKnowledgeSegmentStatus(reqVO);
return success(true);
}
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库文档的分页 Request VO")
@Data
public class AiKnowledgeDocumentPageReqVO extends PageParam {
@Schema(description = "文档名称", example = "Java 开发手册")
private String name;
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
@Data
public class AiKnowledgeDocumentRespVO extends PageParam {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long knowledgeId;
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String name;
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
private String content;
@Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
private String url;
@Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer tokens;
@Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
private Integer wordCount;
@Schema(description = "切片状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sliceStatus;
@Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO")
@Data
public class AiKnowledgeDocumentUpdateReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
@NotNull(message = "编号不能为空")
private Long id;
@Schema(description = "是否启用", example = "1")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "名称", example = "Java 开发手册")
private String name;
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - AI 知识库创建 Request VO")
@Data
public class AiKnowledgeCreateReqVO {
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
@NotBlank(message = "知识库名称不能为空")
private String name;
@Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
private String description;
@Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
private List<Long> visibilityPermissions;
@Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "嵌入模型不能为空")
private Long modelId;
@Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
@NotNull(message = "相似性阈值不能为空")
private Double similarityThreshold;
@Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "topK 不能为空")
private Integer topK;
}

View File

@ -1,46 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
@Schema(description = "管理后台 - AI 知识库文档的创建 Request VO")
@Data
public class AiKnowledgeDocumentCreateReqVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
@NotNull(message = "知识库编号不能为空")
private Long knowledgeId;
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
@NotBlank(message = "文档名称不能为空")
private String name;
@Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
@URL(message = "文档 URL 格式不正确")
private String url;
@Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
@NotNull(message = "每个段落的目标 token 数不能为空")
private Integer defaultSegmentTokens;
@Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350")
@NotNull(message = "每个段落的最小字符数不能为空")
private Integer minSegmentWordCount;
@Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "丢弃阈值不能为空")
private Integer minChunkLengthToEmbed;
@Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
@NotNull(message = "最大段落数不能为空")
private Integer maxNumSegments;
@Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "分块是否保留分隔符不能为空")
private Boolean keepSeparator;
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库的分页 Request VO")
@Data
public class AiKnowledgePageReqVO extends PageParam {
@Schema(description = "知识库名称", example = "Java 开发手册")
private String name;
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库 Response VO")
@Data
public class AiKnowledgeRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long id;
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
private String name;
@Schema(description = "知识库描述", example = "帮助你快速构建系统")
private String description;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
private Long modelId;
@Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
private String model;
}

View File

@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
@Data
public class AiKnowledgeUpdateReqVO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
@NotNull(message = "知识库编号不能为空")
private Long id;
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
@NotBlank(message = "知识库名称不能为空")
private String name;
@Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private String description;
@Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
private List<Long> visibilityPermissions;
@Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "嵌入模型不能为空")
private Long modelId;
}

View File

@ -1,20 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库分段的分页 Request VO")
@Data
public class AiKnowledgeSegmentPageReqVO extends PageParam {
@Schema(description = "分段状态", example = "1")
private Integer status;
@Schema(description = "文档编号", example = "1")
private Integer documentId;
@Schema(description = "分段内容关键字", example = "Java 开发")
private String keyword;
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
@Data
public class AiKnowledgeSegmentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long id;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long documentId;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long knowledgeId;
@Schema(description = "向量库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c")
private String vectorId;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String content;
@Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer tokens;
@Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
private Integer wordCount;
@Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库段落召回 Request VO")
@Data
public class AiKnowledgeSegmentSearchReqVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long knowledgeId;
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
private String content;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO")
@Data
public class AiKnowledgeSegmentUpdateReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long id;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String content;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 知识库段落的更新状态 Request VO")
@Data
public class AiKnowledgeSegmentUpdateStatusReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long id;
@Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "是否启用不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 思维导图分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AiMindMapPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "4325")
private Long userId;
@Schema(description = "生成内容提示", example = "Java 学习路线")
private String prompt;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - AI 思维导图 Response VO")
@Data
public class AiMindMapRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3373")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4325")
private Long userId;
@Schema(description = "生成内容提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
private String prompt;
@Schema(description = "生成的思维导图内容")
private String generatedContent;
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
private String platform;
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125")
private String model;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,74 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.List;
/**
* AI 知识库 DO
*
* @author xiaoxin
*/
@TableName(value = "ai_knowledge", autoResultMap = true)
@Data
public class AiKnowledgeDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 用户编号
* <p>
* 关联 AdminUserDO userId 字段
*/
private Long userId;
/**
* 知识库名称
*/
private String name;
/**
* 知识库描述
*/
private String description;
/**
* 可见权限,选择哪些人可见
* <p>
* -1 所有人可见其他为各自用户编号
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> visibilityPermissions;
/**
* 嵌入模型编号
*/
private Long modelId;
/**
* 模型标识
*/
private String model;
/**
* topK
*/
private Integer topK;
/**
* 相似度阈值
*/
private Double similarityThreshold;
/**
* 状态
* <p>
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* AI 知识库-文档 DO
*
* @author xiaoxin
*/
@TableName(value = "ai_knowledge_document")
@Data
public class AiKnowledgeDocumentDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 知识库编号
* <p>
* 关联 {@link AiKnowledgeDO#getId()}
*/
private Long knowledgeId;
/**
* 文件名称
*/
private String name;
/**
* 内容
*/
private String content;
/**
* 文件 URL
*/
private String url;
/**
* 文档 token 数量
*/
private Integer tokens;
/**
* 文档字符数
*/
private Integer wordCount;
// ========== 自定义分段所用参数 ==========
// TODO @新3defaultChunkSizedefaultChunkSizeminChunkSizeCharsmaxNumChunks 这几个字段的命名可能要微信一起讨论下尽量命名保持风格统一哈
/**
* 每个文本块的目标 token
*/
private Integer defaultSegmentTokens;
/**
* 每个文本块的最小字符数
*/
private Integer minSegmentWordCount;
/**
* 低于此值的块会被丢弃
*/
private Integer minChunkLengthToEmbed;
/**
* 最大块数
*/
private Integer maxNumSegments;
/**
* 分块是否保留分隔符
*/
private Boolean keepSeparator;
// ===================================
/**
* 切片状态
* <p>
* 枚举 {@link AiKnowledgeDocumentStatusEnum}
*/
private Integer sliceStatus;
/**
* 状态
* <p>
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@ -1,60 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* AI 知识库-文档分段 DO
*
* @author xiaoxin
*/
@TableName(value = "ai_knowledge_segment")
@Data
public class AiKnowledgeSegmentDO extends BaseDO {
public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
/**
* 编号
*/
@TableId
private Long id;
/**
* 向量库的编号
*/
private String vectorId;
/**
* 知识库编号
* <p>
* 关联 {@link AiKnowledgeDO#getId()}
*/
private Long knowledgeId;
/**
* 文档编号
* <p>
* 关联 {@link AiKnowledgeDocumentDO#getId()}
*/
private Long documentId;
/**
* 切片内容
*/
private String content;
/**
* 字符数
*/
private Integer wordCount;
/**
* token 数量
*/
private Integer tokens;
/**
* 状态
* <p>
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import org.apache.ibatis.annotations.Mapper;
/**
* AI 知识库-文档 Mapper
*
* @author xiaoxin
*/
@Mapper
public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> {
default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
.likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
.orderByDesc(AiKnowledgeDocumentDO::getId));
}
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import org.apache.ibatis.annotations.Mapper;
/**
* AI 知识库基础信息 Mapper
*
* @author xiaoxin
*/
@Mapper
public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
default PageResult<AiKnowledgeDO> selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
.eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
.likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName())
.and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)")))
.orderByDesc(AiKnowledgeDO::getId));
}
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* AI 知识库-分片 Mapper
*
* @author xiaoxin
*/
@Mapper
public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> {
default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
.eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
.eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
.likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
.orderByDesc(AiKnowledgeSegmentDO::getId));
}
default List<AiKnowledgeSegmentDO> selectListByVectorIds(List<String> vectorIdList) {
return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
.in(AiKnowledgeSegmentDO::getVectorId, vectorIdList)
.orderByDesc(AiKnowledgeSegmentDO::getId));
}
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
/**
* AI 知识库-文档 Service 接口
*
* @author xiaoxin
*/
public interface AiKnowledgeDocumentService {
/**
* 创建文档
*
* @param createReqVO 文档创建 Request VO
* @return 文档编号
*/
Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
/**
* 获取文档分页
*
* @param pageReqVO 分页参数
* @return 文档分页
*/
PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
/**
* 更新文档
*
* @param reqVO 更新信息
*/
void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
}

View File

@ -1,130 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.tokenizer.TokenCountEstimator;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
/**
* AI 知识库文档 Service 实现类
*
* @author xiaoxin
*/
@Service
@Slf4j
public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService {
@Resource
private AiKnowledgeDocumentMapper documentMapper;
@Resource
private AiKnowledgeSegmentMapper segmentMapper;
@Resource
private TokenCountEstimator tokenCountEstimator;
@Resource
private AiKnowledgeService knowledgeService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
// 0. 校验并获取向量存储实例
VectorStore vectorStore = knowledgeService.getVectorStoreById(createReqVO.getKnowledgeId());
// 1.1 下载文档
TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl()));
List<Document> documents = loader.get();
Document document = CollUtil.getFirst(documents);
// 1.2 文档记录入库
String content = document.getContent();
AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
.setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())
.setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
documentMapper.insert(documentDO);
Long documentId = documentDO.getId();
if (CollUtil.isEmpty(documents)) {
return documentId;
}
// 2 构造文本分段器
TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(),
createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator());
// 2.1 文档分段
List<Document> segments = tokenTextSplitter.apply(documents);
// 2.2 分段内容入库
List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)
.setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
.setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
segmentMapper.insertBatch(segmentDOList);
// 3. 向量化并存储
segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId()));
vectorStore.add(segments);
return documentId;
}
@Override
public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
return documentMapper.selectPage(pageReqVO);
}
@Override
public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
// 1. 校验文档是否存在
validateKnowledgeDocumentExists(reqVO.getId());
// 2. 更新文档
AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
documentMapper.updateById(document);
}
/**
* 校验文档是否存在
*
* @param id 文档编号
* @return 文档信息
*/
private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
if (knowledgeDocument == null) {
throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
}
return knowledgeDocument;
}
private org.springframework.core.io.Resource downloadFile(String url) {
try {
byte[] bytes = HttpUtil.downloadBytes(url);
return new ByteArrayResource(bytes);
} catch (Exception e) {
log.error("[downloadFile][url({}) 下载失败]", url, e);
throw new RuntimeException(e);
}
}
}

View File

@ -1,49 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import java.util.List;
/**
* AI 知识库段落 Service 接口
*
* @author xiaoxin
*/
public interface AiKnowledgeSegmentService {
/**
* 获取段落分页
*
* @param pageReqVO 分页查询
* @return 文档分页
*/
PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
/**
* 更新段落的内容
*
* @param reqVO 更新内容
*/
void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
/**
* 更新段落的状态
*
* @param reqVO 更新内容
*/
void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
/**
* 召回段落
*
* @param reqVO 召回请求信息
* @return 召回的段落
*/
List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO);
}

View File

@ -1,134 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS;
/**
* AI 知识库分片 Service 实现类
*
* @author xiaoxin
*/
@Service
@Slf4j
public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
@Resource
private AiKnowledgeSegmentMapper segmentMapper;
@Resource
private AiKnowledgeService knowledgeService;
@Resource
private AiChatModelService chatModelService;
@Resource
private AiApiKeyService apiKeyService;
@Override
public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
return segmentMapper.selectPage(pageReqVO);
}
@Override
public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
// 1. 校验
AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
// 2.1 获取知识库向量实例
VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
// 2.2 删除原向量
vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
// 2.3 重新向量化
Document document = new Document(reqVO.getContent());
document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
vectorStore.add(List.of(document));
// 3. 更新段落内容
AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
knowledgeSegment.setVectorId(document.getId());
segmentMapper.updateById(knowledgeSegment);
}
@Override
public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
// 0 校验
AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
// 1 获取知识库向量实例
VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
if (Objects.equals(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
// 2.1 启用重新向量化
Document document = new Document(oldKnowledgeSegment.getContent());
document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
vectorStore.add(List.of(document));
knowledgeSegment.setVectorId(document.getId());
} else {
// 2.2 禁用删除向量
vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
knowledgeSegment.setVectorId("");
}
// 3 更新段落状态
segmentMapper.updateById(knowledgeSegment);
}
@Override
public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) {
// 1. 校验
AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId());
AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
// 2. 获取向量存储实例
VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
// 3.1 向量检索
List<Document> documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent())
.withTopK(knowledge.getTopK())
.withSimilarityThreshold(knowledge.getSimilarityThreshold())
.withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build()));
if (CollUtil.isEmpty(documentList)) {
return ListUtil.empty();
}
// 3.2 段落召回
return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class));
}
/**
* 校验段落是否存在
*
* @param id 文档编号
* @return 段落信息
*/
private AiKnowledgeSegmentDO validateKnowledgeSegmentExists(Long id) {
AiKnowledgeSegmentDO knowledgeSegment = segmentMapper.selectById(id);
if (knowledgeSegment == null) {
throw exception(KNOWLEDGE_SEGMENT_NOT_EXISTS);
}
return knowledgeSegment;
}
}

View File

@ -1,58 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import org.springframework.ai.vectorstore.VectorStore;
/**
* AI 知识库-基础信息 Service 接口
*
* @author xiaoxin
*/
public interface AiKnowledgeService {
/**
* 创建知识库
*
* @param createReqVO 创建信息
* @param userId 用户编号
* @return 编号
*/
Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId);
/**
* 更新知识库
*
* @param updateReqVO 更新信息
* @param userId 用户编号
*/
void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId);
/**
* 校验知识库是否存在
*
* @param id 记录编号
*/
AiKnowledgeDO validateKnowledgeExists(Long id);
/**
* 获得知识库分页
*
* @param userId 用户编号
* @param pageReqVO 分页查询
* @return 知识库分页
*/
PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO);
/**
* 根据知识库编号获取向量存储实例
*
* @param id 知识库编号
* @return 向量存储实例
*/
VectorStore getVectorStoreById(Long id);
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS;
/**
* AI 知识库-基础信息 Service 实现类
*
* @author xiaoxin
*/
@Service
@Slf4j
public class AiKnowledgeServiceImpl implements AiKnowledgeService {
@Resource
private AiKnowledgeMapper knowledgeMapper;
@Resource
private AiChatModelService chatModelService;
@Resource
private AiApiKeyService apiKeyService;
@Override
public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) {
// 1. 校验模型配置
AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId());
// 2. 插入知识库
AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
.setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
knowledgeMapper.insert(knowledgeBase);
return knowledgeBase.getId();
}
@Override
public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) {
// 1.1 校验知识库存在
AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
throw exception(KNOWLEDGE_NOT_EXISTS);
}
// 1.2 校验模型配置
AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId());
// 2. 更新知识库
AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
updateDO.setModel(model.getModel());
knowledgeMapper.updateById(updateDO);
}
@Override
public AiKnowledgeDO validateKnowledgeExists(Long id) {
AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
if (knowledgeBase == null) {
throw exception(KNOWLEDGE_NOT_EXISTS);
}
return knowledgeBase;
}
@Override
public PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) {
return knowledgeMapper.selectPage(userId, pageReqVO);
}
@Override
public VectorStore getVectorStoreById(Long id) {
AiKnowledgeDO knowledge = validateKnowledgeExists(id);
AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
// 创建或获取 VectorStore 对象
return apiKeyService.getOrCreateVectorStore(model.getKeyId());
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2023 - 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.redis;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPooled;
/**
* TODO @xin 先拿 spring-ai 最新代码覆盖1.0.0-M1 redis 自动配置会冲突
*
* TODO 这个官方有说啥时候 fix
* TODO 看着是列在1.0.0-M2版本
*
* @author Christian Tzolov
* @author Eddú Meléndez
*/
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
@ConditionalOnBean(JedisConnectionFactory.class)
@EnableConfigurationProperties(RedisVectorStoreProperties.class)
public class RedisVectorStoreAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
JedisConnectionFactory jedisConnectionFactory) {
var config = RedisVectorStoreConfig.builder()
.withIndexName(properties.getIndex())
.withPrefix(properties.getPrefix())
.build();
return new RedisVectorStore(config, embeddingModel,
new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
properties.isInitializeSchema());
}
}

View File

@ -1,456 +0,0 @@
/*
* Copyright 2023 - 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.json.Path2;
import redis.clients.jedis.search.*;
import redis.clients.jedis.search.Schema.FieldType;
import redis.clients.jedis.search.schemafields.*;
import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* The RedisVectorStore is for managing and querying vector data in a Redis database. It
* offers functionalities like adding, deleting, and performing similarity searches on
* documents.
*
* The store utilizes RedisJSON and RedisSearch to handle JSON documents and to index and
* search vector data. It supports various vector algorithms (e.g., FLAT, HSNW) for
* efficient similarity searches. Additionally, it allows for custom metadata fields in
* the documents to be stored alongside the vector and content data.
*
* This class requires a RedisVectorStoreConfig configuration object for initialization,
* which includes settings like Redis URI, index name, field names, and vector algorithms.
* It also requires an EmbeddingModel to convert documents into embeddings before storing
* them.
*
* @author Julien Ruaux
* @author Christian Tzolov
* @author Eddú Meléndez
* @see VectorStore
* @see RedisVectorStoreConfig
* @see EmbeddingModel
*/
public class RedisVectorStore implements VectorStore, InitializingBean {
public enum Algorithm {
FLAT, HSNW
}
public record MetadataField(String name, FieldType fieldType) {
public static MetadataField text(String name) {
return new MetadataField(name, FieldType.TEXT);
}
public static MetadataField numeric(String name) {
return new MetadataField(name, FieldType.NUMERIC);
}
public static MetadataField tag(String name) {
return new MetadataField(name, FieldType.TAG);
}
}
/**
* Configuration for the Redis vector store.
*/
public static final class RedisVectorStoreConfig {
private final String indexName;
private final String prefix;
private final String contentFieldName;
private final String embeddingFieldName;
private final Algorithm vectorAlgorithm;
private final List<MetadataField> metadataFields;
private RedisVectorStoreConfig() {
this(builder());
}
private RedisVectorStoreConfig(Builder builder) {
this.indexName = builder.indexName;
this.prefix = builder.prefix;
this.contentFieldName = builder.contentFieldName;
this.embeddingFieldName = builder.embeddingFieldName;
this.vectorAlgorithm = builder.vectorAlgorithm;
this.metadataFields = builder.metadataFields;
}
/**
* Start building a new configuration.
* @return The entry point for creating a new configuration.
*/
public static Builder builder() {
return new Builder();
}
/**
* {@return the default config}
*/
public static RedisVectorStoreConfig defaultConfig() {
return builder().build();
}
public static class Builder {
private String indexName = DEFAULT_INDEX_NAME;
private String prefix = DEFAULT_PREFIX;
private String contentFieldName = DEFAULT_CONTENT_FIELD_NAME;
private String embeddingFieldName = DEFAULT_EMBEDDING_FIELD_NAME;
private Algorithm vectorAlgorithm = DEFAULT_VECTOR_ALGORITHM;
private List<MetadataField> metadataFields = new ArrayList<>();
private Builder() {
}
/**
* Configures the Redis index name to use.
* @param name the index name to use
* @return this builder
*/
public Builder withIndexName(String name) {
this.indexName = name;
return this;
}
/**
* Configures the Redis key prefix to use (default: "embedding:").
* @param prefix the prefix to use
* @return this builder
*/
public Builder withPrefix(String prefix) {
this.prefix = prefix;
return this;
}
/**
* Configures the Redis content field name to use.
* @param name the content field name to use
* @return this builder
*/
public Builder withContentFieldName(String name) {
this.contentFieldName = name;
return this;
}
/**
* Configures the Redis embedding field name to use.
* @param name the embedding field name to use
* @return this builder
*/
public Builder withEmbeddingFieldName(String name) {
this.embeddingFieldName = name;
return this;
}
/**
* Configures the Redis vector algorithmto use.
* @param algorithm the vector algorithm to use
* @return this builder
*/
public Builder withVectorAlgorithm(Algorithm algorithm) {
this.vectorAlgorithm = algorithm;
return this;
}
public Builder withMetadataFields(MetadataField... fields) {
return withMetadataFields(Arrays.asList(fields));
}
public Builder withMetadataFields(List<MetadataField> fields) {
this.metadataFields = fields;
return this;
}
/**
* {@return the immutable configuration}
*/
public RedisVectorStoreConfig build() {
return new RedisVectorStoreConfig(this);
}
}
}
private final boolean initializeSchema;
public static final String DEFAULT_INDEX_NAME = "spring-ai-index";
public static final String DEFAULT_CONTENT_FIELD_NAME = "content";
public static final String DEFAULT_EMBEDDING_FIELD_NAME = "embedding";
public static final String DEFAULT_PREFIX = "embedding:";
public static final Algorithm DEFAULT_VECTOR_ALGORITHM = Algorithm.HSNW;
private static final String QUERY_FORMAT = "%s=>[KNN %s @%s $%s AS %s]";
private static final Path2 JSON_SET_PATH = Path2.of("$");
private static final String JSON_PATH_PREFIX = "$.";
private static final Logger logger = LoggerFactory.getLogger(RedisVectorStore.class);
private static final Predicate<Object> RESPONSE_OK = Predicate.isEqual("OK");
private static final Predicate<Object> RESPONSE_DEL_OK = Predicate.isEqual(1l);
private static final String VECTOR_TYPE_FLOAT32 = "FLOAT32";
private static final String EMBEDDING_PARAM_NAME = "BLOB";
public static final String DISTANCE_FIELD_NAME = "vector_score";
private static final String DEFAULT_DISTANCE_METRIC = "COSINE";
private final JedisPooled jedis;
private final EmbeddingModel embeddingModel;
private final RedisVectorStoreConfig config;
private FilterExpressionConverter filterExpressionConverter;
public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis,
boolean initializeSchema) {
Assert.notNull(config, "Config must not be null");
Assert.notNull(embeddingModel, "Embedding model must not be null");
this.initializeSchema = initializeSchema;
this.jedis = jedis;
this.embeddingModel = embeddingModel;
this.config = config;
this.filterExpressionConverter = new RedisFilterExpressionConverter(this.config.metadataFields);
}
public JedisPooled getJedis() {
return this.jedis;
}
@Override
public void add(List<Document> documents) {
try (Pipeline pipeline = this.jedis.pipelined()) {
for (Document document : documents) {
var embedding = this.embeddingModel.embed(document);
document.setEmbedding(embedding);
var fields = new HashMap<String, Object>();
fields.put(this.config.embeddingFieldName, embedding);
fields.put(this.config.contentFieldName, document.getContent());
fields.putAll(document.getMetadata());
pipeline.jsonSetWithEscape(key(document.getId()), JSON_SET_PATH, fields);
}
List<Object> responses = pipeline.syncAndReturnAll();
Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_OK)).findAny();
if (errResponse.isPresent()) {
String message = MessageFormat.format("Could not add document: {0}", errResponse.get());
if (logger.isErrorEnabled()) {
logger.error(message);
}
throw new RuntimeException(message);
}
}
}
private String key(String id) {
return this.config.prefix + id;
}
@Override
public Optional<Boolean> delete(List<String> idList) {
try (Pipeline pipeline = this.jedis.pipelined()) {
for (String id : idList) {
pipeline.jsonDel(key(id));
}
List<Object> responses = pipeline.syncAndReturnAll();
Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_DEL_OK)).findAny();
if (errResponse.isPresent()) {
if (logger.isErrorEnabled()) {
logger.error("Could not delete document: {}", errResponse.get());
}
return Optional.of(false);
}
return Optional.of(true);
}
}
@Override
public List<Document> similaritySearch(SearchRequest request) {
Assert.isTrue(request.getTopK() > 0, "The number of documents to returned must be greater than zero");
Assert.isTrue(request.getSimilarityThreshold() >= 0 && request.getSimilarityThreshold() <= 1,
"The similarity score is bounded between 0 and 1; least to most similar respectively.");
String filter = nativeExpressionFilter(request);
String queryString = String.format(QUERY_FORMAT, filter, request.getTopK(), this.config.embeddingFieldName,
EMBEDDING_PARAM_NAME, DISTANCE_FIELD_NAME);
List<String> returnFields = new ArrayList<>();
this.config.metadataFields.stream().map(MetadataField::name).forEach(returnFields::add);
returnFields.add(this.config.embeddingFieldName);
returnFields.add(this.config.contentFieldName);
returnFields.add(DISTANCE_FIELD_NAME);
var embedding = toFloatArray(this.embeddingModel.embed(request.getQuery()));
Query query = new Query(queryString).addParam(EMBEDDING_PARAM_NAME, RediSearchUtil.toByteArray(embedding))
.returnFields(returnFields.toArray(new String[0]))
.setSortBy(DISTANCE_FIELD_NAME, true)
.dialect(2);
SearchResult result = this.jedis.ftSearch(this.config.indexName, query);
return result.getDocuments()
.stream()
.filter(d -> similarityScore(d) >= request.getSimilarityThreshold())
.map(this::toDocument)
.toList();
}
private Document toDocument(redis.clients.jedis.search.Document doc) {
var id = doc.getId().substring(this.config.prefix.length());
var content = doc.hasProperty(this.config.contentFieldName) ? doc.getString(this.config.contentFieldName)
: null;
Map<String, Object> metadata = this.config.metadataFields.stream()
.map(MetadataField::name)
.filter(doc::hasProperty)
.collect(Collectors.toMap(Function.identity(), doc::getString));
metadata.put(DISTANCE_FIELD_NAME, 1 - similarityScore(doc));
return new Document(id, content, metadata);
}
private float similarityScore(redis.clients.jedis.search.Document doc) {
return (2 - Float.parseFloat(doc.getString(DISTANCE_FIELD_NAME))) / 2;
}
private String nativeExpressionFilter(SearchRequest request) {
if (request.getFilterExpression() == null) {
return "*";
}
return "(" + this.filterExpressionConverter.convertExpression(request.getFilterExpression()) + ")";
}
@Override
public void afterPropertiesSet() {
if (!this.initializeSchema) {
return;
}
// If index already exists don't do anything
if (this.jedis.ftList().contains(this.config.indexName)) {
return;
}
String response = this.jedis.ftCreate(this.config.indexName,
FTCreateParams.createParams().on(IndexDataType.JSON).addPrefix(this.config.prefix), schemaFields());
if (!RESPONSE_OK.test(response)) {
String message = MessageFormat.format("Could not create index: {0}", response);
throw new RuntimeException(message);
}
}
private Iterable<SchemaField> schemaFields() {
Map<String, Object> vectorAttrs = new HashMap<>();
vectorAttrs.put("DIM", this.embeddingModel.dimensions());
vectorAttrs.put("DISTANCE_METRIC", DEFAULT_DISTANCE_METRIC);
vectorAttrs.put("TYPE", VECTOR_TYPE_FLOAT32);
List<SchemaField> fields = new ArrayList<>();
fields.add(TextField.of(jsonPath(this.config.contentFieldName)).as(this.config.contentFieldName).weight(1.0));
fields.add(VectorField.builder()
.fieldName(jsonPath(this.config.embeddingFieldName))
.algorithm(vectorAlgorithm())
.attributes(vectorAttrs)
.as(this.config.embeddingFieldName)
.build());
if (!CollectionUtils.isEmpty(this.config.metadataFields)) {
for (MetadataField field : this.config.metadataFields) {
fields.add(schemaField(field));
}
}
return fields;
}
private SchemaField schemaField(MetadataField field) {
String fieldName = jsonPath(field.name);
switch (field.fieldType) {
case NUMERIC:
return NumericField.of(fieldName).as(field.name);
case TAG:
return TagField.of(fieldName).as(field.name);
case TEXT:
return TextField.of(fieldName).as(field.name);
default:
throw new IllegalArgumentException(
MessageFormat.format("Field {0} has unsupported type {1}", field.name, field.fieldType));
}
}
private VectorAlgorithm vectorAlgorithm() {
if (config.vectorAlgorithm == Algorithm.HSNW) {
return VectorAlgorithm.HNSW;
}
return VectorAlgorithm.FLAT;
}
private String jsonPath(String field) {
return JSON_PATH_PREFIX + field;
}
private static float[] toFloatArray(List<Double> embeddingDouble) {
float[] embeddingFloat = new float[embeddingDouble.size()];
int i = 0;
for (Double d : embeddingDouble) {
embeddingFloat[i++] = d.floatValue();
}
return embeddingFloat;
}
}

View File

@ -1,70 +0,0 @@
package cn.iocoder.yudao.framework.ai.chat;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.util.ClientOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
/**
* {@link AzureOpenAiChatModel} 集成测试
*
* @author 芋道源码
*/
public class AzureOpenAIChatModelTests {
private final OpenAIClient openAiApi = (new OpenAIClientBuilder())
.endpoint("https://eastusprejade.openai.azure.com")
.credential(new AzureKeyCredential("xxx"))
.clientOptions((new ClientOptions()).setApplicationId("spring-ai"))
.buildClient();
private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
AzureOpenAiChatOptions.builder().withDeploymentName(DEFAULT_DEPLOYMENT_NAME).build());
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 边界事件 (boundary event) 自定义类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmBoundaryEventType {
USER_TASK_TIMEOUT(1,"用户任务超时");
private final Integer type;
private final String name;
public static BpmBoundaryEventType typeOf(Integer type) {
return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
}
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 表单权限的枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmFieldPermissionEnum {
READ(1, "只读"),
WRITE(2, "可编辑"),
NONE(3, "隐藏");
/**
* 权限
*/
private final Integer permission;
/**
* 名字
*/
private final String name;
public static BpmFieldPermissionEnum valueOf(Integer permission) {
return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values());
}
}

View File

@ -1,31 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 模型的类型的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmModelTypeEnum implements IntArrayValuable {
BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/
SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉飞书工作流的设计器
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray();
private final Integer type;
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 仿钉钉的流程器设计器条件节点的条件类型
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmSimpleModeConditionType implements IntArrayValuable {
EXPRESSION(1, "条件表达式"),
RULE(2, "条件规则");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray();
private final Integer type;
private final String name;
public static BpmSimpleModeConditionType valueOf(Integer type) {
return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,76 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 仿钉钉的流程器设计器的模型节点类型
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmSimpleModelNodeType implements IntArrayValuable {
// 0 ~ 1 开始和结束
START_NODE(0, "startEvent", "开始节点"),
END_NODE(1, "endEvent", "结束节点"),
// 10 ~ 49 各种节点
START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点前端的开始节点Id 固定
APPROVE_NODE(11, "userTask", "审批人节点"),
COPY_NODE(12, "serviceTask", "抄送人节点"),
// 50 ~ 条件分支
CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason是不是改成叫 条件分支
PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason是不是一个 并行分支 就可以啦 后面是否去掉并行网关只用包容网关
INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"),
// TODO @jason建议整合 join最终只有 条件分支并行分支包容分支三种~
// TODO @芋艿 感觉还是分开好理解一点,也好处理一点前端结构中把聚合节点显示并传过来
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
public static final String BPMN_USER_TASK_TYPE = "userTask";
private final Integer type;
private final String bpmnType;
private final String name;
/**
* 判断是否为分支节点
*
* @param type 节点类型
*/
public static boolean isBranchNode(Integer type) {
return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
|| Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
|| Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
}
/**
* 判断是否需要记录的节点
*
* @param bpmnType bpmn节点类型
*/
public static boolean isRecordNode(String bpmnType) {
return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType)
|| Objects.equals(END_NODE.getBpmnType(), bpmnType);
}
public static BpmSimpleModelNodeType valueOf(Integer type) {
return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 多人审批方式的枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
RANDOM(1, "随机挑选一人审批"),
RATIO(2, "多人会签(按通过比例)"), // 会签按通过比例
ANY(3, "多人或签(一人通过或拒绝)"), // 或签通过只需一人拒绝只需一人
SEQUENTIAL(4, "依次审批"); // 依次审批
/**
* 审批方式
*/
private final Integer method;
/**
* 名字
*/
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();
public static BpmUserTaskApproveMethodEnum valueOf(Integer method) {
return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values());
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,31 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户任务的审批类型枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable {
USER(1), // 人工审批
AUTO_APPROVE(2), // 自动通过
AUTO_REJECT(3); // 自动拒绝
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人为空时处理类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable {
APPROVE(1), // 自动通过
REJECT(2), // 自动拒绝
ASSIGN_USER(3), // 指定人员审批
ASSIGN_ADMIN(4), // 转交给流程管理员
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,31 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人与发起人相同时处理类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable {
START_USER_AUDIT(1), // 由发起人对自己审批
SKIP(2), // 自动跳过参考飞书1如果当前节点还有其他审批人则交由其他审批人进行审批2如果当前节点没有其他审批人则该节点自动通过
TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批参考飞书若部门负责人为空则自动通过
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray();
private final Integer type;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 用户任务拒绝处理类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskRejectHandlerType implements IntArrayValuable {
FINISH_PROCESS_INSTANCE(1, "终止流程"),
RETURN_USER_TASK(2, "驳回到指定任务节点");
private final Integer type;
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray();
public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户任务超时处理类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable {
REMINDER(1,"自动提醒"),
APPROVE(2, "自动同意"),
REJECT(3, "自动拒绝");
private final Integer type;
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray();
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,4 +0,0 @@
/**
* 基础包放一些通用的 VO
*/
package cn.iocoder.yudao.module.bpm.controller.admin.base;

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.base.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户精简信息 VO")
@Data
public class UserSimpleBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
}

View File

@ -1,20 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO")
@Data
public class BpmModeUpdateBpmnReqVO {
@Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotEmpty(message = "流程编号不能为空")
private String id;
@Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "BPMN XML 不能为空")
private String bpmnXml;
}

View File

@ -1,62 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* BPM 流程 MetaInfo Response DTO
* 主要用于 { Model#setMetaInfo(String)} 的存储
*
* 最终它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的
*
* @author 芋道源码
*/
@Data
public class BpmModelMetaInfoVO {
@Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
@NotEmpty(message = "流程图标不能为空")
@URL(message = "流程图标格式不正确")
private String icon;
@Schema(description = "流程描述", example = "我是描述")
private String description;
@Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@InEnum(BpmModelTypeEnum.class)
@NotNull(message = "流程类型不能为空")
private Integer type;
@Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@InEnum(BpmModelFormTypeEnum.class)
@NotNull(message = "表单类型不能为空")
private Integer formType;
@Schema(description = "表单编号", example = "1024")
private Long formId; // formType NORMAL 使用必须非空
@Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址",
example = "/bpm/oa/leave/create")
private String formCustomCreatePath; // 表单类型为 CUSTOM 必须非空
@Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址",
example = "/bpm/oa/leave/view")
private String formCustomViewPath; // 表单类型为 CUSTOM 必须非空
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否可见不能为空")
private Boolean visible;
@Schema(description = "可发起用户编号数组", example = "[1,2,3]")
private List<Long> startUserIds;
@Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
@NotEmpty(message = "可管理用户编号数组不能为空")
private List<Long> managerUserIds;
}

View File

@ -1,220 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.*;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BpmSimpleModelNodeVO {
@Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
@NotEmpty(message = "模型节点编号不能为空")
private String id;
@Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "模型节点类型不能为空")
@InEnum(BpmSimpleModelNodeType.class)
private Integer type;
@Schema(description = "模型节点名称", example = "领导审批")
private String name;
// TODO @jason gpt 大模型对了下这个字段的命名貌似叫 displayText 合适点可以等最后我们全局替换下优先级
@Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
private String showText;
@Schema(description = "子节点")
private BpmSimpleModelNodeVO childNode; // 补充说明在该模型下子节点有且仅有一个不会有多个
@Schema(description = "条件节点")
private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明有且仅有条件并行包容等分支会使用
@Schema(description = "条件类型", example = "1")
@InEnum(BpmSimpleModeConditionType.class)
private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
@Schema(description = "条件表达式", example = "${day>3}")
private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
@Schema(description = "是否默认条件", example = "true")
private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
/**
* 条件组
*/
private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
@Schema(description = "候选人策略", example = "30")
@InEnum(BpmTaskCandidateStrategyEnum.class)
private Integer candidateStrategy; // 用于审批抄送节点
@Schema(description = "候选人参数")
private String candidateParam; // 用于审批抄送节点
@Schema(description = "审批节点类型", example = "1")
@InEnum(BpmUserTaskApproveTypeEnum.class)
private Integer approveType; // 用于审批节点
@Schema(description = "多人审批方式", example = "1")
@InEnum(BpmUserTaskApproveMethodEnum.class)
private Integer approveMethod; // 用于审批节点
@Schema(description = "通过比例", example = "100")
private Integer approveRatio; // 通过比例当多人审批方式为多人会签(按通过比例) 需要设置
@Schema(description = "表单权限", example = "[]")
private List<Map<String, String>> fieldsPermission;
@Schema(description = "操作按钮设置", example = "[]")
private List<OperationButtonSetting> buttonsSetting; // 用于审批节点
// TODO @jason看看是不是可以简化@芋艿 暂时先放着不知道后面是否会用到
/**
* 附加节点 Id, 该节点不从前端传入 由程序生成. 由于当个节点无法完成功能 需要附加节点来完成
*/
@JsonIgnore
private String attachNodeId;
/**
* 审批节点拒绝处理
*/
private RejectHandler rejectHandler;
/**
* 审批节点超时处理
*/
private TimeoutHandler timeoutHandler;
@Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
@InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
private Integer assignStartUserHandlerType;
/**
* 空处理策略
*/
private AssignEmptyHandler assignEmptyHandler;
@Schema(description = "审批节点拒绝处理策略")
@Data
public static class RejectHandler {
@Schema(description = "拒绝处理类型", example = "1")
@InEnum(BpmUserTaskRejectHandlerType.class)
private Integer type;
@Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1")
private String returnNodeId;
}
@Schema(description = "审批节点超时处理策略")
@Valid
@Data
public static class TimeoutHandler {
@Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
@NotNull(message = "是否开启超时处理不能为空")
private Boolean enable;
@Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "任务超时未处理的行为不能为空")
@InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class)
private Integer type;
@Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H")
@NotEmpty(message = "超时时间不能为空")
private String timeDuration;
@Schema(description = "最大提醒次数", example = "1")
private Integer maxRemindCount;
}
@Schema(description = "空处理策略")
@Data
@Valid
public static class AssignEmptyHandler {
@Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "空处理类型不能为空")
@InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
private Integer type;
@Schema(description = "指定人员审批的用户编号数组", example = "1")
private List<Long> userIds;
}
@Schema(description = "操作按钮设置")
@Data
@Valid
public static class OperationButtonSetting {
// TODO @jason是不是按钮的标识id 会和数据库的 id 自增有点模糊key 标识会更合理一点点哈
@Schema(description = "按钮 Id", example = "1")
private Integer id;
@Schema(description = "显示名称", example = "审批")
private String displayName;
@Schema(description = "是否启用", example = "true")
private Boolean enable;
}
@Schema(description = "条件组")
@Data
@Valid
public static class ConditionGroups {
@Schema(description = "条件组下的条件关系是否为与关系", example = "true")
@NotNull(message = "条件关系不能为空")
private Boolean and;
@Schema(description = "条件组下的条件", example = "[]")
@NotEmpty(message = "条件不能为空")
private List<Condition> conditions;
}
@Schema(description = "条件")
@Data
@Valid
public static class Condition {
@Schema(description = "条件下的规则关系是否为与关系", example = "true")
@NotNull(message = "规则关系不能为空")
private Boolean and;
@Schema(description = "条件下的规则", example = "[]")
@NotEmpty(message = "规则不能为空")
private List<ConditionRule> rules;
}
@Schema(description = "条件规则")
@Data
@Valid
public static class ConditionRule {
@Schema(description = "运行符号", example = "==")
@NotEmpty(message = "运行符号不能为空")
private String opCode;
@Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId")
@NotEmpty(message = "运算符左边的值不能为空")
private String leftSide;
@Schema(description = "运算符右边的值", example = "1")
@NotEmpty(message = "运算符右边的值不能为空")
private String rightSide;
}
// TODO @芋艿条件建议可以固化的一些选项然后有个表达式兜底要支持
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
// TODO @jason需要考虑如果某个节点的配置不正确需要有提示具体怎么实现可以讨论下
@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
@Data
public class BpmSimpleModelUpdateReqVO {
@Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "流程模型编号不能为空")
private String id; // 对应 Flowable act_re_model ID_ 字段
@Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "仿钉钉流程设计模型对象不能为空")
@Valid
private BpmSimpleModelNodeVO simpleModel;
}

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
// TODO @jason这个可以简化下使用 @RequestParam嘿嘿主要 VO 项不要太多
@Schema(description = "管理后台 - 审批详情 Request VO")
@Data
public class BpmApprovalDetailReqVO {
@Schema(description = "流程定义的编号", example = "1024")
private String processDefinitionId;
@Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId;
@AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
@JsonIgnore
public boolean isValidProcessParam() {
return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
}
}

View File

@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 审批详情 Response VO")
@Data
public class BpmApprovalDetailRespVO {
@Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
@Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ApprovalNodeInfo> approveNodes;
@Schema(description = "审批节点信息")
@Data
public static class ApprovalNodeInfo {
@Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
private String id;
@Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人")
private String name;
@Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举
@Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status; // 参见 BpmTaskStatusEnum 枚举
@Schema(description = "节点的开始时间")
private LocalDateTime startTime;
@Schema(description = "节点的结束时间")
private LocalDateTime endTime;
@Schema(description = "审批节点的任务信息")
private List<ApprovalTaskInfo> tasks;
@Schema(description = "候选人用户列表")
// TODO @jasoncandidateUserList => candidateUsers保持和 tasks 的命名风格一致哈
private List<User> candidateUserList; // 用于未运行任务节点
}
// TODO @jason可以替换成 UserSimpleBaseVO简化下
@Schema(description = "用户信息")
@Data
public static class User {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
}
@Schema(description = "审批任务信息")
@Data
public static class ApprovalTaskInfo {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String id;
@Schema(description = "任务所属人", example = "1024")
private User ownerUser;
@Schema(description = "任务分配人", example = "2048")
private User assigneeUser;
@Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 BpmTaskStatusEnum 枚举
@Schema(description = "审批意见", example = "同意")
private String reason;
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
@Schema(description = "管理后台 - 表单字段权限 Request VO")
@Data
public class BpmFormFieldsPermissionReqVO {
@Schema(description = "流程定义的编号", example = "1024")
private String processDefinitionId;
@Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId;
@Schema(description = "流程活动编号", example = "StartUserNode")
private String activityId; // 对应 BPMN XML 节点 Id
@Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
private String taskId; // UserTask 对应的Id
@AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
@JsonIgnore
public boolean isValidProcessParam() {
return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
}
@AssertTrue(message = "流程活动编号和流程任务编号编号不能同时为空")
@JsonIgnore
public boolean isValidActivityParam() {
return StrUtil.isNotEmpty(activityId) || StrUtil.isNotEmpty(taskId);
}
}

View File

@ -1,78 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import java.util.*;
/**
* 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类
*
* @author jason
*/
public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
protected DeptApi deptApi;
public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi);
this.deptApi = deptApi;
}
/**
* 获得指定层级的部门负责人只有第 level 的负责人
*
* @param dept 指定部门
* @param level 第几级
* @return 部门负责人的编号
*/
protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) {
Assert.isTrue(level > 0, "level 必须大于 0");
if (dept == null) {
return null;
}
DeptRespDTO currentDept = dept;
for (int i = 1; i < level; i++) {
DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId());
if (parentDept == null) { // 找不到父级部门到了最高级返回最高级的部门负责人
break;
}
currentDept = parentDept;
}
return currentDept.getLeaderUserId();
}
/**
* 获得连续层级的部门负责人包含 [1, level] 的负责人
*
* @param deptIds 指定部门编号数组
* @param level 最大层级
* @return 连续部门负责人 Id
*/
protected Set<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) {
Assert.isTrue(level > 0, "level 必须大于 0");
if (CollUtil.isEmpty(deptIds)) {
return new HashSet<>();
}
Set<Long> deptLeaderIds = new LinkedHashSet<>(); // 保证有序
for (Long deptId : deptIds) {
DeptRespDTO dept = deptApi.getDept(deptId);
for (int i = 0; i < level; i++) {
if (dept.getLeaderUserId() != null) {
deptLeaderIds.add(dept.getLeaderUserId());
}
DeptRespDTO parentDept = deptApi.getDept(dept.getParentId());
if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了
break;
}
dept = parentDept;
}
}
return deptLeaderIds;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import java.util.Map;
import java.util.Set;
/**
* {@link BpmTaskCandidateStrategy} 抽象类
*
* @author jason
*/
public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy {
protected AdminUserApi adminUserApi;
public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) {
this.adminUserApi = adminUserApi;
}
@Override
public void removeDisableUsers(Set<Long> users) {
if (CollUtil.isEmpty(users)) {
return;
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(users);
users.removeIf(id -> {
AdminUserRespDTO user = userMap.get(id);
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
});
}
}

View File

@ -1,66 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 审批人为空 {@link BpmTaskCandidateStrategy} 实现类
*
* @author kyle
*/
@Component
public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy {
@Resource
@Lazy // 延迟加载避免循环依赖
private BpmProcessDefinitionService processDefinitionService;
public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
}
@Override
public void validateParam(String param) {
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
// 情况一指定人员审批
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement());
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
Set<Long> users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement()));
removeDisableUsers(users);
return users;
}
// 情况二流程管理员
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId());
return new HashSet<>(processDefinition.getManagerUserIds());
}
// 都不满足还是返回空
return new HashSet<>();
}
}

View File

@ -1,45 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
*
* @author jason
*/
@Component
public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI;
}
@Override
public void validateParam(String param) {
// 参数格式: | 分隔1左边为部门多个部门用 , 分隔2右边为部门层级
String[] params = param.split("\\|");
Assert.isTrue(params.length == 2, "参数格式不匹配");
deptApi.validateDeptList(StrUtils.splitToLong(params[0], ","));
Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0");
}
@Override
public Set<Long> calculateUsers(String param) {
String[] params = param.split("\\|");
return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1]));
}
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;
import static cn.hutool.core.collection.ListUtil.toList;
/**
* 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
*
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
@Resource
@Lazy
private BpmProcessInstanceService processInstanceService;
public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
}
@Override
public void validateParam(String param) {
// 参数是部门的层级
Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
// 获得流程发起人
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的 multi 部门负责人
DeptRespDTO dept = getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
// TODO @jason这里 removeDisableUsers 的原因是啥呀
removeDisableUsers(users);
return users;
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
DeptRespDTO dept = getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
removeDisableUsers(users);
return users;
}
/**
* 获取发起人的部门
*
* @param startUserId 发起人 Id
*/
protected DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
if (startUser.getDeptId() == null) { // 找不到部门
return null;
}
return deptApi.getDept(startUser.getDeptId());
}
}

View File

@ -1,91 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
/**
* 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类
*
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
@Resource
@Lazy // 避免循环依赖
private BpmProcessInstanceService processInstanceService;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
}
public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
@Override
public void validateParam(String param) {
// 参数是部门的层级
Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
// 获得流程发起人
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的部门负责人
Set<Long> users = getStartUserDeptLeader(startUserId, param);
removeDisableUsers(users);
return users;
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
// 获取发起人的部门负责人
Set<Long> users = getStartUserDeptLeader(startUserId, param);
removeDisableUsers(users);
return users;
}
private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
DeptRespDTO dept = getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级
return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
}
/**
* 获取发起人的部门
*
* @param startUserId 发起人 Id
*/
protected DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
if (startUser.getDeptId() == null) { // 找不到部门
return null;
}
return deptApi.getDept(startUser.getDeptId());
}
}

View File

@ -1,62 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
/**
* 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类
* <p>
* 适合场景用于需要发起人信息复核等场景
*
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy {
@Resource
@Lazy // 延迟加载避免循环依赖
private BpmProcessInstanceService processInstanceService;
public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER;
}
@Override
public void validateParam(String param) {
}
@Override
public boolean isParamRequired() {
return false;
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Set<Long> users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
removeDisableUsers(users);
return users;
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
Set<Long> users = SetUtils.asSet(startUserId);
removeDisableUsers(users);
return users;
}
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.el;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
import org.springframework.stereotype.Component;
// TODO @jason这个自定义转换的原因是啥呀
/**
* 根据流程变量 variable 的类型, 转换参数的值
*
* @author jason
*/
@Component
public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction {
public VariableConvertByTypeExpressionFunction() {
super("convertByType");
}
public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) {
Object variable = variableContainer.getVariable(variableName);
if (variable != null && parmaValue != null) {
// 如果值不是字符串类型, 流程变量的类型是字符串 把值转成字符串
if (!(parmaValue instanceof String) && variable instanceof String ) {
return parmaValue.toString();
}
}
return parmaValue;
}
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
// TODO @jason要不合并到 BpmnModelConstants
/**
* 仿钉钉快搭 JSON 常量信息
*
* @author jason
*/
public interface SimpleModelConstants {
// TODO @芋艿条件表达式的字段名
/**
* 网关节点默认序列流属性
*/
String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow";
/**
* 条件节点的条件类型属性
*/
String CONDITION_TYPE_ATTRIBUTE = "conditionType";
/**
* 条件节点条件表达式属性
*/
String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression";
/**
* 条件规则的条件组属性
*/
String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups";
}

View File

@ -1,47 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME;
/**
* 处理抄送用户的 {@link JavaDelegate} 的实现类
*
* 目前只有快搭模式的抄送节点使用
*
* @author jason
*/
@Component(BEAN_NAME)
public class BpmCopyTaskDelegate implements JavaDelegate {
public static final String BEAN_NAME = "bpmCopyTaskDelegate";
@Resource
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Resource
private BpmProcessInstanceCopyService processInstanceCopyService;
@Override
public void execute(DelegateExecution execution) {
// 1. 获得抄送人
Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
if (CollUtil.isEmpty(userIds)) {
return;
}
// 2. 执行抄送
FlowElement currentFlowElement = execution.getCurrentFlowElement();
processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
currentFlowElement.getId(), null, currentFlowElement.getName());
}
}

View File

@ -1,632 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.*;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
/**
* 仿钉钉快搭模型相关的工具方法
*
* @author jason
*/
public class SimpleModelUtils {
/**
* 聚合网关节点 Id 后缀
*/
public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join";
/**
* 所有审批人同意的表达式
*/
public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
/**
* 任一一名审批人同意的表达式
*/
public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
/**
* 按通过比例完成表达式
*/
public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
// TODO @yunai注释需要完善下
/**
* 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善
*
* @param processId 流程标识
* @param processName 流程名称
* @param simpleModelNode 仿钉钉流程设计模型数据结构
* @return Bpmn Model
*/
public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
BpmnModel bpmnModel = new BpmnModel();
// 不加这个 解析 Message 会报 NPE 异常 .
bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason待定是不是搞个自定义的 namespace
// TODO 芋艿后续在 review
Process process = new Process();
process.setId(processId);
process.setName(processName);
process.setExecutable(Boolean.TRUE); // TODO @jason这个是必须设置的么
bpmnModel.addProcess(process);
// TODO 芋艿这里可能纠结下到底前端传递还是后端创建出来
// 目前前端的第一个节点是发起人节点这里构建一个 StartNode用于创建 Bpmn StartEvent 节点
BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode();
startNode.setChildNode(simpleModelNode);
// 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process
traverseNodeToBuildFlowNode(startNode, process);
// 找到 end event
EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent);
// 构建并添加节点之间的连线 Sequence Flow
traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
// 自动布局
new BpmnAutoLayout(bpmnModel).execute();
return bpmnModel;
}
private static BpmSimpleModelNodeVO buildStartSimpleModelNode() {
BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO();
startNode.setId(START_EVENT_NODE_ID);
startNode.setName(START_EVENT_NODE_NAME);
startNode.setType(START_NODE.getType());
return startNode;
}
// TODO @芋艿在优化下这个注释
private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
// 1.1 无效节点返回
if (!isValidNode(node)) {
return;
}
// 1.2 END_NODE 直接返回
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
if (nodeType == END_NODE) {
return;
}
// 2.1 情况一普通节点
BpmSimpleModelNodeVO childNode = node.getChildNode();
if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
if (!isValidNode(childNode)) {
// 2.1.1 普通节点且无孩子节点分两种情况
// a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线
if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
// 2.1.1.1 如果有附加节点. 需要先建立和附加节点的连线再建立附加节点和目标节点的连线
List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), targetNodeId);
sequenceFlows.forEach(process::addFlowElement);
} else {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null);
process.addFlowElement(sequenceFlow);
}
} else {
// 2.1.2 普通节点且有孩子节点建立连线
if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
// 2.1.1.2 如果有附加节点. 需要先建立和附加节点的连线再建立附加节点和目标节点的连线
List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), childNode.getId());
sequenceFlows.forEach(process::addFlowElement);
} else {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null);
process.addFlowElement(sequenceFlow);
}
// 递归调用后续节点
traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
}
} else {
// 2.2 情况二分支节点
List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
// 分支终点节点 Id
String branchEndNodeId = null;
if (nodeType == CONDITION_BRANCH_NODE) { // 条件分支
// 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点Id
branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
} else if (nodeType == PARALLEL_BRANCH_NODE) { // 并行分支
// 分支节点分支终点节点 Id 为程序创建的网关集合节点目前不会从前端传入
branchEndNodeId = node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX;
}
// TODO 包容网关待实现
Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
// 3.1 遍历分支节点. 如下情况:
// 分支1A->B->C->D->E 分支2A->D->E A为分支节点, D为A孩子节点
for (BpmSimpleModelNodeVO item : conditionNodes) {
// TODO @jason条件分支的情况下需要分 item 搞的条件 conditionNodes 搞的条件
// @芋艿 这个是啥意思 这里的 item 的节点类型为 BpmSimpleModelNodeType.CONDITION_NODE 类型没有对应的 bpmn 的节点 仅仅用于构建条件表达式
Assert.isTrue(Objects.equals(item.getType(), CONDITION_NODE.getType()), "条件节点类型不符合");
// 构建表达式,可以为空. 并行分支为空
String conditionExpression = buildConditionExpression(item);
BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
// 3.2 分支有后续节点, 分支1: A->B->C->D
if (isValidNode(nextNodeOnCondition)) {
// 3.2.1 建立 A->B
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(),
item.getId(), item.getName(), conditionExpression);
process.addFlowElement(sequenceFlow);
// 3.2.2 递归调用后续节点连线 建立 B->C->D 的连线
traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId);
} else {
// 3.3 分支无后续节点 建立 A->D
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId,
item.getId(), item.getName(), conditionExpression);
process.addFlowElement(sequenceFlow);
}
}
// 如果是并行分支由于是程序创建的聚合网关需要手工创建聚合网关和下一个节点的连线
if (nodeType == PARALLEL_BRANCH_NODE) {
String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId, null, null, null);
process.addFlowElement(sequenceFlow);
}
// 4.递归调用后续节点 继续递归建立 D->E 的连线
traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
}
}
/**
* 构建有附加节点的连线
*
* @param nodeId 当前节点 Id
* @param attachNodeId 附属节点 Id
* @param targetNodeId 目标节点 Id
*/
private static List<SequenceFlow> buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null);
SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null);
return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow);
}
/**
* 构造条件表达式
*
* @param conditionNode 条件节点
*/
public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) {
BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionNode.getConditionType());
String conditionExpression = null;
if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
conditionExpression = conditionNode.getConditionExpression();
} else if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
ConditionGroups conditionGroups = conditionNode.getConditionGroups();
if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) {
List<String> strConditionGroups = conditionGroups.getConditions().stream().map(item -> {
if (CollUtil.isNotEmpty(item.getRules())) {
Boolean and = item.getAnd();
List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
// 如果非数值类型加引号
String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\"";
return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
});
return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
} else {
return "";
}
}).collect(Collectors.toList());
conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
}
}
// TODO 待增加其它类型
return conditionExpression;
}
private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) {
Assert.notEmpty(sourceId, "sourceId 不能为空");
Assert.notEmpty(targetId, "targetId 不能为空");
// TODO @jason如果 seqFlowId 不存在的时候是不是要生成一个默认的 seqFlowId @芋艿 貌似不需要,Flowable 会默认生成
// TODO @jason如果 name 不存在的时候是不是要生成一个默认的 name @芋艿 不需要生成默认的吧 这个会在流程图展示的 一般用户填写的不好生成默认的吧
SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
if (StrUtil.isNotEmpty(conditionExpression)) {
sequenceFlow.setConditionExpression(conditionExpression);
}
if (StrUtil.isNotEmpty(seqFlowId)) {
sequenceFlow.setId(seqFlowId);
}
if (StrUtil.isNotEmpty(seqName)) {
sequenceFlow.setName(seqName);
}
return sequenceFlow;
}
// TODO @芋艿 改成了 traverseNodeToBuildFlowNode 连线的叫 traverseNodeToBuildSequenceFlow
private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
// 判断是否有效节点
if (!isValidNode(node)) {
return;
}
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
List<FlowElement> flowElements = buildFlowNode(node, nodeType);
flowElements.forEach(process::addFlowElement);
// 如果不是网关类型的接口 并且chileNode为空退出
// 如果是分支节点则递归处理条件
if (BpmSimpleModelNodeType.isBranchNode(node.getType())
&& ArrayUtil.isNotEmpty(node.getConditionNodes())) {
node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
}
// 如果有节点则递归处理子节点
traverseNodeToBuildFlowNode(node.getChildNode(), process);
}
public static boolean isValidNode(BpmSimpleModelNodeVO node) {
return node != null && node.getId() != null;
}
public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod());
}
private static List<FlowElement> buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) {
List<FlowElement> list = new ArrayList<>();
switch (nodeType) {
case START_NODE: { // 开始节点
StartEvent startEvent = convertStartNode(node);
list.add(startEvent);
break;
}
case END_NODE: { // 结束节点
EndEvent endEvent = convertEndNode(node);
list.add(endEvent);
break;
}
case START_USER_NODE: { // 发起人节点
UserTask userTask = convertStartUserNode(node);
list.add(userTask);
break;
}
case APPROVE_NODE: { // 审批节点
List<FlowElement> flowElements = convertApproveNode(node);
list.addAll(flowElements);
break;
}
case COPY_NODE: { // 抄送节点
ServiceTask serviceTask = convertCopyNode(node);
list.add(serviceTask);
break;
}
case CONDITION_BRANCH_NODE: {
ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node);
list.add(exclusiveGateway);
break;
}
case PARALLEL_BRANCH_NODE: {
List<ParallelGateway> parallelGateways = convertParallelBranchNode(node);
list.addAll(parallelGateways);
break;
}
case INCLUSIVE_BRANCH_NODE: {
// TODO jason 待实现
break;
}
default: {
// TODO 其它节点类型的实现
}
}
return list;
}
private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) {
return buildBpmnStartUserTask(node);
}
private static List<FlowElement> convertApproveNode(BpmSimpleModelNodeVO node) {
List<FlowElement> flowElements = new ArrayList<>();
UserTask userTask = buildBpmnUserTask(node);
flowElements.add(userTask);
// 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
flowElements.add(boundaryEvent);
}
return flowElements;
}
/**
* 添加 UserTask 用户的审批超时 BoundaryEvent 事件
*
* @param userTask 审批任务
* @param timeoutHandler 超时处理器
* @return BoundaryEvent 超时事件
*/
private static BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
// 1.1 定时器边界事件
BoundaryEvent boundaryEvent = new BoundaryEvent();
boundaryEvent.setId("Event-" + IdUtil.fastUUID());
boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
boundaryEvent.setAttachedToRef(userTask);
// 1.2 定义超时时间最大提醒次数
TimerEventDefinition eventDefinition = new TimerEventDefinition();
eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
if (Objects.equals(REMINDER.getType(), timeoutHandler.getType()) &&
timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
eventDefinition.setTimeCycle(String.format("R%d/%s",
timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
}
boundaryEvent.addEventDefinition(eventDefinition);
// 2.1 添加定时器边界事件类型
addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString());
// 2.2 添加超时执行动作元素
addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, StrUtil.toStringOrNull(timeoutHandler.getType()));
return boundaryEvent;
}
private static List<ParallelGateway> convertParallelBranchNode(BpmSimpleModelNodeVO node) {
ParallelGateway parallelGateway = new ParallelGateway();
parallelGateway.setId(node.getId());
// TODO @jasonsetName
// TODO @芋艿 + jason合并网关是不是要有条件啥的微信讨论
// 并行聚合网关有程序创建前端不需要传入
ParallelGateway joinParallelGateway = new ParallelGateway();
joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX);
return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
}
private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) {
ServiceTask serviceTask = new ServiceTask();
serviceTask.setId(node.getId());
serviceTask.setName(node.getName());
serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
// 添加抄送候选人元素
addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
// 添加表单字段权限属性元素
addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
return serviceTask;
}
/**
* 给节点添加候选人元素
*/
private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
candidateStrategy == null ? null : candidateStrategy.toString());
addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
}
private static ExclusiveGateway convertConditionBranchNode(BpmSimpleModelNodeVO node) {
Assert.notEmpty(node.getConditionNodes(), "条件分支节点不能为空");
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
exclusiveGateway.setId(node.getId());
// 寻找默认的序列流
BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
item -> BooleanUtil.isTrue(item.getDefaultFlow()));
if (defaultSeqFlow != null) {
exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
}
return exclusiveGateway;
}
private static InclusiveGateway convertInclusiveBranchNode(BpmSimpleModelNodeVO node, Boolean isFork) {
InclusiveGateway inclusiveGateway = new InclusiveGateway();
inclusiveGateway.setId(node.getId());
// TODO @jason这里是不是 setName
// TODO @芋艿 + jason是不是搞个合并网关这里微信讨论下有点奇怪
// @芋艿 isFork false 就是合并网关由前端传入这个前端暂时还未实现
if (isFork) {
Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空");
// 寻找默认的序列流
BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(
node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getDefaultFlow()));
if (defaultSeqFlow != null) {
inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
}
}
return inclusiveGateway;
}
private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
// 如果不是审批人节点则直接返回
addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType()));
if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) {
return userTask;
}
// 添加候选人元素
addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
// 添加表单字段权限属性元素
addFormFieldsPermission(node.getFieldsPermission(), userTask);
// 添加操作按钮配置属性元素
addButtonsSetting(node.getButtonsSetting(), userTask);
// 处理多实例审批方式
processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
// 添加任务被拒绝的处理元素
addTaskRejectElements(node.getRejectHandler(), userTask);
// 添加用户任务的审批人与发起人相同时的处理元素
addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
// 添加用户任务的空处理元素
addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
// 设置审批任务的截止时间
if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
}
return userTask;
}
private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) {
if (rejectHandler == null) {
return;
}
addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
}
private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
if (assignStartUserHandlerType == null) {
return;
}
addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
}
private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
if (emptyHandler == null) {
return;
}
addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
}
private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
// TODO @jason这种枚举最终不要去掉哈 BpmUserTaskApproveMethodEnum因为容易不经意重叠
if (approveMethodEnum == null || approveMethodEnum == RANDOM) {
return;
}
// 添加审批方式的扩展属性
addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD,
approveMethod == null ? null : approveMethod.toString());
MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
// 设置 collectionVariable本系统用不到仅仅为了 Flowable 校验不报错
multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION);
multiInstanceCharacteristics.setSequential(false);
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
} else if (approveMethodEnum == SEQUENTIAL) {
multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION);
multiInstanceCharacteristics.setSequential(true);
multiInstanceCharacteristics.setLoopCardinality("1");
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
} else if (approveMethodEnum == RATIO) {
Assert.notNull(approveRatio, "通过比例不能为空");
multiInstanceCharacteristics.setCompletionCondition(
String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100)));
multiInstanceCharacteristics.setSequential(false);
}
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
}
/**
* 给节点添加操作按钮设置元素
*/
private static void addButtonsSetting(List<OperationButtonSetting> buttonsSetting, UserTask userTask) {
if (CollUtil.isNotEmpty(buttonsSetting)) {
List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
Map<String, String> settingMap = MapUtil.newHashMap(16);
settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
return settingMap;
});
list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
}
}
/**
* 给节点添加表单字段权限元素
*/
private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
if (CollUtil.isNotEmpty(fieldsPermissions)) {
fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
}
}
private static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
if (attributes == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setName(name);
attributes.forEach((key, value) -> {
ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.addAttribute(extensionAttribute);
});
element.addExtensionElement(extensionElement);
}
private static void addExtensionElement(FlowElement element, String name, String value) {
if (value == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setElementText(value);
extensionElement.setName(name);
element.addExtensionElement(extensionElement);
}
// ========== 各种 build 节点的方法 ==========
private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) {
StartEvent startEvent = new StartEvent();
startEvent.setId(node.getId());
startEvent.setName(node.getName());
return startEvent;
}
private static UserTask buildBpmnStartUserTask(BpmSimpleModelNodeVO node) {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
// 人工审批
addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString());
// 候选人策略为发起人自己
addCandidateElements(START_USER.getStrategy(), null, userTask);
// 添加表单字段权限属性元素
addFormFieldsPermission(node.getFieldsPermission(), userTask);
// 添加操作按钮配置属性元素
addButtonsSetting(node.getButtonsSetting(), userTask);
// 使用自动通过策略 TODO @芋艿 复用了SKIP 是否需要新加一个策略TODO @芋艿回复是不是应该类似飞书搞个草稿状态待定还有一种策略不标记自动通过而是首次发起后第一个节点自动通过
addAssignStartUserHandlerType(SKIP.getType(), userTask);
return userTask;
}
private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) {
EndEvent endEvent = new EndEvent();
endEvent.setId(node.getId());
endEvent.setName(node.getName());
// TODO @芋艿 + jason要不要加一个终止定义
return endEvent;
}
}

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.message.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* BPM 发送任务审批超时 Request DTO
*/
@Data
public class BpmMessageSendWhenTaskTimeoutReqDTO {
/**
* 流程实例的编号
*/
@NotEmpty(message = "流程实例的编号不能为空")
private String processInstanceId;
/**
* 流程实例的名字
*/
@NotEmpty(message = "流程实例的名字不能为空")
private String processInstanceName;
/**
* 流程任务的编号
*/
@NotEmpty(message = "流程任务的编号不能为空")
private String taskId;
/**
* 流程任务的名字
*/
@NotEmpty(message = "流程任务的名字不能为空")
private String taskName;
/**
* 审批人的用户编号
*/
@NotNull(message = "审批人的用户编号不能为空")
private Long assigneeUserId;
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task.bo;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo;
/**
* 已经进行中的审批节点 Response BO
*
* @author jason
*/
@Data
public class AlreadyRunApproveNodeRespBO {
/**
* 审批节点信息数组
*/
private List<ApprovalNodeInfo> approveNodes;
/**
* 已运行的节点 ID 数组 (对应 Bpmn XML 节点 id)
*/
private Set<String> runNodeIds;
/**
* 正在运行的节点的审批信息key: activityId, value: 审批信息
* <p>
* 用于依次审批需要加上候选人信息
*/
private Map<String, ApprovalNodeInfo> runningApprovalNodes;
}

View File

@ -1,25 +0,0 @@
<?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">
<parent>
<artifactId>yudao</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modules>
<module>yudao-module-iot-api</module>
<module>yudao-module-iot-biz</module>
</modules>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-iot</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
物联网模块
<!-- TODO 芋艿:需要补充下说明! -->
</description>
</project>

View File

@ -1,26 +0,0 @@
<?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">
<parent>
<artifactId>yudao-module-iot</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-iot-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
物联网 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,6 +0,0 @@
/**
* 占位
*
* TODO 芋艿后续删除
*/
package cn.iocoder.yudao.module.iot.api;

View File

@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.iot.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* iot 错误码枚举类
* <p>
* iot 系统使用 1-050-000-000
*/
public interface ErrorCodeConstants {
// ========== IoT 产品相关 1-050-001-000 ============
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
// ========== IoT 产品物模型 1-050-002-000 ============
ErrorCode THINK_MODEL_FUNCTION_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
ErrorCode THINK_MODEL_FUNCTION_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
ErrorCode THINK_MODEL_FUNCTION_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
// ========== IoT 设备 1-050-003-000 ============
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
ErrorCode DEVICE_NAME_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_003, "设备名称不能修改");
ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改");
ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态");
}

View File

@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.device;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 设备状态枚举
*
* @author haohao
*/
@Getter
public enum IotDeviceStatusEnum implements IntArrayValuable {
INACTIVE(0, "未激活"),
ONLINE(1, "在线"),
OFFLINE(2, "离线"),
DISABLED(3, "已禁用");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotDeviceStatusEnum::getStatus).toArray();
/**
* 状态
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
IotDeviceStatusEnum(Integer status, String name) {
this.status = status;
this.name = name;
}
public static IotDeviceStatusEnum fromStatus(Integer status) {
for (IotDeviceStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return value;
}
}
return null;
}
public static boolean isValidStatus(Integer status) {
return fromStatus(status) != null;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* IOT 访问方式枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotAccessModeEnum {
READ("r"),
WRITE("w"),
READ_WRITE("rw");
private final String mode;
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 产品数据格式枚举类
*
* @author ahh
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/message-parsing">阿里云 - 什么是消息解析</a>
*/
@AllArgsConstructor
@Getter
public enum IotDataFormatEnum implements IntArrayValuable {
JSON(0, "标准数据格式JSON"),
CUSTOMIZE(1, "透传/自定义");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotDataFormatEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 联网方式枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotNetTypeEnum implements IntArrayValuable {
WIFI(0, "Wi-Fi"),
CELLULAR(1, "Cellular"),
ETHERNET(2, "Ethernet"),
OTHER(3, "其他");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotNetTypeEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 产品的设备类型
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotProductDeviceTypeEnum implements IntArrayValuable {
DIRECT(0, "直连设备"),
GATEWAY_CHILD(1, "网关子设备"),
GATEWAY(2, "网关设备");
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductDeviceTypeEnum::getType).toArray();
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 产品功能物模型类型枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotProductFunctionTypeEnum implements IntArrayValuable {
PROPERTY(1, "属性"),
SERVICE(2, "服务"),
EVENT(3, "事件");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductFunctionTypeEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 产品的状态枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotProductStatusEnum implements IntArrayValuable {
UNPUBLISHED(0, "开发中"),
PUBLISHED(1, "已发布");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductStatusEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 接入网关协议枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotProtocolTypeEnum implements IntArrayValuable {
CUSTOM(0, "自定义"),
MODBUS(1, "Modbus"),
OPC_UA(2, "OPC UA"),
ZIGBEE(3, "ZigBee"),
BLE(4, "BLE");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProtocolTypeEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.product;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IOT 数据校验级别枚举类
*
* @author ahh
*/
@AllArgsConstructor
@Getter
public enum IotValidateTypeEnum implements IntArrayValuable {
WEAK(0, "弱校验"),
NONE(1, "免校验");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotValidateTypeEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 描述
*/
private final String description;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,64 +0,0 @@
<?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">
<parent>
<artifactId>yudao-module-iot</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>yudao-module-iot-biz</artifactId>
<name>${project.artifactId}</name>
<description>
物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。
<!-- TODO 芋艿:后续补充下 -->
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-iot-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<!-- MQTT -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备")
@RestController
@RequestMapping("/iot/device")
@Validated
public class IotDeviceController {
@Resource
private IotDeviceService deviceService;
@PostMapping("/create")
@Operation(summary = "创建设备")
@PreAuthorize("@ss.hasPermission('iot:device:create')")
public CommonResult<Long> createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) {
return success(deviceService.createDevice(createReqVO));
}
@PutMapping("/update-status")
@Operation(summary = "更新设备状态")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqVO updateReqVO) {
deviceService.updateDeviceStatus(updateReqVO);
return success(true);
}
@PutMapping("/update")
@Operation(summary = "更新设备")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> updateDevice(@Valid @RequestBody IotDeviceSaveReqVO updateReqVO) {
deviceService.updateDevice(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除设备")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:device:delete')")
public CommonResult<Boolean> deleteDevice(@RequestParam("id") Long id) {
deviceService.deleteDevice(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得设备")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<IotDeviceRespVO> getDevice(@RequestParam("id") Long id) {
IotDeviceDO device = deviceService.getDevice(id);
return success(BeanUtils.toBean(device, IotDeviceRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得设备分页")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<PageResult<IotDeviceRespVO>> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class));
}
@GetMapping("/count")
@Operation(summary = "获得设备数量")
@Parameter(name = "productId", description = "产品编号", example = "1")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<Long> getDeviceCount(@RequestParam("productId") Long productId) {
return success(deviceService.getDeviceCountByProductId(productId));
}
}

View File

@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - IoT 设备分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IotDevicePageReqVO extends PageParam {
// TODO @芋艿需要去掉一些多余的字段
@Schema(description = "设备唯一标识符", example = "24602")
private String deviceKey;
@Schema(description = "设备名称", example = "王五")
private String deviceName;
@Schema(description = "备注名称", example = "张三")
private String nickname;
@Schema(description = "产品编号", example = "26202")
private Long productId;
@Schema(description = "产品标识")
private String productKey;
@Schema(description = "设备类型", example = "1")
@InEnum(IotProductDeviceTypeEnum.class)
private Integer deviceType;
@Schema(description = "网关设备 ID", example = "16380")
private Long gatewayId;
@Schema(description = "设备状态", example = "1")
@InEnum(IotDeviceStatusEnum.class)
private Integer status;
@Schema(description = "设备状态最后更新时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] statusLastUpdateTime;
@Schema(description = "最后上线时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastOnlineTime;
@Schema(description = "最后离线时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastOfflineTime;
@Schema(description = "设备激活时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activeTime;
@Schema(description = "设备密钥,用于设备认证,需安全存储")
private String deviceSecret;
@Schema(description = "MQTT 客户端 ID", example = "24602")
private String mqttClientId;
@Schema(description = "MQTT 用户名", example = "芋艿")
private String mqttUsername;
@Schema(description = "MQTT 密码")
private String mqttPassword;
@Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
private String authType;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备 Response VO")
@Data
@ExcelIgnoreUnannotated
public class IotDeviceRespVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
private Long id;
@Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("设备唯一标识符")
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@ExcelProperty("设备名称备")
private String deviceName;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
@ExcelProperty("产品编号")
private Long productId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("产品 Key")
private String productKey;
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("设备类型")
private Integer deviceType;
@Schema(description = "设备备注名称", example = "张三")
@ExcelProperty("设备备注名称")
private String nickname;
@Schema(description = "网关设备 ID", example = "16380")
private Long gatewayId;
@Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("设备状态")
private Integer status;
@Schema(description = "设备状态最后更新时间")
@ExcelProperty("设备状态最后更新时间")
private LocalDateTime statusLastUpdateTime;
@Schema(description = "最后上线时间")
@ExcelProperty("最后上线时间")
private LocalDateTime lastOnlineTime;
@Schema(description = "最后离线时间")
@ExcelProperty("最后离线时间")
private LocalDateTime lastOfflineTime;
@Schema(description = "设备激活时间")
@ExcelProperty("设备激活时间")
private LocalDateTime activeTime;
@Schema(description = "设备密钥,用于设备认证")
@ExcelProperty("设备密钥")
private String deviceSecret;
@Schema(description = "MQTT 客户端 ID", example = "24602")
@ExcelProperty("MQTT 客户端 ID")
private String mqttClientId;
@Schema(description = "MQTT 用户名", example = "芋艿")
@ExcelProperty("MQTT 用户名")
private String mqttUsername;
@Schema(description = "MQTT 密码")
@ExcelProperty("MQTT 密码")
private String mqttPassword;
@Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
@ExcelProperty("认证类型(如一机一密、动态注册)")
private String authType;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备新增/修改 Request VO")
@Data
public class IotDeviceSaveReqVO {
@Schema(description = "设备编号", example = "177")
private Long id;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String deviceName;
@Schema(description = "备注名称", example = "张三")
private String nickname;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
private Long productId;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - IoT 设备状态更新 Request VO")
@Data
public class IotDeviceStatusUpdateReqVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备编号不能为空")
private Long id;
@Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备状态不能为空")
@InEnum(IotDeviceStatusEnum.class)
private Integer status;
}

View File

@ -1,95 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.product;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSimpleRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 产品")
@RestController
@RequestMapping("/iot/product")
@Validated
public class IotProductController {
@Resource
private IotProductService productService;
@PostMapping("/create")
@Operation(summary = "创建产品")
@PreAuthorize("@ss.hasPermission('iot:product:create')")
public CommonResult<Long> createProduct(@Valid @RequestBody IotProductSaveReqVO createReqVO) {
return success(productService.createProduct(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品")
@PreAuthorize("@ss.hasPermission('iot:product:update')")
public CommonResult<Boolean> updateProduct(@Valid @RequestBody IotProductSaveReqVO updateReqVO) {
productService.updateProduct(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新产品状态")
@Parameter(name = "id", description = "编号", required = true)
@Parameter(name = "status", description = "状态", required = true)
@PreAuthorize("@ss.hasPermission('iot:product:update')")
public CommonResult<Boolean> updateProductStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
productService.updateProductStatus(id, status);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:product:delete')")
public CommonResult<Boolean> deleteProduct(@RequestParam("id") Long id) {
productService.deleteProduct(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:product:query')")
public CommonResult<IotProductRespVO> getProduct(@RequestParam("id") Long id) {
IotProductDO product = productService.getProduct(id);
return success(BeanUtils.toBean(product, IotProductRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得产品分页")
@PreAuthorize("@ss.hasPermission('iot:product:query')")
public CommonResult<PageResult<IotProductRespVO>> getProductPage(@Valid IotProductPageReqVO pageReqVO) {
PageResult<IotProductDO> pageResult = productService.getProductPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotProductRespVO.class));
}
// TODO @haohao改成 simple-list
@GetMapping("/list-all-simple")
@Operation(summary = "获得所有产品列表")
@PreAuthorize("@ss.hasPermission('iot:product:query')")
public CommonResult<List<IotProductSimpleRespVO>> listAllSimpleProducts() {
List<IotProductDO> list = productService.getProductList();
return success(BeanUtils.toBean(list, IotProductSimpleRespVO.class));
}
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - IoT 产品分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IotProductPageReqVO extends PageParam {
@Schema(description = "产品名称", example = "李四")
private String name;
@Schema(description = "产品标识")
private String productKey;
}

View File

@ -1,67 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 产品 Response VO")
@Data
@ExcelIgnoreUnannotated
public class IotProductRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26087")
@ExcelProperty("产品编号")
private Long id;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@ExcelProperty("产品名称")
private String name;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("产品标识")
private String productKey;
@Schema(description = "接入网关协议", example = "2")
@ExcelProperty("接入网关协议")
private Integer protocolType;
@Schema(description = "协议编号(脚本解析 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "13177")
@ExcelProperty("协议编号(脚本解析 id")
private Long protocolId;
@Schema(description = "产品所属品类标识符", example = "14237")
@ExcelProperty("产品所属品类标识符")
private Long categoryId;
@Schema(description = "产品描述", example = "你猜")
@ExcelProperty("产品描述")
private String description;
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("数据校验级别")
private Integer validateType;
@Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("产品状态")
private Integer status;
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("设备类型")
private Integer deviceType;
@Schema(description = "联网方式", example = "2")
@ExcelProperty("联网方式")
private Integer netType;
@Schema(description = "数据格式")
@ExcelProperty("数据格式")
private Integer dataFormat;
}

View File

@ -1,51 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.product.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - IoT 产品新增/修改 Request VO")
@Data
public class IotProductSaveReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.AUTO, example = "1")
private Long id;
@Schema(description = "产品Key", requiredMode = Schema.RequiredMode.AUTO, example = "12345abc")
private String productKey;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温湿度")
@NotEmpty(message = "产品名称不能为空")
private String name;
@Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotProductDeviceTypeEnum.class, message = "设备类型必须是 {value}")
@NotNull(message = "设备类型不能为空")
private Integer deviceType;
@Schema(description = "联网方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
private Integer netType;
@Schema(description = "接入网关协议", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotProtocolTypeEnum.class, message = "接入网关协议必须是 {value}")
private Integer protocolType;
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotDataFormatEnum.class, message = "数据格式必须是 {value}")
@NotNull(message = "数据格式不能为空")
private Integer dataFormat;
@Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@InEnum(value = IotValidateTypeEnum.class, message = "数据校验级别必须是 {value}")
@NotNull(message = "数据校验级别不能为空")
private Integer validateType;
@Schema(description = "产品描述", example = "描述")
private String description;
}

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - IoT 产品 Response VO")
@Data
public class IotProductSimpleRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26087")
private Long id;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
private String name;
}

View File

@ -1,112 +0,0 @@
### 请求 /iot/think-model-function/create 接口 => 成功
POST {{baseUrl}}/iot/think-model-function/create
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
{
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Temperature",
"name": "温度",
"description": "当前温度值",
"type": 1,
"property": {
"identifier": "Temperature",
"name": "温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": -10.0,
"max": 100.0,
"step": 0.1,
"unit": "℃"
}
},
"description": "当前温度值"
}
}
### 请求 /iot/think-model-function/create 接口 => 成功
POST {{baseUrl}}/iot/think-model-function/create
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
{
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Humidity",
"name": "湿度",
"description": "当前湿度值",
"type": 1,
"property": {
"identifier": "Humidity",
"name": "湿度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": 0.0,
"max": 100.0,
"step": 0.1,
"unit": "%"
}
},
"description": "当前湿度值"
}
}
### 请求 /iot/think-model-function/update 接口 => 成功
PUT {{baseUrl}}/iot/think-model-function/update
Content-Type: application/json
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
{
"id": 11,
"productId": 1001,
"productKey": "smart-sensor-001",
"identifier": "Temperature",
"name": "温度",
"description": "当前温度值",
"type": 1,
"property": {
"identifier": "Temperature",
"name": "温度",
"accessMode": "r",
"required": true,
"dataType": {
"type": "float",
"specs": {
"min": -111.0,
"max": 222.0,
"step": 0.1,
"unit": "℃"
}
},
"description": "当前温度值"
}
}
### 请求 /iot/think-model-function/delete 接口 => 成功
DELETE {{baseUrl}}/iot/think-model-function/delete?id=7
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
### 请求 /iot/think-model-function/get 接口 => 成功
GET {{baseUrl}}/iot/think-model-function/get?id=10
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}
### 请求 /iot/think-model-function/list-by-product-id 接口 => 成功
GET {{baseUrl}}/iot/think-model-function/list-by-product-id?productId=1001
tenant-id: {{adminTenentId}}
Authorization: Bearer {{token}}

View File

@ -1,84 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
import cn.iocoder.yudao.module.iot.convert.thinkmodelfunction.IotThinkModelFunctionConvert;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 产品物模型")
@RestController
@RequestMapping("/iot/think-model-function")
@Validated
public class IotThinkModelFunctionController {
@Resource
private IotThinkModelFunctionService thinkModelFunctionService;
@PostMapping("/create")
@Operation(summary = "创建产品物模型")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:create')")
public CommonResult<Long> createThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO createReqVO) {
return success(thinkModelFunctionService.createThinkModelFunction(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品物模型")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:update')")
public CommonResult<Boolean> updateThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO updateReqVO) {
thinkModelFunctionService.updateThinkModelFunction(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品物模型")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:think-model-function:delete')")
public CommonResult<Boolean> deleteThinkModelFunction(@RequestParam("id") Long id) {
thinkModelFunctionService.deleteThinkModelFunction(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品物模型")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:think-model-function:query')")
public CommonResult<IotThinkModelFunctionRespVO> getThinkModelFunction(@RequestParam("id") Long id) {
IotThinkModelFunctionDO function = thinkModelFunctionService.getThinkModelFunction(id);
return success(IotThinkModelFunctionConvert.INSTANCE.convert(function));
}
@GetMapping("/list-by-product-id")
@Operation(summary = "获得产品物模型")
@Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:query')")
public CommonResult<List<IotThinkModelFunctionRespVO>> getThinkModelFunctionListByProductId(@RequestParam("productId") Long productId) {
List<IotThinkModelFunctionDO> list = thinkModelFunctionService.getThinkModelFunctionListByProductId(productId);
return success(IotThinkModelFunctionConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得产品物模型分页")
@PreAuthorize("@ss.hasPermission('iot:think-model-function:query')")
public CommonResult<PageResult<IotThinkModelFunctionRespVO>> getThinkModelFunctionPage(@Valid IotThinkModelFunctionPageReqVO pageReqVO) {
PageResult<IotThinkModelFunctionDO> pageResult = thinkModelFunctionService.getThinkModelFunctionPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotThinkModelFunctionRespVO.class));
}
}

View File

@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArgument;
import lombok.Data;
import java.util.List;
@Data
public class ThingModelEvent {
/**
* 事件标识符
*/
private String identifier;
/**
* 事件名称
*/
private String name;
/**
* 事件描述
*/
private String description;
/**
* 事件类型
*
* "info""alert""error"
*/
private String type;
private List<ThingModelArgument> outputData;
private String method;
}

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelDataType;
import lombok.Data;
@Data
public class ThingModelProperty {
/**
* 属性标识符
*/
private String identifier;
/**
* 属性名称
*/
private String name;
/**
* 属性描述
*/
private String description;
private String accessMode; // "rw""r""w"
private Boolean required;
// TODO @haohao这个是不是 dataSpecs dataSpecsListhttps://help.aliyun.com/zh/iot/developer-reference/api-a99t11
private ThingModelDataType dataType;
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel;
import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArgument;
import lombok.Data;
import java.util.List;
@Data
public class ThingModelService {
/**
* 服务标识符
*/
private String identifier;
/**
* 服务名称
*/
private String name;
/**
* 服务描述
*/
private String description;
/**
* 调用类型
*
* "sync""async"
*/
private String callType;
private List<ThingModelArgument> inputData;
private List<ThingModelArgument> outputData;
private String method;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType;
import lombok.Data;
@Data
public class ThingModelArgument {
private String identifier;
private String name;
private ThingModelDataType dataType;
/**
* 用于区分输入或输出参数"input" "output"
*/
private String direction;
private String description;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType;
import lombok.Data;
@Data
public class ThingModelArraySpecs {
/**
* 数组长度
*/
private int size;
/**
* 数组元素的类型
*/
private ThingModelDataType item;
}

View File

@ -1,12 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType;
import lombok.Data;
// TODO @haohao这个是不是和别的类不太统一哈
@Data
public class ThingModelArrayType extends ThingModelDataType {
private ThingModelArraySpecs specs;
}

Some files were not shown because too many files have changed in this diff Show More