1. 优化用户全局公司与部门选择的体验
2. 新增规则引擎模块
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -23,7 +23,7 @@
|
|||||||
<module>yudao-module-template</module>
|
<module>yudao-module-template</module>
|
||||||
<!-- <module>yudao-module-iot</module>-->
|
<!-- <module>yudao-module-iot</module>-->
|
||||||
<!-- <module>yudao-module-databus</module>-->
|
<!-- <module>yudao-module-databus</module>-->
|
||||||
<!-- <module>yudao-module-rule</module>-->
|
<module>yudao-module-rule</module>
|
||||||
<!-- <module>yudao-module-html2pdf</module>-->
|
<!-- <module>yudao-module-html2pdf</module>-->
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,13 @@
|
|||||||
<artifactId>yudao-spring-boot-starter-biz-business</artifactId>
|
<artifactId>yudao-spring-boot-starter-biz-business</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- LiteFlow 规则引擎相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||||
|
<version>2.15.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,25 +1,132 @@
|
|||||||
package cn.iocoder.yudao.module.rule.controller.admin.rule;
|
package cn.iocoder.yudao.module.rule.controller.admin.rule;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||||
|
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.rule.convert.rule.RuleConvert;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import cn.iocoder.yudao.module.rule.service.rule.RuleService;
|
||||||
|
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.servlet.http.HttpServletResponse;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
|
||||||
|
|
||||||
/**
|
@Tag(name = "管理后台 - 规则引擎")
|
||||||
* Rule 控制器
|
|
||||||
*
|
|
||||||
* @author ZT
|
|
||||||
*/
|
|
||||||
@Tag(name = "管理后台 - Rule")
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/admin/rule/rule")
|
@RequestMapping("/admin/rule/rule")
|
||||||
|
@Validated
|
||||||
public class RuleController {
|
public class RuleController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RuleService ruleService;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@Operation(summary = "创建规则")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:create')")
|
||||||
|
@OperateLog(type = CREATE)
|
||||||
|
public CommonResult<Long> createRule(@Valid @RequestBody RuleCreateReqVO createReqVO) {
|
||||||
|
return success(ruleService.createRule(createReqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
@Operation(summary = "更新规则")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:update')")
|
||||||
|
@OperateLog(type = UPDATE)
|
||||||
|
public CommonResult<Boolean> updateRule(@Valid @RequestBody RuleUpdateReqVO updateReqVO) {
|
||||||
|
ruleService.updateRule(updateReqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete")
|
||||||
|
@Operation(summary = "删除规则")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:delete')")
|
||||||
|
@OperateLog(type = DELETE)
|
||||||
|
public CommonResult<Boolean> deleteRule(@RequestParam("id") Long id) {
|
||||||
|
ruleService.deleteRule(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
@Operation(summary = "获得规则")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:query')")
|
||||||
|
public CommonResult<RuleRespVO> getRule(@RequestParam("id") Long id) {
|
||||||
|
RuleDO rule = ruleService.getRule(id);
|
||||||
|
return success(RuleConvert.INSTANCE.convert(rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "获得规则分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:query')")
|
||||||
|
public CommonResult<PageResult<RuleRespVO>> getRulePage(@Valid RulePageReqVO pageReqVO) {
|
||||||
|
PageResult<RuleDO> pageResult = ruleService.getRulePage(pageReqVO);
|
||||||
|
return success(RuleConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/export-excel")
|
||||||
|
@Operation(summary = "导出规则 Excel")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:export')")
|
||||||
|
@OperateLog(type = EXPORT)
|
||||||
|
public void exportRuleExcel(@Valid RulePageReqVO pageReqVO,
|
||||||
|
HttpServletResponse response) throws IOException {
|
||||||
|
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||||
|
List<RuleDO> list = ruleService.getRulePage(pageReqVO).getList();
|
||||||
|
// 导出 Excel
|
||||||
|
ExcelUtils.write(response, "规则.xls", "数据", RuleRespVO.class,
|
||||||
|
BeanUtils.toBean(list, RuleRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/execute")
|
||||||
|
@Operation(summary = "执行规则")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:execute')")
|
||||||
|
@OperateLog(type = OTHER)
|
||||||
|
public CommonResult<RuleExecuteRespVO> executeRule(@Valid @RequestBody RuleExecuteReqVO executeReqVO) {
|
||||||
|
return success(ruleService.executeRule(executeReqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/enable")
|
||||||
|
@Operation(summary = "启用规则")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:update')")
|
||||||
|
@OperateLog(type = UPDATE)
|
||||||
|
public CommonResult<Boolean> enableRule(@RequestParam("id") Long id) {
|
||||||
|
ruleService.enableRule(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/disable")
|
||||||
|
@Operation(summary = "禁用规则")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:update')")
|
||||||
|
@OperateLog(type = UPDATE)
|
||||||
|
public CommonResult<Boolean> disableRule(@RequestParam("id") Long id) {
|
||||||
|
ruleService.disableRule(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/validate")
|
||||||
|
@Operation(summary = "验证规则配置")
|
||||||
|
@PreAuthorize("@ss.hasPermission('rule:rule:validate')")
|
||||||
|
public CommonResult<Boolean> validateRuleConfig(@RequestBody String config) {
|
||||||
|
return success(ruleService.validateRuleConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/hello")
|
@GetMapping("/hello")
|
||||||
@Operation(summary = "Hello Rule")
|
@Operation(summary = "Hello Rule")
|
||||||
public CommonResult<String> hello() {
|
public CommonResult<String> hello() {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||||
|
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RuleBaseVO {
|
||||||
|
|
||||||
|
@Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户积分计算规则")
|
||||||
|
@NotBlank(message = "规则名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "规则描述", example = "根据用户行为计算积分奖励")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "规则类型:1-原子规则 2-链式规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "规则类型不能为空")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "规则状态:0-禁用 1-启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "规则状态不能为空")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "规则配置(JSON格式)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "规则配置不能为空")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
@Schema(description = "LiteFlow规则链ID", example = "userPointsChain")
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
@Schema(description = "规则版本", example = "1.0.0")
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
@Schema(description = "排序", example = "1")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 规则创建 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class RuleCreateReqVO extends RuleBaseVO {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则执行请求 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RuleExecuteReqVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则ID
|
||||||
|
*/
|
||||||
|
private Long ruleId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则链ID(与ruleId二选一)
|
||||||
|
*/
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行上下文数据
|
||||||
|
*/
|
||||||
|
private Map<String, Object> contextData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展参数
|
||||||
|
*/
|
||||||
|
private Map<String, Object> extParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则执行配置(JSON格式的规则定义)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class RuleConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则链配置
|
||||||
|
*/
|
||||||
|
private ChainConfig chain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置列表
|
||||||
|
*/
|
||||||
|
private List<NodeConfig> nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则链配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ChainConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 链ID
|
||||||
|
*/
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 链名称
|
||||||
|
*/
|
||||||
|
private String chainName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行表达式(THEN、WHEN、FOR等)
|
||||||
|
*/
|
||||||
|
private String expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class NodeConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private String nodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
private String nodeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型:common-普通节点,condition-条件节点,switch-选择节点,for-循环节点
|
||||||
|
*/
|
||||||
|
private String nodeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类全限定名
|
||||||
|
*/
|
||||||
|
private String clazz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置参数
|
||||||
|
*/
|
||||||
|
private Map<String, Object> properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则执行结果 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RuleExecuteRespVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行是否成功
|
||||||
|
*/
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误消息
|
||||||
|
*/
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行结果数据
|
||||||
|
*/
|
||||||
|
private Map<String, Object> resultData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行耗时(毫秒)
|
||||||
|
*/
|
||||||
|
private Long executionTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行开始时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行结束时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行的规则链ID
|
||||||
|
*/
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行的节点列表
|
||||||
|
*/
|
||||||
|
private String executedNodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行上下文快照
|
||||||
|
*/
|
||||||
|
private Map<String, Object> contextSnapshot;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.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 java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 规则分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class RulePageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "规则名称", example = "用户积分规则")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "规则类型:1-原子规则 2-链式规则", example = "1")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "规则状态:0-禁用 1-启用", example = "1")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "规则链ID", example = "userPointsChain")
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime[] createTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 规则 Response VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class RuleRespVO extends RuleBaseVO {
|
||||||
|
|
||||||
|
@Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.controller.admin.rule.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 规则更新 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class RuleUpdateReqVO extends RuleBaseVO {
|
||||||
|
|
||||||
|
@Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
|
@NotNull(message = "规则ID不能为空")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.convert.rule;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 Convert
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface RuleConvert {
|
||||||
|
|
||||||
|
RuleConvert INSTANCE = Mappers.getMapper(RuleConvert.class);
|
||||||
|
|
||||||
|
RuleDO convert(RuleCreateReqVO bean);
|
||||||
|
|
||||||
|
RuleDO convert(RuleUpdateReqVO bean);
|
||||||
|
|
||||||
|
RuleRespVO convert(RuleDO bean);
|
||||||
|
|
||||||
|
List<RuleRespVO> convertList(List<RuleDO> list);
|
||||||
|
|
||||||
|
PageResult<RuleRespVO> convertPage(PageResult<RuleDO> page);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.dal.dataobject.rule;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("rule_rule")
|
||||||
|
@KeySequence("rule_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RuleDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则ID
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则类型:1-原子规则 2-链式规则
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则状态:0-禁用 1-启用
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则配置(JSON格式)
|
||||||
|
*/
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiteFlow规则链ID
|
||||||
|
*/
|
||||||
|
private String chainId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则版本
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.dal.mysql.rule;
|
||||||
|
|
||||||
|
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.rule.controller.admin.rule.vo.RulePageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface RuleMapper extends BaseMapperX<RuleDO> {
|
||||||
|
|
||||||
|
default PageResult<RuleDO> selectPage(RulePageReqVO reqVO) {
|
||||||
|
return selectPage(reqVO, new LambdaQueryWrapperX<RuleDO>()
|
||||||
|
.likeIfPresent(RuleDO::getName, reqVO.getName())
|
||||||
|
.eqIfPresent(RuleDO::getType, reqVO.getType())
|
||||||
|
.eqIfPresent(RuleDO::getStatus, reqVO.getStatus())
|
||||||
|
.eqIfPresent(RuleDO::getChainId, reqVO.getChainId())
|
||||||
|
.betweenIfPresent(RuleDO::getCreateTime, reqVO.getCreateTime())
|
||||||
|
.orderByDesc(RuleDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.enums;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule 错误码枚举类
|
||||||
|
*
|
||||||
|
* rule 系统,使用 1-009-000-000 段
|
||||||
|
*/
|
||||||
|
public interface ErrorCodeConstants {
|
||||||
|
|
||||||
|
// ========== 规则相关错误码 1-009-001-000 ==========
|
||||||
|
ErrorCode RULE_NOT_EXISTS = new ErrorCode(1_009_001_000, "规则不存在");
|
||||||
|
ErrorCode RULE_CONFIG_INVALID = new ErrorCode(1_009_001_001, "规则配置无效");
|
||||||
|
ErrorCode RULE_LOAD_FAILED = new ErrorCode(1_009_001_002, "规则加载失败");
|
||||||
|
ErrorCode RULE_NOT_ENABLED = new ErrorCode(1_009_001_003, "规则未启用");
|
||||||
|
ErrorCode RULE_CHAIN_ID_EMPTY = new ErrorCode(1_009_001_004, "规则链ID不能为空");
|
||||||
|
ErrorCode RULE_EXECUTE_FAILED = new ErrorCode(1_009_001_005, "规则执行失败");
|
||||||
|
ErrorCode RULE_VALIDATION_FAILED = new ErrorCode(1_009_001_006, "规则验证失败");
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.component.action;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.component.base.BaseRuleComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据设置组件
|
||||||
|
* 用于设置上下文数据
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@LiteflowComponent("dataSetNode")
|
||||||
|
@Slf4j
|
||||||
|
public class DataSetComponent extends BaseRuleComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws Exception {
|
||||||
|
// 获取参数
|
||||||
|
String dataKey = getNodeProperty("dataKey", String.class);
|
||||||
|
Object dataValue = getNodeProperty("dataValue", Object.class);
|
||||||
|
String valueType = getNodeProperty("valueType", String.class);
|
||||||
|
|
||||||
|
if (dataKey == null) {
|
||||||
|
throw new IllegalArgumentException("数据设置组件参数不完整: dataKey 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object finalValue = dataValue;
|
||||||
|
|
||||||
|
// 根据valueType进行类型转换
|
||||||
|
if (valueType != null && dataValue != null) {
|
||||||
|
String strValue = String.valueOf(dataValue);
|
||||||
|
switch (valueType.toLowerCase()) {
|
||||||
|
case "string":
|
||||||
|
finalValue = strValue;
|
||||||
|
break;
|
||||||
|
case "integer":
|
||||||
|
case "int":
|
||||||
|
finalValue = Integer.valueOf(strValue);
|
||||||
|
break;
|
||||||
|
case "long":
|
||||||
|
finalValue = Long.valueOf(strValue);
|
||||||
|
break;
|
||||||
|
case "double":
|
||||||
|
finalValue = Double.valueOf(strValue);
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
finalValue = Boolean.valueOf(strValue);
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
// 保持原有类型
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn("不支持的数据类型: {}, 将保持原有类型", valueType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置数据到上下文
|
||||||
|
setContextData(dataKey, finalValue);
|
||||||
|
|
||||||
|
// 同时设置到结果数据中
|
||||||
|
Map<String, Object> resultData = getContextData("resultData", Map.class);
|
||||||
|
if (resultData == null) {
|
||||||
|
resultData = new HashMap<>();
|
||||||
|
setContextData("resultData", resultData);
|
||||||
|
}
|
||||||
|
resultData.put(dataKey, finalValue);
|
||||||
|
|
||||||
|
log.info("设置数据: key={}, value={}, valueType={}", dataKey, finalValue, valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.component.action;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.component.base.BaseRuleComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数值计算组件
|
||||||
|
* 支持加减乘除等数学运算
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@LiteflowComponent("mathCalculateNode")
|
||||||
|
@Slf4j
|
||||||
|
public class MathCalculateComponent extends BaseRuleComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws Exception {
|
||||||
|
// 获取参数
|
||||||
|
String leftValue = getNodeProperty("leftValue", String.class);
|
||||||
|
String rightValue = getNodeProperty("rightValue", String.class);
|
||||||
|
String operator = getNodeProperty("operator", String.class);
|
||||||
|
String resultKey = getNodeProperty("resultKey", String.class);
|
||||||
|
Integer scale = getNodeProperty("scale", Integer.class);
|
||||||
|
|
||||||
|
if (leftValue == null || rightValue == null || operator == null) {
|
||||||
|
throw new IllegalArgumentException("数值计算组件参数不完整: leftValue, rightValue, operator 都不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultKey == null) {
|
||||||
|
resultKey = "calculateResult";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale == null) {
|
||||||
|
scale = 2; // 默认保留2位小数
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换为数值
|
||||||
|
BigDecimal left = new BigDecimal(leftValue);
|
||||||
|
BigDecimal right = new BigDecimal(rightValue);
|
||||||
|
BigDecimal result;
|
||||||
|
|
||||||
|
// 根据操作符进行计算
|
||||||
|
switch (operator) {
|
||||||
|
case "+":
|
||||||
|
case "add":
|
||||||
|
result = left.add(right);
|
||||||
|
break;
|
||||||
|
case "-":
|
||||||
|
case "subtract":
|
||||||
|
result = left.subtract(right);
|
||||||
|
break;
|
||||||
|
case "*":
|
||||||
|
case "multiply":
|
||||||
|
result = left.multiply(right);
|
||||||
|
break;
|
||||||
|
case "/":
|
||||||
|
case "divide":
|
||||||
|
if (right.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
throw new IllegalArgumentException("除数不能为0");
|
||||||
|
}
|
||||||
|
result = left.divide(right, scale, RoundingMode.HALF_UP);
|
||||||
|
break;
|
||||||
|
case "%":
|
||||||
|
case "mod":
|
||||||
|
result = left.remainder(right);
|
||||||
|
break;
|
||||||
|
case "max":
|
||||||
|
result = left.max(right);
|
||||||
|
break;
|
||||||
|
case "min":
|
||||||
|
result = left.min(right);
|
||||||
|
break;
|
||||||
|
case "pow":
|
||||||
|
result = left.pow(right.intValue());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的数学操作符: " + operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置计算结果
|
||||||
|
setContextData(resultKey, result);
|
||||||
|
setContextData("lastCalculateResult", result);
|
||||||
|
|
||||||
|
log.info("数值计算: {} {} {} = {}", leftValue, operator, rightValue, result);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("数值格式错误: leftValue=" + leftValue + ", rightValue=" + rightValue, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.component.base;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础规则组件
|
||||||
|
* 提供通用的属性获取和上下文操作方法
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public abstract class BaseRuleComponent extends NodeComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点配置属性
|
||||||
|
*
|
||||||
|
* @param key 属性键
|
||||||
|
* @param clazz 属性类型
|
||||||
|
* @return 属性值
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected <T> T getNodeProperty(String key, Class<T> clazz) {
|
||||||
|
DefaultContext context = this.getContextBean(DefaultContext.class);
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先尝试从节点配置中获取
|
||||||
|
String nodeConfigKey = "nodeConfig_" + this.getNodeId();
|
||||||
|
Object nodeConfig = context.getData(nodeConfigKey);
|
||||||
|
|
||||||
|
if (nodeConfig instanceof Map) {
|
||||||
|
Map<String, Object> configMap = (Map<String, Object>) nodeConfig;
|
||||||
|
Object value = configMap.get(key);
|
||||||
|
if (value != null) {
|
||||||
|
return convertValue(value, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其次尝试从上下文数据中获取
|
||||||
|
Object value = context.getData(key);
|
||||||
|
if (value != null) {
|
||||||
|
return convertValue(value, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置上下文数据
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
protected void setContextData(String key, Object value) {
|
||||||
|
DefaultContext context = this.getContextBean(DefaultContext.class);
|
||||||
|
if (context != null) {
|
||||||
|
context.setData(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取上下文数据
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param clazz 类型
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected <T> T getContextData(String key, Class<T> clazz) {
|
||||||
|
DefaultContext context = this.getContextBean(DefaultContext.class);
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = context.getData(key);
|
||||||
|
if (value != null) {
|
||||||
|
return convertValue(value, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型转换
|
||||||
|
*
|
||||||
|
* @param value 原始值
|
||||||
|
* @param clazz 目标类型
|
||||||
|
* @return 转换后的值
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T convertValue(Object value, Class<T> clazz) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz.isInstance(value)) {
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz == String.class) {
|
||||||
|
return (T) String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz == Integer.class || clazz == int.class) {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return (T) Integer.valueOf(((Number) value).intValue());
|
||||||
|
}
|
||||||
|
return (T) Integer.valueOf(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz == Long.class || clazz == long.class) {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return (T) Long.valueOf(((Number) value).longValue());
|
||||||
|
}
|
||||||
|
return (T) Long.valueOf(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz == Boolean.class || clazz == boolean.class) {
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
|
return (T) Boolean.valueOf(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz == Double.class || clazz == double.class) {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return (T) Double.valueOf(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
return (T) Double.valueOf(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试JSON转换
|
||||||
|
if (value instanceof String && !clazz.isPrimitive()) {
|
||||||
|
String strValue = (String) value;
|
||||||
|
if (JSONUtil.isTypeJSON(strValue)) {
|
||||||
|
return JSONUtil.toBean(strValue, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("无法将类型 " + value.getClass().getSimpleName() +
|
||||||
|
" 转换为 " + clazz.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.component.common;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.component.base.BaseRuleComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件判断组件 - 数值比较
|
||||||
|
* 支持大于、小于、等于、大于等于、小于等于比较
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@LiteflowComponent("numberCompareNode")
|
||||||
|
@Slf4j
|
||||||
|
public class NumberCompareComponent extends BaseRuleComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws Exception {
|
||||||
|
// 获取参数
|
||||||
|
String leftValue = getNodeProperty("leftValue", String.class);
|
||||||
|
String operator = getNodeProperty("operator", String.class);
|
||||||
|
String rightValue = getNodeProperty("rightValue", String.class);
|
||||||
|
|
||||||
|
if (leftValue == null || operator == null || rightValue == null) {
|
||||||
|
throw new IllegalArgumentException("数值比较组件参数不完整: leftValue, operator, rightValue 都不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换为数值
|
||||||
|
BigDecimal left = new BigDecimal(leftValue);
|
||||||
|
BigDecimal right = new BigDecimal(rightValue);
|
||||||
|
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
// 根据操作符进行比较
|
||||||
|
switch (operator) {
|
||||||
|
case ">":
|
||||||
|
case "gt":
|
||||||
|
result = left.compareTo(right) > 0;
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
case "lt":
|
||||||
|
result = left.compareTo(right) < 0;
|
||||||
|
break;
|
||||||
|
case "=":
|
||||||
|
case "==":
|
||||||
|
case "eq":
|
||||||
|
result = left.compareTo(right) == 0;
|
||||||
|
break;
|
||||||
|
case ">=":
|
||||||
|
case "gte":
|
||||||
|
result = left.compareTo(right) >= 0;
|
||||||
|
break;
|
||||||
|
case "<=":
|
||||||
|
case "lte":
|
||||||
|
result = left.compareTo(right) <= 0;
|
||||||
|
break;
|
||||||
|
case "!=":
|
||||||
|
case "ne":
|
||||||
|
result = left.compareTo(right) != 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的比较操作符: " + operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置比较结果
|
||||||
|
this.setIsEnd(!result);
|
||||||
|
getSlot().setData("compareResult", result);
|
||||||
|
|
||||||
|
log.info("数值比较: {} {} {} = {}", leftValue, operator, rightValue, result);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("数值格式错误: leftValue=" + leftValue + ", rightValue=" + rightValue, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.component.common;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.component.base.BaseRuleComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串判断组件
|
||||||
|
* 支持字符串相等、包含、长度等判断
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@LiteflowComponent("stringConditionNode")
|
||||||
|
@Slf4j
|
||||||
|
public class StringConditionComponent extends BaseRuleComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws Exception {
|
||||||
|
// 获取参数
|
||||||
|
String sourceValue = getNodeProperty("sourceValue", String.class);
|
||||||
|
String targetValue = getNodeProperty("targetValue", String.class);
|
||||||
|
String operator = getNodeProperty("operator", String.class);
|
||||||
|
|
||||||
|
if (sourceValue == null || operator == null) {
|
||||||
|
throw new IllegalArgumentException("字符串判断组件参数不完整: sourceValue, operator 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
// 根据操作符进行判断
|
||||||
|
switch (operator) {
|
||||||
|
case "equals":
|
||||||
|
case "eq":
|
||||||
|
result = StrUtil.equals(sourceValue, targetValue);
|
||||||
|
break;
|
||||||
|
case "equalsIgnoreCase":
|
||||||
|
case "eqIgnoreCase":
|
||||||
|
result = StrUtil.equalsIgnoreCase(sourceValue, targetValue);
|
||||||
|
break;
|
||||||
|
case "contains":
|
||||||
|
result = StrUtil.contains(sourceValue, targetValue);
|
||||||
|
break;
|
||||||
|
case "startsWith":
|
||||||
|
result = StrUtil.startWith(sourceValue, targetValue);
|
||||||
|
break;
|
||||||
|
case "endsWith":
|
||||||
|
result = StrUtil.endWith(sourceValue, targetValue);
|
||||||
|
break;
|
||||||
|
case "isEmpty":
|
||||||
|
result = StrUtil.isEmpty(sourceValue);
|
||||||
|
break;
|
||||||
|
case "isNotEmpty":
|
||||||
|
result = StrUtil.isNotEmpty(sourceValue);
|
||||||
|
break;
|
||||||
|
case "isBlank":
|
||||||
|
result = StrUtil.isBlank(sourceValue);
|
||||||
|
break;
|
||||||
|
case "isNotBlank":
|
||||||
|
result = StrUtil.isNotBlank(sourceValue);
|
||||||
|
break;
|
||||||
|
case "lengthEquals":
|
||||||
|
Integer expectedLength = getNodeProperty("expectedLength", Integer.class);
|
||||||
|
if (expectedLength != null) {
|
||||||
|
result = sourceValue.length() == expectedLength;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "lengthGreaterThan":
|
||||||
|
Integer minLength = getNodeProperty("minLength", Integer.class);
|
||||||
|
if (minLength != null) {
|
||||||
|
result = sourceValue.length() > minLength;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "lengthLessThan":
|
||||||
|
Integer maxLength = getNodeProperty("maxLength", Integer.class);
|
||||||
|
if (maxLength != null) {
|
||||||
|
result = sourceValue.length() < maxLength;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "matches":
|
||||||
|
String pattern = getNodeProperty("pattern", String.class);
|
||||||
|
if (pattern != null) {
|
||||||
|
result = sourceValue.matches(pattern);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的字符串操作符: " + operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置判断结果
|
||||||
|
this.setIsEnd(!result);
|
||||||
|
setContextData("stringConditionResult", result);
|
||||||
|
|
||||||
|
log.info("字符串判断: sourceValue={}, operator={}, targetValue={}, result={}",
|
||||||
|
sourceValue, operator, targetValue, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.config;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.spring.ComponentScanner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiteFlow 配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class LiteFlowConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件扫描器
|
||||||
|
* 自动扫描LiteFlow组件
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ComponentScanner componentScanner() {
|
||||||
|
return new ComponentScanner("cn.iocoder.yudao.module.rule.framework.liteflow.component");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.framework.liteflow.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.RuleExecuteReqVO;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.flow.element.chain.LiteFlowChain;
|
||||||
|
import com.yomahub.liteflow.flow.element.chain.LiteFlowChainBuilder;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiteFlow 服务实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class LiteFlowService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行规则
|
||||||
|
*
|
||||||
|
* @param chainId 规则链ID
|
||||||
|
* @param contextData 上下文数据
|
||||||
|
* @param extParams 扩展参数
|
||||||
|
* @return 执行结果
|
||||||
|
*/
|
||||||
|
public Map<String, Object> executeRule(String chainId, Map<String, Object> contextData, Map<String, Object> extParams) {
|
||||||
|
try {
|
||||||
|
// 创建上下文
|
||||||
|
DefaultContext context = new DefaultContext();
|
||||||
|
|
||||||
|
// 设置上下文数据
|
||||||
|
if (MapUtil.isNotEmpty(contextData)) {
|
||||||
|
contextData.forEach(context::setData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置扩展参数
|
||||||
|
if (MapUtil.isNotEmpty(extParams)) {
|
||||||
|
extParams.forEach(context::setData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行规则链
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp(chainId, null, context);
|
||||||
|
|
||||||
|
// 构建返回结果
|
||||||
|
Map<String, Object> resultData = new HashMap<>();
|
||||||
|
resultData.put("success", response.isSuccess());
|
||||||
|
resultData.put("message", response.getMessage());
|
||||||
|
resultData.put("executeStepStr", response.getExecuteStepStr());
|
||||||
|
|
||||||
|
// 获取执行结果数据
|
||||||
|
if (response.getSlot() != null && response.getSlot().getContextBean() instanceof DefaultContext) {
|
||||||
|
DefaultContext resultContext = (DefaultContext) response.getSlot().getContextBean();
|
||||||
|
if (resultContext != null) {
|
||||||
|
resultData.put("contextData", resultContext.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultData;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("执行规则链失败: chainId={}", chainId, e);
|
||||||
|
throw new RuntimeException("规则执行失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载规则到LiteFlow
|
||||||
|
*
|
||||||
|
* @param rule 规则DO
|
||||||
|
*/
|
||||||
|
public void loadRule(RuleDO rule) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isBlank(rule.getConfig())) {
|
||||||
|
throw new IllegalArgumentException("规则配置不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析JSON配置
|
||||||
|
JSONObject configJson = JSONUtil.parseObj(rule.getConfig());
|
||||||
|
RuleExecuteReqVO.RuleConfig ruleConfig = JSONUtil.toBean(configJson, RuleExecuteReqVO.RuleConfig.class);
|
||||||
|
|
||||||
|
if (ruleConfig.getChain() == null) {
|
||||||
|
throw new IllegalArgumentException("规则链配置不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建LiteFlow规则链
|
||||||
|
String chainId = StrUtil.isNotBlank(rule.getChainId()) ? rule.getChainId() : ruleConfig.getChain().getChainId();
|
||||||
|
String expression = ruleConfig.getChain().getExpression();
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(expression)) {
|
||||||
|
throw new IllegalArgumentException("规则链表达式不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并加载规则链
|
||||||
|
LiteFlowChain chain = LiteFlowChainBuilder.createChain()
|
||||||
|
.setChainId(chainId)
|
||||||
|
.setChainName(ruleConfig.getChain().getChainName())
|
||||||
|
.setEL(expression)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 注册规则链
|
||||||
|
flowExecutor.getChainContainer().setChain(chainId, chain);
|
||||||
|
|
||||||
|
log.info("成功加载规则链: chainId={}, expression={}", chainId, expression);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("加载规则到LiteFlow失败: ruleId={}", rule.getId(), e);
|
||||||
|
throw new RuntimeException("加载规则失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新加载规则
|
||||||
|
*
|
||||||
|
* @param rule 规则DO
|
||||||
|
*/
|
||||||
|
public void reloadRule(RuleDO rule) {
|
||||||
|
// 先移除再加载
|
||||||
|
if (StrUtil.isNotBlank(rule.getChainId())) {
|
||||||
|
removeRule(rule.getChainId());
|
||||||
|
}
|
||||||
|
loadRule(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除规则
|
||||||
|
*
|
||||||
|
* @param chainId 规则链ID
|
||||||
|
*/
|
||||||
|
public void removeRule(String chainId) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isNotBlank(chainId)) {
|
||||||
|
flowExecutor.getChainContainer().removeChain(chainId);
|
||||||
|
log.info("成功移除规则链: chainId={}", chainId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("移除规则链失败: chainId={}", chainId, e);
|
||||||
|
throw new RuntimeException("移除规则失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证规则配置
|
||||||
|
*
|
||||||
|
* @param config 规则配置JSON
|
||||||
|
* @return 验证结果
|
||||||
|
*/
|
||||||
|
public Boolean validateRuleConfig(String config) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isBlank(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析JSON
|
||||||
|
JSONObject configJson = JSONUtil.parseObj(config);
|
||||||
|
RuleExecuteReqVO.RuleConfig ruleConfig = JSONUtil.toBean(configJson, RuleExecuteReqVO.RuleConfig.class);
|
||||||
|
|
||||||
|
// 验证必要字段
|
||||||
|
if (ruleConfig.getChain() == null) {
|
||||||
|
log.warn("规则链配置为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(ruleConfig.getChain().getChainId())) {
|
||||||
|
log.warn("规则链ID为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(ruleConfig.getChain().getExpression())) {
|
||||||
|
log.warn("规则链表达式为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证节点配置
|
||||||
|
List<RuleExecuteReqVO.NodeConfig> nodes = ruleConfig.getNodes();
|
||||||
|
if (nodes != null) {
|
||||||
|
for (RuleExecuteReqVO.NodeConfig node : nodes) {
|
||||||
|
if (StrUtil.isBlank(node.getNodeId())) {
|
||||||
|
log.warn("节点ID为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(node.getClazz())) {
|
||||||
|
log.warn("节点类名为空: nodeId={}", node.getNodeId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("验证规则配置时发生异常", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.service.rule;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface RuleService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建规则
|
||||||
|
*
|
||||||
|
* @param createReqVO 创建信息
|
||||||
|
* @return 编号
|
||||||
|
*/
|
||||||
|
Long createRule(@Valid RuleCreateReqVO createReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新规则
|
||||||
|
*
|
||||||
|
* @param updateReqVO 更新信息
|
||||||
|
*/
|
||||||
|
void updateRule(@Valid RuleUpdateReqVO updateReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除规则
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
void deleteRule(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得规则
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
* @return 规则
|
||||||
|
*/
|
||||||
|
RuleDO getRule(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得规则分页
|
||||||
|
*
|
||||||
|
* @param pageReqVO 分页查询
|
||||||
|
* @return 规则分页
|
||||||
|
*/
|
||||||
|
PageResult<RuleDO> getRulePage(RulePageReqVO pageReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行规则
|
||||||
|
*
|
||||||
|
* @param executeReqVO 执行请求
|
||||||
|
* @return 执行结果
|
||||||
|
*/
|
||||||
|
RuleExecuteRespVO executeRule(@Valid RuleExecuteReqVO executeReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用规则
|
||||||
|
*
|
||||||
|
* @param id 规则ID
|
||||||
|
*/
|
||||||
|
void enableRule(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用规则
|
||||||
|
*
|
||||||
|
* @param id 规则ID
|
||||||
|
*/
|
||||||
|
void disableRule(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证规则配置
|
||||||
|
*
|
||||||
|
* @param config 规则配置JSON
|
||||||
|
* @return 验证结果
|
||||||
|
*/
|
||||||
|
Boolean validateRuleConfig(String config);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.service.rule;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.rule.convert.rule.RuleConvert;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.mysql.rule.RuleMapper;
|
||||||
|
import cn.iocoder.yudao.module.rule.enums.ErrorCodeConstants;
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.service.LiteFlowService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.rule.enums.ErrorCodeConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
public class RuleServiceImpl implements RuleService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RuleMapper ruleMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LiteFlowService liteFlowService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long createRule(RuleCreateReqVO createReqVO) {
|
||||||
|
// 验证规则配置
|
||||||
|
if (!validateRuleConfig(createReqVO.getConfig())) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_CONFIG_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入
|
||||||
|
RuleDO rule = RuleConvert.INSTANCE.convert(createReqVO);
|
||||||
|
rule.setVersion("1.0.0");
|
||||||
|
ruleMapper.insert(rule);
|
||||||
|
|
||||||
|
// 如果是启用状态,加载到LiteFlow中
|
||||||
|
if (rule.getStatus() == 1) {
|
||||||
|
try {
|
||||||
|
liteFlowService.loadRule(rule);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("加载规则到LiteFlow失败", e);
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateRule(RuleUpdateReqVO updateReqVO) {
|
||||||
|
// 校验存在
|
||||||
|
validateRuleExists(updateReqVO.getId());
|
||||||
|
|
||||||
|
// 验证规则配置
|
||||||
|
if (!validateRuleConfig(updateReqVO.getConfig())) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_CONFIG_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新
|
||||||
|
RuleDO updateObj = RuleConvert.INSTANCE.convert(updateReqVO);
|
||||||
|
ruleMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
// 重新加载到LiteFlow中
|
||||||
|
RuleDO rule = ruleMapper.selectById(updateReqVO.getId());
|
||||||
|
if (rule.getStatus() == 1) {
|
||||||
|
try {
|
||||||
|
liteFlowService.reloadRule(rule);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("重新加载规则到LiteFlow失败", e);
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteRule(Long id) {
|
||||||
|
// 校验存在
|
||||||
|
RuleDO rule = validateRuleExists(id);
|
||||||
|
|
||||||
|
// 从LiteFlow中移除
|
||||||
|
try {
|
||||||
|
liteFlowService.removeRule(rule.getChainId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("从LiteFlow中移除规则失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
ruleMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuleDO validateRuleExists(Long id) {
|
||||||
|
RuleDO rule = ruleMapper.selectById(id);
|
||||||
|
if (rule == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RuleDO getRule(Long id) {
|
||||||
|
return ruleMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<RuleDO> getRulePage(RulePageReqVO pageReqVO) {
|
||||||
|
return ruleMapper.selectPage(pageReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RuleExecuteRespVO executeRule(RuleExecuteReqVO executeReqVO) {
|
||||||
|
LocalDateTime startTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取规则配置
|
||||||
|
String chainId;
|
||||||
|
if (executeReqVO.getRuleId() != null) {
|
||||||
|
RuleDO rule = getRule(executeReqVO.getRuleId());
|
||||||
|
if (rule == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
if (rule.getStatus() != 1) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_NOT_ENABLED);
|
||||||
|
}
|
||||||
|
chainId = rule.getChainId();
|
||||||
|
} else {
|
||||||
|
chainId = executeReqVO.getChainId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(chainId)) {
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_CHAIN_ID_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行规则
|
||||||
|
Map<String, Object> resultData = liteFlowService.executeRule(
|
||||||
|
chainId,
|
||||||
|
executeReqVO.getContextData(),
|
||||||
|
executeReqVO.getExtParams()
|
||||||
|
);
|
||||||
|
|
||||||
|
LocalDateTime endTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
RuleExecuteRespVO response = new RuleExecuteRespVO();
|
||||||
|
response.setSuccess(true);
|
||||||
|
response.setResultData(resultData);
|
||||||
|
response.setStartTime(startTime);
|
||||||
|
response.setEndTime(endTime);
|
||||||
|
response.setExecutionTime(java.time.Duration.between(startTime, endTime).toMillis());
|
||||||
|
response.setChainId(chainId);
|
||||||
|
response.setContextSnapshot(executeReqVO.getContextData());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("规则执行失败", e);
|
||||||
|
|
||||||
|
LocalDateTime endTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
RuleExecuteRespVO response = new RuleExecuteRespVO();
|
||||||
|
response.setSuccess(false);
|
||||||
|
response.setErrorMessage(e.getMessage());
|
||||||
|
response.setStartTime(startTime);
|
||||||
|
response.setEndTime(endTime);
|
||||||
|
response.setExecutionTime(java.time.Duration.between(startTime, endTime).toMillis());
|
||||||
|
response.setContextSnapshot(executeReqVO.getContextData());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void enableRule(Long id) {
|
||||||
|
RuleDO rule = validateRuleExists(id);
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
RuleDO updateObj = new RuleDO();
|
||||||
|
updateObj.setId(id);
|
||||||
|
updateObj.setStatus(1);
|
||||||
|
ruleMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
// 加载到LiteFlow中
|
||||||
|
try {
|
||||||
|
rule.setStatus(1);
|
||||||
|
liteFlowService.loadRule(rule);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("启用规则时加载到LiteFlow失败", e);
|
||||||
|
throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void disableRule(Long id) {
|
||||||
|
RuleDO rule = validateRuleExists(id);
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
RuleDO updateObj = new RuleDO();
|
||||||
|
updateObj.setId(id);
|
||||||
|
updateObj.setStatus(0);
|
||||||
|
ruleMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
// 从LiteFlow中移除
|
||||||
|
try {
|
||||||
|
liteFlowService.removeRule(rule.getChainId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("禁用规则时从LiteFlow中移除失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean validateRuleConfig(String config) {
|
||||||
|
if (StrUtil.isBlank(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 验证JSON格式
|
||||||
|
if (!JSONUtil.isTypeJSON(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进一步验证规则配置结构
|
||||||
|
return liteFlowService.validateRuleConfig(config);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("规则配置验证失败", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -120,4 +120,30 @@ yudao:
|
|||||||
tenant: # 多租户相关配置项
|
tenant: # 多租户相关配置项
|
||||||
enable: true
|
enable: true
|
||||||
|
|
||||||
|
# LiteFlow 配置
|
||||||
|
liteflow:
|
||||||
|
# 规则来源,支持本地文件、zookeeper、nacos、etcd、sql、apollo、redis等
|
||||||
|
rule-source: classpath:liteflow/*.json
|
||||||
|
# 启用
|
||||||
|
enable: true
|
||||||
|
# 解析格式,支持 xml,json,yml
|
||||||
|
parse-type: json
|
||||||
|
# 是否开启主要过程耗时统计
|
||||||
|
main-executor-works: true
|
||||||
|
# 是否开启监控
|
||||||
|
monitor:
|
||||||
|
enable-log: true
|
||||||
|
# 是否支持多种类型的规则文件混合
|
||||||
|
support-multiple-type: true
|
||||||
|
# 全局重试次数
|
||||||
|
retry-count: 0
|
||||||
|
# 并行执行器的最大等待时间(毫秒)
|
||||||
|
when-max-wait-time: 15000
|
||||||
|
# 并行执行器的最大等待时间是否开启
|
||||||
|
when-max-wait-time-enable: true
|
||||||
|
# 异步线程的最大等待时间(毫秒)
|
||||||
|
async-max-wait-time: 60000
|
||||||
|
# 异步线程池最大线程数
|
||||||
|
thread-executor-max-pool-size: 256
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"flow": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "numberCompareNode",
|
||||||
|
"name": "数值比较节点",
|
||||||
|
"type": "common",
|
||||||
|
"clazz": "cn.iocoder.yudao.module.rule.framework.liteflow.component.common.NumberCompareComponent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stringConditionNode",
|
||||||
|
"name": "字符串条件节点",
|
||||||
|
"type": "common",
|
||||||
|
"clazz": "cn.iocoder.yudao.module.rule.framework.liteflow.component.common.StringConditionComponent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mathCalculateNode",
|
||||||
|
"name": "数学计算节点",
|
||||||
|
"type": "common",
|
||||||
|
"clazz": "cn.iocoder.yudao.module.rule.framework.liteflow.component.action.MathCalculateComponent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dataSetNode",
|
||||||
|
"name": "数据设置节点",
|
||||||
|
"type": "common",
|
||||||
|
"clazz": "cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"chains": [
|
||||||
|
{
|
||||||
|
"name": "示例规则链",
|
||||||
|
"condition": "THEN(numberCompareNode, mathCalculateNode, dataSetNode)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for rule_rule
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `rule_rule`;
|
||||||
|
CREATE TABLE `rule_rule` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '规则ID',
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则名称',
|
||||||
|
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规则描述',
|
||||||
|
`type` tinyint NOT NULL COMMENT '规则类型:1-原子规则 2-链式规则',
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1' COMMENT '规则状态:0-禁用 1-启用',
|
||||||
|
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则配置(JSON格式)',
|
||||||
|
`chain_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'LiteFlow规则链ID',
|
||||||
|
`version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1.0.0' COMMENT '规则版本',
|
||||||
|
`sort` int DEFAULT '1' COMMENT '排序',
|
||||||
|
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `uk_chain_id` (`chain_id`,`deleted`,`tenant_id`) USING BTREE COMMENT '规则链ID唯一索引',
|
||||||
|
KEY `idx_name` (`name`) USING BTREE COMMENT '规则名称索引',
|
||||||
|
KEY `idx_status` (`status`) USING BTREE COMMENT '规则状态索引',
|
||||||
|
KEY `idx_type` (`type`) USING BTREE COMMENT '规则类型索引'
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则表';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of rule_rule
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `rule_rule` VALUES (1, '用户积分计算规则', '根据用户行为计算积分奖励', 2, 1, '{"chain":{"chainId":"userPointsChain","chainName":"用户积分计算链","expression":"THEN(numberCompareNode, mathCalculateNode, dataSetNode)","enable":true},"nodes":[{"nodeId":"numberCompareNode","nodeName":"数值比较","nodeType":"common","clazz":"cn.iocoder.yudao.module.rule.framework.liteflow.component.common.NumberCompareComponent","properties":{"leftValue":"100","operator":">","rightValue":"50"},"enable":true},{"nodeId":"mathCalculateNode","nodeName":"积分计算","nodeType":"common","clazz":"cn.iocoder.yudao.module.rule.framework.liteflow.component.action.MathCalculateComponent","properties":{"leftValue":"100","operator":"*","rightValue":"1.5","resultKey":"finalPoints","scale":"0"},"enable":true},{"nodeId":"dataSetNode","nodeName":"设置结果","nodeType":"common","clazz":"cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent","properties":{"dataKey":"userPoints","dataValue":"150","valueType":"integer"},"enable":true}]}', 'userPointsChain', '1.0.0', 1, '', '2024-01-01 00:00:00', '', '2024-01-01 00:00:00', b'0', 1);
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.example;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.RuleExecuteReqVO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则引擎使用示例
|
||||||
|
* 演示如何使用JSON配置来定义和组合原子规则
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class RuleEngineExample {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例1:用户积分计算规则
|
||||||
|
* 规则逻辑:如果用户消费金额大于100元,则积分=消费金额*1.5,否则积分=消费金额*1.0
|
||||||
|
*/
|
||||||
|
public static String buildUserPointsRule() {
|
||||||
|
RuleExecuteReqVO.RuleConfig ruleConfig = new RuleExecuteReqVO.RuleConfig();
|
||||||
|
|
||||||
|
// 设置规则链
|
||||||
|
RuleExecuteReqVO.ChainConfig chain = new RuleExecuteReqVO.ChainConfig();
|
||||||
|
chain.setChainId("userPointsChain");
|
||||||
|
chain.setChainName("用户积分计算链");
|
||||||
|
chain.setExpression("IF(amountCheck, THEN(highLevelCalc, setHighPoints), THEN(normalCalc, setNormalPoints))");
|
||||||
|
chain.setEnable(true);
|
||||||
|
ruleConfig.setChain(chain);
|
||||||
|
|
||||||
|
// 设置节点列表
|
||||||
|
List<RuleExecuteReqVO.NodeConfig> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
// 节点1:金额检查
|
||||||
|
RuleExecuteReqVO.NodeConfig amountCheckNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
amountCheckNode.setNodeId("amountCheck");
|
||||||
|
amountCheckNode.setNodeName("金额检查");
|
||||||
|
amountCheckNode.setNodeType("condition");
|
||||||
|
amountCheckNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.common.NumberCompareComponent");
|
||||||
|
Map<String, Object> amountCheckProps = new HashMap<>();
|
||||||
|
amountCheckProps.put("leftValue", "#{amount}"); // 从上下文获取
|
||||||
|
amountCheckProps.put("operator", ">");
|
||||||
|
amountCheckProps.put("rightValue", "100");
|
||||||
|
amountCheckNode.setProperties(amountCheckProps);
|
||||||
|
amountCheckNode.setEnable(true);
|
||||||
|
nodes.add(amountCheckNode);
|
||||||
|
|
||||||
|
// 节点2:高等级计算
|
||||||
|
RuleExecuteReqVO.NodeConfig highLevelCalcNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
highLevelCalcNode.setNodeId("highLevelCalc");
|
||||||
|
highLevelCalcNode.setNodeName("高等级积分计算");
|
||||||
|
highLevelCalcNode.setNodeType("common");
|
||||||
|
highLevelCalcNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.MathCalculateComponent");
|
||||||
|
Map<String, Object> highCalcProps = new HashMap<>();
|
||||||
|
highCalcProps.put("leftValue", "#{amount}");
|
||||||
|
highCalcProps.put("operator", "*");
|
||||||
|
highCalcProps.put("rightValue", "1.5");
|
||||||
|
highCalcProps.put("resultKey", "points");
|
||||||
|
highCalcProps.put("scale", "0");
|
||||||
|
highLevelCalcNode.setProperties(highCalcProps);
|
||||||
|
highLevelCalcNode.setEnable(true);
|
||||||
|
nodes.add(highLevelCalcNode);
|
||||||
|
|
||||||
|
// 节点3:普通计算
|
||||||
|
RuleExecuteReqVO.NodeConfig normalCalcNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
normalCalcNode.setNodeId("normalCalc");
|
||||||
|
normalCalcNode.setNodeName("普通积分计算");
|
||||||
|
normalCalcNode.setNodeType("common");
|
||||||
|
normalCalcNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.MathCalculateComponent");
|
||||||
|
Map<String, Object> normalCalcProps = new HashMap<>();
|
||||||
|
normalCalcProps.put("leftValue", "#{amount}");
|
||||||
|
normalCalcProps.put("operator", "*");
|
||||||
|
normalCalcProps.put("rightValue", "1.0");
|
||||||
|
normalCalcProps.put("resultKey", "points");
|
||||||
|
normalCalcProps.put("scale", "0");
|
||||||
|
normalCalcNode.setProperties(normalCalcProps);
|
||||||
|
normalCalcNode.setEnable(true);
|
||||||
|
nodes.add(normalCalcNode);
|
||||||
|
|
||||||
|
// 节点4:设置高积分结果
|
||||||
|
RuleExecuteReqVO.NodeConfig setHighPointsNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
setHighPointsNode.setNodeId("setHighPoints");
|
||||||
|
setHighPointsNode.setNodeName("设置高积分结果");
|
||||||
|
setHighPointsNode.setNodeType("common");
|
||||||
|
setHighPointsNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent");
|
||||||
|
Map<String, Object> setHighProps = new HashMap<>();
|
||||||
|
setHighProps.put("dataKey", "level");
|
||||||
|
setHighProps.put("dataValue", "VIP");
|
||||||
|
setHighProps.put("valueType", "string");
|
||||||
|
setHighPointsNode.setProperties(setHighProps);
|
||||||
|
setHighPointsNode.setEnable(true);
|
||||||
|
nodes.add(setHighPointsNode);
|
||||||
|
|
||||||
|
// 节点5:设置普通积分结果
|
||||||
|
RuleExecuteReqVO.NodeConfig setNormalPointsNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
setNormalPointsNode.setNodeId("setNormalPoints");
|
||||||
|
setNormalPointsNode.setNodeName("设置普通积分结果");
|
||||||
|
setNormalPointsNode.setNodeType("common");
|
||||||
|
setNormalPointsNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent");
|
||||||
|
Map<String, Object> setNormalProps = new HashMap<>();
|
||||||
|
setNormalProps.put("dataKey", "level");
|
||||||
|
setNormalProps.put("dataValue", "NORMAL");
|
||||||
|
setNormalProps.put("valueType", "string");
|
||||||
|
setNormalPointsNode.setProperties(setNormalProps);
|
||||||
|
setNormalPointsNode.setEnable(true);
|
||||||
|
nodes.add(setNormalPointsNode);
|
||||||
|
|
||||||
|
ruleConfig.setNodes(nodes);
|
||||||
|
|
||||||
|
return JSONUtil.toJsonPrettyStr(ruleConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 示例2:商品折扣规则
|
||||||
|
* 规则逻辑:VIP用户享受8折,普通用户满200元享受9折,否则无折扣
|
||||||
|
*/
|
||||||
|
public static String buildDiscountRule() {
|
||||||
|
RuleExecuteReqVO.RuleConfig ruleConfig = new RuleExecuteReqVO.RuleConfig();
|
||||||
|
|
||||||
|
// 设置规则链
|
||||||
|
RuleExecuteReqVO.ChainConfig chain = new RuleExecuteReqVO.ChainConfig();
|
||||||
|
chain.setChainId("discountChain");
|
||||||
|
chain.setChainName("商品折扣计算链");
|
||||||
|
chain.setExpression("IF(vipCheck, THEN(setVipDiscount), IF(amountCheck, setNormalDiscount, setNoDiscount))");
|
||||||
|
chain.setEnable(true);
|
||||||
|
ruleConfig.setChain(chain);
|
||||||
|
|
||||||
|
// 设置节点列表
|
||||||
|
List<RuleExecuteReqVO.NodeConfig> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
// 节点1:VIP检查
|
||||||
|
RuleExecuteReqVO.NodeConfig vipCheckNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
vipCheckNode.setNodeId("vipCheck");
|
||||||
|
vipCheckNode.setNodeName("VIP用户检查");
|
||||||
|
vipCheckNode.setNodeType("condition");
|
||||||
|
vipCheckNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.common.StringConditionComponent");
|
||||||
|
Map<String, Object> vipCheckProps = new HashMap<>();
|
||||||
|
vipCheckProps.put("sourceValue", "#{userLevel}");
|
||||||
|
vipCheckProps.put("operator", "equals");
|
||||||
|
vipCheckProps.put("targetValue", "VIP");
|
||||||
|
vipCheckNode.setProperties(vipCheckProps);
|
||||||
|
vipCheckNode.setEnable(true);
|
||||||
|
nodes.add(vipCheckNode);
|
||||||
|
|
||||||
|
// 节点2:金额检查
|
||||||
|
RuleExecuteReqVO.NodeConfig amountCheckNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
amountCheckNode.setNodeId("amountCheck");
|
||||||
|
amountCheckNode.setNodeName("金额检查");
|
||||||
|
amountCheckNode.setNodeType("condition");
|
||||||
|
amountCheckNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.common.NumberCompareComponent");
|
||||||
|
Map<String, Object> amountCheckProps = new HashMap<>();
|
||||||
|
amountCheckProps.put("leftValue", "#{totalAmount}");
|
||||||
|
amountCheckProps.put("operator", ">=");
|
||||||
|
amountCheckProps.put("rightValue", "200");
|
||||||
|
amountCheckNode.setProperties(amountCheckProps);
|
||||||
|
amountCheckNode.setEnable(true);
|
||||||
|
nodes.add(amountCheckNode);
|
||||||
|
|
||||||
|
// 节点3:设置VIP折扣
|
||||||
|
RuleExecuteReqVO.NodeConfig setVipDiscountNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
setVipDiscountNode.setNodeId("setVipDiscount");
|
||||||
|
setVipDiscountNode.setNodeName("设置VIP折扣");
|
||||||
|
setVipDiscountNode.setNodeType("common");
|
||||||
|
setVipDiscountNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent");
|
||||||
|
Map<String, Object> setVipProps = new HashMap<>();
|
||||||
|
setVipProps.put("dataKey", "discount");
|
||||||
|
setVipProps.put("dataValue", "0.8");
|
||||||
|
setVipProps.put("valueType", "double");
|
||||||
|
setVipDiscountNode.setProperties(setVipProps);
|
||||||
|
setVipDiscountNode.setEnable(true);
|
||||||
|
nodes.add(setVipDiscountNode);
|
||||||
|
|
||||||
|
// 节点4:设置普通折扣
|
||||||
|
RuleExecuteReqVO.NodeConfig setNormalDiscountNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
setNormalDiscountNode.setNodeId("setNormalDiscount");
|
||||||
|
setNormalDiscountNode.setNodeName("设置普通折扣");
|
||||||
|
setNormalDiscountNode.setNodeType("common");
|
||||||
|
setNormalDiscountNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent");
|
||||||
|
Map<String, Object> setNormalProps = new HashMap<>();
|
||||||
|
setNormalProps.put("dataKey", "discount");
|
||||||
|
setNormalProps.put("dataValue", "0.9");
|
||||||
|
setNormalProps.put("valueType", "double");
|
||||||
|
setNormalDiscountNode.setProperties(setNormalProps);
|
||||||
|
setNormalDiscountNode.setEnable(true);
|
||||||
|
nodes.add(setNormalDiscountNode);
|
||||||
|
|
||||||
|
// 节点5:设置无折扣
|
||||||
|
RuleExecuteReqVO.NodeConfig setNoDiscountNode = new RuleExecuteReqVO.NodeConfig();
|
||||||
|
setNoDiscountNode.setNodeId("setNoDiscount");
|
||||||
|
setNoDiscountNode.setNodeName("设置无折扣");
|
||||||
|
setNoDiscountNode.setNodeType("common");
|
||||||
|
setNoDiscountNode.setClazz("cn.iocoder.yudao.module.rule.framework.liteflow.component.action.DataSetComponent");
|
||||||
|
Map<String, Object> setNoProps = new HashMap<>();
|
||||||
|
setNoProps.put("dataKey", "discount");
|
||||||
|
setNoProps.put("dataValue", "1.0");
|
||||||
|
setNoProps.put("valueType", "double");
|
||||||
|
setNoDiscountNode.setProperties(setNoProps);
|
||||||
|
setNoDiscountNode.setEnable(true);
|
||||||
|
nodes.add(setNoDiscountNode);
|
||||||
|
|
||||||
|
ruleConfig.setNodes(nodes);
|
||||||
|
|
||||||
|
return JSONUtil.toJsonPrettyStr(ruleConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印示例配置
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
log.info("=== 用户积分计算规则配置 ===");
|
||||||
|
log.info(buildUserPointsRule());
|
||||||
|
|
||||||
|
log.info("\n=== 商品折扣规则配置 ===");
|
||||||
|
log.info(buildDiscountRule());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package cn.iocoder.yudao.module.rule.service.rule;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.rule.controller.admin.rule.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.dataobject.rule.RuleDO;
|
||||||
|
import cn.iocoder.yudao.module.rule.dal.mysql.rule.RuleMapper;
|
||||||
|
import cn.iocoder.yudao.module.rule.framework.liteflow.service.LiteFlowService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RuleServiceImpl} 的单元测试类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Import(RuleServiceImpl.class)
|
||||||
|
public class RuleServiceImplTest extends BaseDbUnitTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RuleServiceImpl ruleService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RuleMapper ruleMapper;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private LiteFlowService liteFlowService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateRule_success() {
|
||||||
|
// 准备参数
|
||||||
|
RuleCreateReqVO createReqVO = randomPojo(RuleCreateReqVO.class, o -> {
|
||||||
|
o.setName("测试规则");
|
||||||
|
o.setType(1);
|
||||||
|
o.setStatus(1);
|
||||||
|
o.setConfig(buildValidRuleConfig());
|
||||||
|
});
|
||||||
|
|
||||||
|
// mock 方法
|
||||||
|
when(liteFlowService.validateRuleConfig(anyString())).thenReturn(true);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Long ruleId = ruleService.createRule(createReqVO);
|
||||||
|
|
||||||
|
// 断言
|
||||||
|
assertNotNull(ruleId);
|
||||||
|
RuleDO rule = ruleMapper.selectById(ruleId);
|
||||||
|
assertNotNull(rule);
|
||||||
|
assertEquals("测试规则", rule.getName());
|
||||||
|
assertEquals(1, rule.getType());
|
||||||
|
assertEquals(1, rule.getStatus());
|
||||||
|
assertEquals("1.0.0", rule.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteRule_success() {
|
||||||
|
// 准备数据
|
||||||
|
RuleDO rule = randomPojo(RuleDO.class, o -> {
|
||||||
|
o.setId(1L);
|
||||||
|
o.setName("测试规则");
|
||||||
|
o.setStatus(1);
|
||||||
|
o.setChainId("testChain");
|
||||||
|
o.setConfig(buildValidRuleConfig());
|
||||||
|
});
|
||||||
|
ruleMapper.insert(rule);
|
||||||
|
|
||||||
|
// 准备参数
|
||||||
|
RuleExecuteReqVO executeReqVO = new RuleExecuteReqVO();
|
||||||
|
executeReqVO.setRuleId(1L);
|
||||||
|
Map<String, Object> contextData = new HashMap<>();
|
||||||
|
contextData.put("amount", 100);
|
||||||
|
executeReqVO.setContextData(contextData);
|
||||||
|
|
||||||
|
// mock 方法
|
||||||
|
Map<String, Object> mockResult = new HashMap<>();
|
||||||
|
mockResult.put("success", true);
|
||||||
|
mockResult.put("result", "执行成功");
|
||||||
|
when(liteFlowService.executeRule(eq("testChain"), any(), any())).thenReturn(mockResult);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
RuleExecuteRespVO result = ruleService.executeRule(executeReqVO);
|
||||||
|
|
||||||
|
// 断言
|
||||||
|
assertTrue(result.getSuccess());
|
||||||
|
assertNotNull(result.getResultData());
|
||||||
|
assertEquals("testChain", result.getChainId());
|
||||||
|
assertNotNull(result.getExecutionTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateRuleConfig_success() {
|
||||||
|
// 准备参数
|
||||||
|
String config = buildValidRuleConfig();
|
||||||
|
|
||||||
|
// mock 方法
|
||||||
|
when(liteFlowService.validateRuleConfig(config)).thenReturn(true);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Boolean result = ruleService.validateRuleConfig(config);
|
||||||
|
|
||||||
|
// 断言
|
||||||
|
assertTrue(result);
|
||||||
|
verify(liteFlowService).validateRuleConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEnableRule_success() {
|
||||||
|
// 准备数据
|
||||||
|
RuleDO rule = randomPojo(RuleDO.class, o -> {
|
||||||
|
o.setId(1L);
|
||||||
|
o.setStatus(0);
|
||||||
|
o.setChainId("testChain");
|
||||||
|
o.setConfig(buildValidRuleConfig());
|
||||||
|
});
|
||||||
|
ruleMapper.insert(rule);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
ruleService.enableRule(1L);
|
||||||
|
|
||||||
|
// 断言
|
||||||
|
RuleDO updatedRule = ruleMapper.selectById(1L);
|
||||||
|
assertEquals(1, updatedRule.getStatus());
|
||||||
|
verify(liteFlowService).loadRule(any(RuleDO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisableRule_success() {
|
||||||
|
// 准备数据
|
||||||
|
RuleDO rule = randomPojo(RuleDO.class, o -> {
|
||||||
|
o.setId(1L);
|
||||||
|
o.setStatus(1);
|
||||||
|
o.setChainId("testChain");
|
||||||
|
});
|
||||||
|
ruleMapper.insert(rule);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
ruleService.disableRule(1L);
|
||||||
|
|
||||||
|
// 断言
|
||||||
|
RuleDO updatedRule = ruleMapper.selectById(1L);
|
||||||
|
assertEquals(0, updatedRule.getStatus());
|
||||||
|
verify(liteFlowService).removeRule("testChain");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildValidRuleConfig() {
|
||||||
|
return "{\n" +
|
||||||
|
" \"chain\": {\n" +
|
||||||
|
" \"chainId\": \"testChain\",\n" +
|
||||||
|
" \"chainName\": \"测试规则链\",\n" +
|
||||||
|
" \"expression\": \"THEN(numberCompareNode, mathCalculateNode)\",\n" +
|
||||||
|
" \"enable\": true\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"nodes\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"nodeId\": \"numberCompareNode\",\n" +
|
||||||
|
" \"nodeName\": \"数值比较\",\n" +
|
||||||
|
" \"nodeType\": \"common\",\n" +
|
||||||
|
" \"clazz\": \"cn.iocoder.yudao.module.rule.framework.liteflow.component.common.NumberCompareComponent\",\n" +
|
||||||
|
" \"properties\": {\n" +
|
||||||
|
" \"leftValue\": \"100\",\n" +
|
||||||
|
" \"operator\": \">\",\n" +
|
||||||
|
" \"rightValue\": \"50\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"enable\": true\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"nodeId\": \"mathCalculateNode\",\n" +
|
||||||
|
" \"nodeName\": \"数学计算\",\n" +
|
||||||
|
" \"nodeType\": \"common\",\n" +
|
||||||
|
" \"clazz\": \"cn.iocoder.yudao.module.rule.framework.liteflow.component.action.MathCalculateComponent\",\n" +
|
||||||
|
" \"properties\": {\n" +
|
||||||
|
" \"leftValue\": \"100\",\n" +
|
||||||
|
" \"operator\": \"*\",\n" +
|
||||||
|
" \"rightValue\": \"1.5\",\n" +
|
||||||
|
" \"resultKey\": \"result\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"enable\": true\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -80,6 +80,14 @@ public class DeptController {
|
|||||||
return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));
|
return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/simple-user-dept-list"})
|
||||||
|
@Operation(summary = "获取当前用户归属部门精简信息列表", description = "只包含当前用户归属的启用部门,主要用于前端的下拉选项")
|
||||||
|
@Parameter(name = "companyId", description = "公司ID,可选参数,用于过滤指定公司下的部门", required = false, example = "1")
|
||||||
|
public CommonResult<List<DeptSimpleRespVO>> getCurrentUserDeptList(@RequestParam(value = "companyId", required = false) Long companyId) {
|
||||||
|
List<DeptDO> list = deptService.getCurrentUserDeptList(companyId);
|
||||||
|
return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(value = {"/list-company-simple", "/simple-company-list"})
|
@GetMapping(value = {"/list-company-simple", "/simple-company-list"})
|
||||||
@Operation(summary = "获取用户所属公司精简信息列表", description = "只包含被开启的部门,主要用于前端的下拉选项")
|
@Operation(summary = "获取用户所属公司精简信息列表", description = "只包含被开启的部门,主要用于前端的下拉选项")
|
||||||
public CommonResult<List<DeptSimpleRespVO>> getSimpleCompanyList() {
|
public CommonResult<List<DeptSimpleRespVO>> getSimpleCompanyList() {
|
||||||
|
|||||||
@@ -117,6 +117,15 @@ public interface DeptService {
|
|||||||
|
|
||||||
List<DeptDO> getUserCompanyList();
|
List<DeptDO> getUserCompanyList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户归属的部门列表
|
||||||
|
* 只返回当前登录用户归属的启用部门
|
||||||
|
*
|
||||||
|
* @param companyId 可选的公司ID,用于过滤指定公司下的部门
|
||||||
|
* @return 当前用户归属的部门列表
|
||||||
|
*/
|
||||||
|
List<DeptDO> getCurrentUserDeptList(Long companyId);
|
||||||
|
|
||||||
Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId);
|
Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -292,6 +292,65 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
return getDeptList(companyIds);
|
return getDeptList(companyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户归属的部门列表
|
||||||
|
* 只返回当前登录用户归属的启用部门
|
||||||
|
*
|
||||||
|
* @return 当前用户归属的部门列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<DeptDO> getCurrentUserDeptList(Long companyId) {
|
||||||
|
Set<Long> deptIds = userDeptMapper.selectValidListByUserIds(singleton(getLoginUserId()))
|
||||||
|
.stream()
|
||||||
|
.map(UserDeptDO::getDeptId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
if (CollUtil.isEmpty(deptIds)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
// 获取部门信息并过滤启用的部门
|
||||||
|
List<DeptDO> deptList = getDeptList(deptIds).stream()
|
||||||
|
.filter(dept -> CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 如果指定了公司ID,则进一步过滤属于该公司的部门
|
||||||
|
if (companyId != null) {
|
||||||
|
return deptList.stream()
|
||||||
|
.filter(dept -> isUnderCompany(dept, companyId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return deptList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断部门是否属于指定公司
|
||||||
|
*
|
||||||
|
* @param dept 部门
|
||||||
|
* @param companyId 公司ID
|
||||||
|
* @return 是否属于指定公司
|
||||||
|
*/
|
||||||
|
private boolean isUnderCompany(DeptDO dept, Long companyId) {
|
||||||
|
if (dept == null || companyId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果部门本身就是指定的公司
|
||||||
|
if (dept.getId().equals(companyId) && Boolean.TRUE.equals(dept.getIsCompany())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向上递归查找,看是否有祖先部门是指定的公司
|
||||||
|
DeptDO current = dept;
|
||||||
|
while (current != null && current.getParentId() != null && !DeptDO.PARENT_ID_ROOT.equals(current.getParentId())) {
|
||||||
|
current = getDept(current.getParentId());
|
||||||
|
if (current != null && current.getId().equals(companyId) && Boolean.TRUE.equals(current.getIsCompany())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户ID查询其归属公司及直属部门关系列表(不递归下级公司)
|
* 根据用户ID查询其归属公司及直属部门关系列表(不递归下级公司)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user