Flowable7.x学习笔记(十五)动态指定用户分配参数启动工作流程

前言

        得益于之前我们的基础工程准备,我们终于可以正式启动工作流程了,在启动之前我们需要分配一下每个用户任务的用户信息,其中有三个选择:【办理人】/【候选组】/【候选用户】,我们需要将系统中的用户ID填入作为固定参数启动工作流程,也可以填入参数在启动时使用动态表单让用户自己选择启动工作流程。

一、参数解析

① 办理人-assignee

        1)定义

        指定一个唯一的任务办理人,该用户在任务创建时就被锁定为责任人,其他用户无法认领。 可以填写固定用户名,如 flowable:assignee="john",也可以使用表达式动态传入变量,如 flowable:assignee="${userId}"。

        2)作用与行为

        优先级最高:若同时配置了 assignee 与候选组或候选用户,流程引擎会忽略候选设置,直接将任务委派给 assignee。拥有直接完成任务的权限,且该任务只会在他的待办列表中出现。

② 候选组-candidateGroups

        1)定义

        指定一个或多个用户组,格式为逗号分隔的组 ID 列表,或使用表达式 flowable:candidateGroups="${groupList}" 动态获取组集合。

        2)作用与行为

        任务创建后会出现在所有指定组中每位成员的待办列表,成员需通过 “Claim” 操作认领后才能完成任务。不指定 assignee 时,使用候选组可实现池化任务分发,适用于审批类、轮询类场景。

③ 候选用户-candidateUsers

         1)定义

        指定一个或多个具体用户 ID 列表,格式为逗号分隔,或使用表达式如 flowable:candidateUsers="${userList}" 来动态获取用户集合。 

        2)作用与行为

        任务池化:任务创建后同时出现在所有候选用户的待办列表,任一候选人认领(Claim)即可完成任务。

        细粒度控制:适用于团队扁平化或小组协作场景,明确列出可处理人员。

        并列候选:与 candidateGroups 可并存,用于覆盖不同层级的权限模型。

④ 使用场景推荐

        单一负责人场景:优先使用 assignee,确保任务责任清晰。

        

二、比较静态参数和动态参数

① 静态参数

        1)样例

        静态配置即在 BPMN XML 中直接写明目标用户或组

<userTask id="approveTask"
          name="审批任务"
          flowable:assignee="john"
          flowable:candidateGroups="managers"
          flowable:candidateUsers="mary,paul"/>

        2)优点

        配置简单:无需引擎解析表达式即可直接使用,模型直观易懂。

        无运行时开销:完全避开了表达式求值过程,对性能几乎无影响。

        3)缺点

        缺乏灵活性:一旦用户或组有变更,需修改流程定义并重新部署。

        重复配置:相似场景下多个任务需分别写入同样的固定值,易产生冗余和维护成本。

        环境耦合:对于多租户或不同部署环境,无法在不改流程的情况下实现差异化分配。

② 动态参数

        1)样例

        在 Assignee/候选字段中使用 OGNL 表达式

<userTask id="holidayApprovedTask"
          name="假期审批"
          flowable:assignee="${employee}"
          flowable:candidateGroups="${departmentGroups}"/>

         2)优点

        高度灵活:可依据流程变量、业务数据、甚至自定义后端逻辑决定办理人/组。

        模型复用:同一流程定义可在不同场景下重用,仅需在启动或执行中传入不同参数。

        无需重部署:修改分配逻辑只需传入或修改流程变量,不必重新上传 BPMN 文件。

        与监听器结合:可通过任务监听器(Task Listener)在 create 事件中进一步增强分配策略。

        3)缺点 

        调试困难:OGNL 语法及变量来源分散,不同于模型中直观的固定值,易出现“未知属性”错误。

        运行时开销:每次创建任务时都要进行表达式求值,在高并发场景需评估性能影响。

        变量依赖:若执行上下文未设置所需变量,会导致任务无法正确分配或抛错。

        已创建任务难更新:对于已激活的任务,仅改变流程变量并不会改变候选组,需要借助 API 手动更新身份链接。

③ 参数方式取舍

        结合实际项目场景中的需求,我们需要高度灵活,不用频繁部署的动态参数方式来完成启动流程,至于复杂的表达式解析,本来也来尽量通过系统方式优化一下实践,若有更好的方案请在评论区留言。

三、获取动态参数解析

        首先先定义一个流程,根据上文的规则定义动态参数。

        在成功创建流程并发布之后,可以通过【RepositoryService】的【getBpmnModel】方法获取流程模型,我们这模型里找找参数在哪里。

        首先我们找到了用户任务在processes的flowElementList里,继续深入查看用户任务。

        ok,我们这就找到想要的动态参数了,我们把这些参数抽取出来。

四、获取动态参数实现

① 后端:定义查询参数

        这里只要部署的流程定义ID即可,我们后台还是要根据ID重新查询一次的

package com.ceair.entity.request;import lombok.Data;import java.io.Serial;
import java.io.Serializable;/*** @author wangbaohai* @ClassName QueryDynamicParametersReq* @description: 查询部署流程的动态参数请求* @date 2025年04月27日* @version: 1.0.0*/
@Data
public class QueryDynamicParametersReq implements Serializable {@Serialprivate static final long serialVersionUID = 1L;/*** 流程定义ID*/private String processDefinitionId;}

② 后端:定义响应参数

        需要的是一个表单元素list,包含表单的名称,任务节点的名称,动态变量的名称(这里前端不需要但是后续要返还给后端做启动操作的时候要用到),下拉项个数限制,下拉框数据(所有用户或者所有角色)。

package com.ceair.entity.vo;import lombok.Data;import java.io.Serial;
import java.io.Serializable;
import java.util.List;/*** @author wangbaohai* @ClassName DynamicParametersVO* @description: 动态参数综合返回对象VO* @date 2025年04月27日* @version: 1.0.0*/
@Data
public class DynamicParametersVO implements Serializable {@Serialprivate static final long serialVersionUID = 1L;// 表单名称private String formName;// 动态变量节点名称private String taskName;// 动态变量名称private String dynamicVariableName;// 下拉选项个数限制private Integer selectLimit;// 下拉框数据private List<SelectDataVO> selectData;}
package com.ceair.entity.vo;import lombok.Data;import java.io.Serial;
import java.io.Serializable;/*** @author wangbaohai* @ClassName SelectDataVO* @description: 动态参数下拉选择数据对象VO* @date 2025年04月27日* @version: 1.0.0*/
@Data
public class SelectDataVO implements Serializable {@Serialprivate static final long serialVersionUID = 1L;// 下拉Keyprivate String key;// 下拉labelprivate String label;// 下拉valueprivate String value;}

③ 后端:定义一个接口服务

/*** 查询动态参数信息* <p>* 该方法用于根据给定的查询条件获取动态参数列表每个动态参数都封装在一个DynamicParametersVO对象中** @param queryDynamicParametersReq 查询动态参数的请求对象,包含了查询动态参数所需的条件和参数* @return 返回一个List集合,集合中每个元素都是一个DynamicParametersVO对象,包含了一组动态参数信息*/
List<DynamicParametersVO> queryDynamicParameters(QueryDynamicParametersReq queryDynamicParametersReq);

④ 后端:实现接口服务

1)首先参数校验

2)feign接口获取下拉数据

        一共是两个feign接口,一个是查询所有用户,一个是查询所有角色;这两个接口大家可以自行实现,如果使用我的脚手架可以参照我的代码,我是写在pm-system服务中的。

Ⅰ 定义feign客户端

package com.ceair.api;import com.ceair.entity.result.Result;
import com.ceair.entity.vo.Oauth2BasicUserVO;
import com.ceair.entity.vo.SysRoleVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;import java.util.List;/*** @author wangbaohai* @ClassName SystemFeignClient* @description: 系统设置对外Feign接口* @date 2025年04月27日* @version: 1.0.0*/
@FeignClient(name = "pm-system")
public interface SystemFeignClient {/*** 查询所有用户信息* <p>* 该方法通过POST请求处理查询所有用户信息的请求它主要用于提供一个接口,* 让客户端能够获取系统中所有用户的列表信息** @return 返回一个Result对象,其中包含用户列表(Oauth2BasicUserVO类型)如果查询成功,* 则在Result对象中包含用户列表数据;如果查询失败,则在Result对象中包含错误信息*/@PostMapping("/systemClient/api/v1/user/queryAllUsers")Result<List<Oauth2BasicUserVO>> queryAllUsers();/*** 查询所有角色信息* <p>* 该接口用于获取系统中所有的角色信息,返回一个包含多个SysRoleVO对象的列表* 主要用于用户管理、权限控制等场景** @return 包含SysRoleVO对象列表的Result对象,表示查询结果和状态*/@PostMapping("/systemClient/api/v1/user/queryAllRoles")Result<List<SysRoleVO>> queryAllRoles();}
package com.ceair.entity.result;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;import java.io.Serializable;/*** @author wangbaohai* @ClassName Result* @description: 公共响应类* @date 2024年11月20日* @version: 1.0.0*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {/*** 响应状态码*/private Integer code;/*** 响应信息*/private String message;/*** 接口是否处理成功*/private Boolean success;/*** 接口响应时携带的数据*/private T data;/*** 操作成功携带数据** @param data 数据* @param <T>  类型* @return 返回统一响应*/public static <T> Result<T> success(T data) {return new Result<>(HttpStatus.OK.value(), ("操作成功."), Boolean.TRUE, data);}/*** 操作成功不带数据** @return 返回统一响应*/public static Result<String> success() {return new Result<>(HttpStatus.OK.value(), ("操作成功."), Boolean.TRUE, (null));}/*** 操作成功携带数据** @param message 成功提示消息* @param data    成功携带数据* @param <T>     类型* @return 返回统一响应*/public static <T> Result<T> success(String message, T data) {return new Result<>(HttpStatus.OK.value(), message, Boolean.TRUE, data);}/*** 操作失败返回** @param message 成功提示消息* @param <T>     类型* @return 返回统一响应*/public static <T> Result<T> error(String message) {return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, Boolean.FALSE, (null));}/*** 操作失败返回** @param code    错误码* @param message 成功提示消息* @param <T>     类型* @return 返回统一响应*/public static <T> Result<T> error(Integer code, String message) {return new Result<>(code, message, Boolean.FALSE, (null));}/*** oauth2 问题** @param message 失败提示消息* @param data    具体的错误信息* @param <T>     类型* @return 返回统一响应*/public static <T> Result<T> oauth2Error(Integer code, String message, T data) {return new Result<>(code, message, Boolean.FALSE, data);}/*** oauth2 问题** @param message 失败提示消息* @param data    具体的错误信息* @param <T>     类型* @return 返回统一响应*/public static <T> Result<T> oauth2Error(String message, T data) {return new Result<>(HttpStatus.UNAUTHORIZED.value(), message, Boolean.FALSE, data);}}
package com.ceair.entity.vo;import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/*** @author wangbaohai* @ClassName Oauth2BasicUserVO* @description: 用户信息前端交互层对象* @date 2025年02月16日* @version: 1.0.0*/
@Data
public class Oauth2BasicUserVO implements Serializable {/*** id*/private Long id;/*** 用户名、昵称*/private String name;/*** 账号*/private String account;/*** 密码*/private String password;/*** 手机号*/private String mobile;/*** 邮箱*/private String email;/*** 头像地址*/private String avatarUrl;/*** 是否已删除*/private Boolean deleted;/*** 用户来源*/private String sourceFrom;/*** 创建人id*/private Long creatorId;/*** 创建人名称*/private String creatorName;/*** 创建时间*/private LocalDateTime createTime;/*** 修改人id*/private Long modifierId;/*** 修改人名称*/private String modifierName;/*** 修改时间*/private LocalDateTime modifyTime;/*** 版本号*/private Integer recordVersion;/*** 扩展字段2*/private String attribute2;/*** 扩展字段3*/private String attribute3;/*** 扩展字段4*/private String attribute4;/*** 扩展字段5*/private String attribute5;/*** 扩展字段1*/private String attribute1;}
package com.ceair.entity.vo;import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/*** @author wangbaohai* @ClassName SysRoleVO* @description: 系统角色表VO* @date 2025年02月26日* @version: 1.0.0*/
@Data
public class SysRoleVO implements Serializable {/*** 角色ID*/private Long id;/*** 角色名*/private String roleName;/*** 0:启用,1:删除*/private Boolean deleted;/*** 排序*/private Integer sort;/*** 创建人id*/private Long creatorId;/*** 创建人名称*/private String creatorName;/*** 创建时间*/private LocalDateTime createTime;/*** 修改人id*/private Long modifierId;/*** 修改人名称*/private String modifierName;/*** 修改时间*/private LocalDateTime modifyTime;/*** 版本号*/private Integer recordVersion;/*** 扩展字段2*/private String attribute2;/*** 扩展字段3*/private String attribute3;/*** 扩展字段4*/private String attribute4;/*** 扩展字段5*/private String attribute5;/*** 扩展字段1*/private String attribute1;}
Ⅱ 实现feign接口

        先在server服务模块引入api模块

<!-- system-api -->
<dependency>
    <groupId>com.ceair</groupId>
    <artifactId>system-api</artifactId>
    <version>${project.version}</version>
</dependency>

        然后就是实现了,这里需要注意,响应的实体都需要使用api模块中的,避免数据转换错误。

package com.ceair.feignController;import com.ceair.entity.Oauth2BasicUser;
import com.ceair.entity.SysRole;
import com.ceair.entity.result.Result;
import com.ceair.entity.vo.Oauth2BasicUserVO;
import com.ceair.entity.vo.SysRoleVO;
import com.ceair.service.IOauth2BasicUserService;
import com.ceair.service.ISysRoleService;
import com.ceair.utils.structMapper.Oauth2BasicUserStructMapper;
import com.ceair.utils.structMapper.SysRoleStructMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author wangbaohai* @ClassName Oauth2BasicUserFeignController* @description: 用户管理相关feign接口* @date 2025年04月27日* @version: 1.0.0*/
@RestController
@RequestMapping("/systemClient/api/v1/user")
@RequiredArgsConstructor
@Slf4j
public class Oauth2BasicUserFeignController {private final IOauth2BasicUserService oauth2BasicUserService;private final ISysRoleService sysRoleService;/*** 查询所有用户的接口* 该方法通过POST请求查询系统中所有未删除的用户信息,并以列表形式返回* 使用了Oauth2BasicUserStructMapper来转换用户实体类到API视图对象** @return 返回一个Result对象,其中包含用户信息列表如果查询失败,返回错误信息*/@PostMapping("/queryAllUsers")public Result<List<Oauth2BasicUserVO>> queryAllUsers() {try {// 查询所有未删除的用户信息List<Oauth2BasicUser> oauth2BasicUsers = oauth2BasicUserService.lambdaQuery().eq(Oauth2BasicUser::getDeleted, false).list();// 使用 Oauth2BasicUserStructMapper 转换输出结果List<Oauth2BasicUserVO> oauth2BasicUserVOS =oauth2BasicUsers.stream().map(Oauth2BasicUserStructMapper.INSTANCE::toApiVO).toList();// 返回成功结果return Result.success(oauth2BasicUserVOS);} catch (Exception e) {// 记录错误日志并返回错误结果log.error("查询所有用户失败,失败原因:{}", e.getMessage(), e);return Result.error("查询所有用户失败,失败原因:" + e.getMessage());}}/*** 处理查询所有角色的POST请求* <p>* 该方法通过调用sysRoleService查询所有未删除的角色信息,并使用SysRoleStructMapper将查询结果转换为API输出格式* 如果查询过程中发生异常,将记录错误日志并返回错误结果** @return 返回一个Result对象,其中包含查询到的角色列表如果查询失败,返回错误信息*/@PostMapping("/queryAllRoles")public Result<List<SysRoleVO>> queryAllRoles() {try {// 查询所有未删除的角色信息List<SysRole> sysRoles = sysRoleService.lambdaQuery().eq(SysRole::getDeleted, false).list();// 使用 SysRoleStructMapper 转换输出结果List<SysRoleVO> sysRoleVOS =sysRoles.stream().map(SysRoleStructMapper.INSTANCE::toApiVO).toList();// 返回成功结果return Result.success(sysRoleVOS);} catch (Exception e) {// 记录错误日志并返回错误结果log.error("查询所有角色失败,失败原因:{}", e.getMessage(), e);return Result.error("查询所有角色失败,失败原因:" + e.getMessage());}}}
Ⅲ 放开feign接口鉴权

        注意需要配置资源服务器的放行设置,把feign接口全部放开,先不鉴权,不然heaer里要放token,这里的方案还不是最优解,后续我再优化吧。所有资源服务器配置我是放在common里的。

Ⅳ 流程服务引入feign接口

主启动类里添加feign接口指定路径

使用feign

3)获取流程 Process

4) 获取 flowElements

5)获取所有 UserTask

6)从UserTask中获取信息封装响应参数

        这里的主要思路就是获取到所有用户任务的信息,每个参数就是对应前端的一个表单数据行,这个表单而且应该都是下拉选项菜单,并且候选用户和候选组都是多个的,所有需要限制用户选择不超过流程定义bpmnjs中属性菜单里的参数个数,并且保留变量名称,虽然变量名称前端用不到,但是后续再返回给后端的时候,启动流程的时候需要知道用户的选择数据需要放到哪个参数中去。

完整代码:

/*** 查询流程动态参数。* <p>* 该方法根据指定的流程定义ID,查询并生成与用户任务相关的动态参数表单数据,供前端使用。* 动态参数表单包括用户和角色的选择数据,用于在流程中分配任务。* <p>* 参数说明:** @param queryDynamicParametersReq 请求对象,包含流程定义ID(processDefinitionId)。*                                  - 不可为空。*                                  - processDefinitionId 必须为有效的非空字符串。*                                  <p>*                                  返回值:* @return 返回一个 DynamicParametersVO 对象列表,包含所有用户任务的动态参数表单数据。* 如果发生异常,则不会返回值,而是抛出相应的异常。* <p>* 异常说明:* - IllegalArgumentException: 当请求对象或流程定义ID无效时抛出。* - BusinessException: 当业务逻辑出现问题(如远程调用失败、流程定义元素为空等)时抛出。* - Exception: 捕获其他未知异常,并将其包装为 BusinessException 抛出。*/
@Override
public List<DynamicParametersVO> queryDynamicParameters(QueryDynamicParametersReq queryDynamicParametersReq) {try {// 初始化动态参数列表List<DynamicParametersVO> dynamicParametersVOList = new ArrayList<>();// 参数校验:确保请求对象不为空if (queryDynamicParametersReq == null) {log.error("获取流程动态参数失败,原因:请求对象不能为空");throw new IllegalArgumentException("获取流程动态参数失败,原因:请求对象不能为空");}String processDefinitionId = queryDynamicParametersReq.getProcessDefinitionId();// 参数校验:确保流程定义ID不为空或空字符串if (StringUtils.isBlank(processDefinitionId)) {log.error("获取流程动态参数失败,原因:流程定义ID不能为空或空字符串,流程定义ID:{}", processDefinitionId);throw new IllegalArgumentException("获取流程动态参数失败,原因:流程定义ID不能为空或空字符串");}// 根据流程定义ID查询流程定义元素BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);// 调用远程接口获取所有用户信息Result<List<Oauth2BasicUserVO>> userResult = systemFeignClient.queryAllUsers();if (!userResult.getSuccess() || userResult.getData() == null) {log.error("获取流程动态参数失败,原因:获取所有用户信息失败,失败原因:{}", userResult.getMessage());throw new BusinessException("获取流程动态参数失败,原因:获取所有用户信息失败,失败原因:" + userResult.getMessage());}List<SelectDataVO> userSelectDataList = convertToSelectDataVOList(userResult.getData());// 调用远程接口获取所有角色信息Result<List<SysRoleVO>> roleResult = systemFeignClient.queryAllRoles();if (!roleResult.getSuccess() || roleResult.getData() == null) {log.error("获取流程动态参数失败,原因:获取所有角色信息失败,失败原因:{}", roleResult.getMessage());throw new BusinessException("获取流程动态参数失败,原因:获取所有角色信息失败,失败原因:" + roleResult.getMessage());}List<SelectDataVO> roleSelectDataList = convertToSelectDataVOList(roleResult.getData());// 从 bpmnModel 获取主流程元素Process process = bpmnModel.getMainProcess();if (process == null) {log.error("获取流程动态参数失败,原因:流程定义元素为空,流程定义ID:{}", processDefinitionId);throw new BusinessException("获取流程动态参数失败,原因:流程定义元素为空,流程定义ID:" + processDefinitionId);}// 获取流程中的所有 FlowElement 元素Collection<FlowElement> flowElements = process.getFlowElements();// 处理所有用户任务,生成动态参数表单数据flowElements.stream().filter(Objects::nonNull).filter(flowElement -> flowElement instanceof UserTask).map(UserTask.class::cast).forEach(userTask -> processUserTask(userTask, userSelectDataList, roleSelectDataList,dynamicParametersVOList));// 返回动态参数表单数据return dynamicParametersVOList;} catch (IllegalArgumentException e) {log.error("获取流程动态参数失败,原因:参数错误", e);throw new BusinessException("获取流程动态参数失败,原因:参数错误", e);} catch (BusinessException e) {log.error("获取流程动态参数失败,原因:业务异常", e);throw new BusinessException("获取流程动态参数失败,原因:业务异常", e);} catch (Exception e) {log.error("获取流程动态参数失败,原因:未知异常", e);throw new BusinessException("获取流程动态参数失败,原因:未知异常", e);}
}/*** 检查字符串是否以特殊模式包围* 该方法使用正则表达式判断字符串是否以"${"开始并以"}"结束* 这种模式常用于变量或表达式的包围,例如"${variable}"** @param input 待检查的字符串* @return 如果字符串符合模式则返回true,否则返回false* 空字符串或null也会返回false*/
private boolean isSurroundedRegex(String input) {// 校验输入是否为非空字符串if (input == null || input.isEmpty()) {return false; // 明确空字符串或 null 的处理方式}// 使用非贪婪匹配优化正则表达式性能return input.matches("\\$\\{.*?\\}");
}/*** 将给定的VO列表转换为SelectDataVO列表,用于在选择框中显示* 此方法主要处理的是将不同类型的VO对象转换为SelectDataVO对象,以便在界面上提供选择数据* 它特别关注于处理Oauth2BasicUserVO和SysRoleVO类型的对象,并根据条件判断是否将其包含在最终结果中** @param voList 含有各种类型VO对象的列表,可以为空* @return 转换后的SelectDataVO列表,用于在选择框中显示*/
private List<SelectDataVO> convertToSelectDataVOList(List<?> voList) {return voList.stream().filter(Objects::nonNull).map(vo -> {// 检查vo对象是否为Oauth2BasicUserVO类型,并且符合条件if (vo instanceof Oauth2BasicUserVO oauth2BasicUserVO && isEligible(oauth2BasicUserVO)) {// 创建并返回一个SelectDataVO对象,用于显示用户信息return createSelectDataVO(oauth2BasicUserVO.getId().toString(), oauth2BasicUserVO.getName());} else if (vo instanceof SysRoleVO sysRoleVO && isEligible(sysRoleVO)) {// 创建并返回一个SelectDataVO对象,用于显示角色信息return createSelectDataVO(sysRoleVO.getId().toString(), sysRoleVO.getRoleName());}// 对于不符合条件的vo对象,返回nullreturn null;}).filter(Objects::nonNull).toList();
}/*** 检查用户是否符合资格条件* <p>* 此方法用于确定提供的用户信息是否满足特定条件主要检查用户对象是否非空,* 用户ID是否已分配,以及用户名是否已提供这些检查确保在后续的认证流程中,* 用户信息是有效且可以安全使用的** @param oauth2BasicUserVO 用户信息对象,包含用户的基本信息* @return 如果用户符合所有资格条件,则返回true;否则返回false*/
private boolean isEligible(Oauth2BasicUserVO oauth2BasicUserVO) {// 确保用户信息对象不为空return oauth2BasicUserVO != null// 确保用户的ID已分配,即ID不为空&& oauth2BasicUserVO.getId() != null// 确保用户名已提供,即用户名非空且不只包含空白字符&& !StringUtils.isEmpty(oauth2BasicUserVO.getName());
}/*** 判断一个系统角色对象是否符合资格条件* <p>* 本方法用于检查系统角色对象是否非空、角色ID是否非空以及角色名称是否非空* 这些检查确保了角色对象在后续操作中具有必要的信息,从而避免空指针异常等问题** @param sysRoleVO 系统角色对象,包含角色相关信息* @return 如果角色对象非空、角色ID非空且角色名称非空,则返回true;否则返回false*/
private boolean isEligible(SysRoleVO sysRoleVO) {// 检查系统角色对象是否非空return sysRoleVO != null// 检查角色ID是否非空&& sysRoleVO.getId() != null// 检查角色名称是否非空&& !StringUtils.isEmpty(sysRoleVO.getRoleName());
}/*** 创建SelectDataVO对象* 此方法用于初始化一个SelectDataVO实例,并设置其关键属性* 选择数据视图对象(SelectDataVO)是用于在界面上展示选择框的数据结构* 它需要关键(key)、标签(label)和值(value)三个属性,其中值通常与键相同** @param key   选择项的唯一标识符,用于后端识别选择项* @param label 选择项在界面上显示的文本* @return 返回一个已初始化的SelectDataVO对象*/
private SelectDataVO createSelectDataVO(String key, String label) {SelectDataVO selectDataVO = new SelectDataVO();selectDataVO.setKey(key);selectDataVO.setLabel(label);selectDataVO.setValue(key);return selectDataVO;
}/*** 处理用户任务的分配和动态参数设置** @param userTask                用户任务对象,包含任务的分配信息* @param userSelectDataList      用户选择数据列表,用于动态表单的设置* @param roleSelectDataList      角色选择数据列表,用于动态表单的设置* @param dynamicParametersVOList 动态参数列表,用于存储生成的动态表单参数*/
private void processUserTask(UserTask userTask, List<SelectDataVO> userSelectDataList,List<SelectDataVO> roleSelectDataList,List<DynamicParametersVO> dynamicParametersVOList) {// 获取任务名称String taskName = userTask.getName();// 获取 assignee / candidateUsers / candidateGroupsString assignee = userTask.getAssignee();List<String> candidateUsers = userTask.getCandidateUsers();List<String> candidateGroups = userTask.getCandidateGroups();// 这里虽然 候选人/候选组 的数量只能有一个,但是可以设置多个,所以这里需要判断一下,提示用户在设计的时候只能填一个参数if (candidateUsers.size() > 1 || candidateGroups.size() > 1) {log.error("获取动态参数不正确,请修改流程定义属性,办理人/候选组/候选用户的参数只能有一个");throw new BusinessException("获取动态参数不正确,请修改流程定义属性,办理人/候选组/候选用户的参数只能有一个");}// 处理 assignee 设置动态表单if (!StringUtils.isEmpty(assignee) && isSurroundedRegex(assignee)) {// 创建并添加动态参数对象到列表中DynamicParametersVO dynamicParametersVO = createDynamicParametersVO(taskName, assignee, 1,userSelectDataList, "办理人");dynamicParametersVOList.add(dynamicParametersVO);}// 处理 candidateUsers 设置动态表单processCandidates(candidateUsers, taskName, userSelectDataList, dynamicParametersVOList, "候选人");// 处理 candidateGroups 设置动态表单processCandidates(candidateGroups, taskName, roleSelectDataList, dynamicParametersVOList, "候选组");
}/*** 处理候选变量字符串列表,构建动态参数对象并添加到动态参数列表中** @param candidates              候选变量字符串列表* @param taskName                任务名称,用于动态参数构建* @param selectDataList          选择数据列表,用于动态参数构建* @param dynamicParametersVOList 动态参数列表,处理后将添加新的动态参数对象* @param label                   动态参数的标签,用于动态参数构建*/
private void processCandidates(List<String> candidates, String taskName, List<SelectDataVO> selectDataList,List<DynamicParametersVO> dynamicParametersVOList, String label) {// 构建变量名称字符串,用于后续动态参数对象的创建StringBuilder variableNameBuilder = new StringBuilder();// 初始化选择限制计数器,用于记录有效候选变量的数量int selectLimit = 0;// 遍历候选变量列表for (String candidate : candidates) {// 检查候选变量是否非空且符合正则表达式包围的条件if (!StringUtils.isEmpty(candidate) && isSurroundedRegex(candidate)) {// 如果变量名称构建器非空,追加逗号分隔符if (!variableNameBuilder.isEmpty()) {variableNameBuilder.append(",");}// 将符合条件的候选变量追加到变量名称构建器中variableNameBuilder.append(candidate);// 增加选择限制计数selectLimit++;}}// 如果存在有效候选变量,创建动态参数对象并添加到列表中,并且不限制多选个数if (selectLimit > 0) {DynamicParametersVO dynamicParametersVO = createDynamicParametersVO(taskName,variableNameBuilder.toString(), 0, selectDataList, label);dynamicParametersVOList.add(dynamicParametersVO);}
}/*** 创建DynamicParametersVO对象的方法* 该方法用于根据传入的任务名称、动态变量名称、选择限制和选择数据列表来构建一个DynamicParametersVO对象** @param taskName            任务名称,用于标识特定的任务* @param dynamicVariableName 动态变量名称,用于标识动态参数* @param selectLimit         选择限制,定义用户可以选择的项数限制* @param selectDataList      选择数据列表,包含用户可以选择的数据项* @param label               动态参数的标签,用于描述动态参数* @return 返回构建好的DynamicParametersVO对象*/
private DynamicParametersVO createDynamicParametersVO(String taskName, String dynamicVariableName,int selectLimit, List<SelectDataVO> selectDataList,String label) {// 创建DynamicParametersVO对象实例DynamicParametersVO dynamicParametersVO = new DynamicParametersVO();// 设置表单名称,由任务名称和动态变量名称组合而成,用于唯一标识该动态参数dynamicParametersVO.setFormName(taskName + "-" + label);// 设置任务名称dynamicParametersVO.setTaskName(taskName);// 设置动态变量名称dynamicParametersVO.setDynamicVariableName(dynamicVariableName);// 设置选择限制dynamicParametersVO.setSelectLimit(selectLimit);// 设置选择数据列表dynamicParametersVO.setSelectData(selectDataList);// 返回构建好的DynamicParametersVO对象return dynamicParametersVO;
}

⑤ 后端:创建接口

/*** 查询流程定义的动态参数* <p>* 此方法接收一个QueryDynamicParametersReq对象作为请求体,用于指定查询条件* 使用Spring Security的注解进行权限控制,只有拥有特定权限的用户才能访问此方法* 它调用actReProcdefService的queryDynamicParameters方法来获取动态参数列表* 如果调用成功,返回包含DynamicParametersVO列表的Result对象* 如果调用失败,返回一个错误的Result对象,包含错误信息** @param queryDynamicParametersReq 查询流程动态参数的请求对象* @return 包含动态参数列表的Result对象,或包含错误信息的Result对象*/
@PreAuthorize("hasAnyAuthority('/api/v1/actReProcdef/queryDynamicParameters')")
@Parameter(name = "queryDynamicParametersReq", description = "查询不俗流程动态参数", required = true)
@Operation(summary = "查询流程定义动态参数")
@PostMapping("/queryDynamicParameters")
public Result<List<DynamicParametersVO>> queryDynamicParameters(@RequestBody QueryDynamicParametersReqqueryDynamicParametersReq) {try {// 调用服务层方法查询动态参数return Result.success(actReProcdefService.queryDynamicParameters(queryDynamicParametersReq));} catch (Exception e) {// 记录查询失败的日志log.error("查询流程定义动态参数失败 具体原因为 : {}", e.getMessage());// 返回查询失败的错误信息return Result.error("查询流程定义动态参数失败,失败原因:" + e.getMessage());}
}

⑥ 前端:定义请求和结果数据类型

// 查询流程启动动态参数请求对象
export interface QueryDynamicParametersReq {processDefinitionId: string // 流程定义ID
}// 动态参数综合返回对象 VO
export interface DynamicParametersVO {formName: string // 表单名称taskName: string // 动态变量节点名称dynamicVariableName: string // 动态变量名称selectLimit: number // 下拉选项个数限制(Java Integer → number)selectData: SelectDataVO[]// 下拉框数据列表(Java List<SelectDataVO> → SelectDataVO[])dynamicVariableValue: string[]
}// 动态参数下拉选择数据对象 VO
export interface SelectDataVO {key: string // 下拉 Key(Java String → string)label: string // 下拉 Labelvalue: string // 下拉 Value
}

⑦ 前端:封装请求接口

// 查询动态参数
export function queryDynamicParameters(data: QueryDynamicParametersReq) {return request.post<any>({url: '/pm-process/api/v1/actReProcdef/queryDynamicParameters',data,})
}

⑧ 前端:创建按钮和动态表单

<el-button v-hasButton="`btn.actReProcdef.queryDynamicParameters`" type="primary" @click="onStart(scope.row)">
  启动
</el-button>

<!-- 启动流程 弹出框 -->
<el-dialog v-model="showStart" title="启动流程" width="30%"><el-form ref="dynamicFormRef" :model="dynamicForm"><el-form-itemv-for="(item, index) in dynamicForm.data":key="index":label="item.formName":prop="`data.${index}.dynamicVariableValue`":rules="{ required: true, message: '请选择选项', trigger: ['change', 'blur'] }"><el-selectv-model="item.dynamicVariableValue"multiple placeholder="请选择":multiple-limit="item.selectLimit > 0 ? item.selectLimit : undefined"><el-optionv-for="one in item.selectData":key="one.value":label="one.label":value="one.value"/></el-select></el-form-item></el-form><template #footer><div class="dialog-footer"><el-button @click="showStart = false">取消</el-button><el-button v-hasButton="`btn.actReProcdef.startProcess`" type="primary" @click="onSaveDynamicParameters">确认</el-button></div></template>
</el-dialog>

⑨ 前端:创建打开对话框方法

// 定义响应式数据 showStart 表示是否显示流程定义的启动对话框
const showStart = ref(false)
// 定义响应式数据 DynamicParametersData 表示动态参数数据
const dynamicParametersData = ref<DynamicParametersVO[]>([])
// 定义 动态参数表单数据
const dynamicForm = reactive<{ data: DynamicParametersVO[] }>({data: [], // 显式初始化 data 属性为一个空数组
})
// 收集 动态参数表单 实例
const dynamicFormRef = ref()
// 定义响应式数据收集 processDefinitionId 表示流程定义ID
const processDefinitionId = ref('')/*** 异步函数:在流程开始时调用* 该函数负责查询流程定义的动态参数,并在成功时显示流程启动对话框* @param data 流程定义的详细信息,用于获取流程定义ID*/
async function onStart(data: ActReProcdefVO) {try {// 组装查询参数,包括流程定义 IDconst param: QueryDynamicParametersReq = {processDefinitionId: data.id,}// 调用后端接口获取流程定义的动态参数const result: any = await queryDynamicParameters(param)// 判断查询结果是否成功if (result.success && result.code === 200) {// 如果成功,则更新流程定义的动态参数dynamicParametersData.value = result.data// 将数据放入 dynamicForm 表单数据中dynamicForm.data = dynamicParametersData.value// 收集流程部署定义IDprocessDefinitionId.value = data.id}// 打开 流程启动对话框showStart.value = true}catch (error) {// 捕获异常并提取错误信息let errorMessage = '未知错误'if (error instanceof Error) {errorMessage = error.message}// 显示操作失败的错误提示信息ElMessage({message: `查询流程动态参数失败: ${errorMessage || '未知错误'}`,type: 'error',})}
}

⑩ 添加按钮和权限

演示

        可以看到,这里两个用户任务的 办理人。候选组。候选用户都动态的展示出来了,并且对应的下拉数据也是有的,选择好之后数据会保存到 【DynamicParametersVO】的【dynamicVariableValue】数组数据中,传递给后台用于启动。

五、启动流程

① 后端:定义启动参数

package com.ceair.entity.request;import lombok.Data;import java.io.Serial;
import java.io.Serializable;
import java.util.List;/*** @author wangbaohai* @ClassName SaveDynamicParametersReq* @description: 启动部署流程动态参数请求对象* @date 2025年04月28日* @version: 1.0.0*/
@Data
public class StartProcessReq implements Serializable {@Serialprivate static final long serialVersionUID = 1L;// 流程定义IDprivate String processDefinitionId;// 动态参数列表private List<StartProcessDynamicParametersReq> dynamicParameters;}
package com.ceair.entity.request;import lombok.Data;import java.io.Serial;
import java.io.Serializable;
import java.util.List;/*** @author wangbaohai* @ClassName StartProcessDynamicParametersReq* @description: 启动流程动态参数请求对象* @date 2025年04月28日* @version: 1.0.0*/
@Data
public class StartProcessDynamicParametersReq implements Serializable {@Serialprivate static final long serialVersionUID = 1L;// 动态变量名称private String dynamicVariableName;// 动态变量值private List<String> dynamicVariableValue;}

② 后端:定义一个接口服务

/*** 启动流程方法* 该方法接收一个启动流程请求对象,并尝试启动一个新的流程实例* 主要用途是作为流程管理的一部分,允许外部系统或用户通过提供特定的请求参数来启动定义好的流程** @param startProcessReq 启动流程所需的请求对象,包含启动流程所需的所有参数和信息* @return 返回一个布尔值,表示流程是否成功启动true表示成功启动,false表示启动失败*/
Boolean startProcess(StartProcessReq startProcessReq);

③ 后端:实现接口服务

        这里的主要思路还是先判空,然后将参数中的动态变量名称去除首位包围的【${}】符号,再传入参数中的动态变量值组装list,放到map中作为启动参数。其中需要注意,办理人只能有一个参数,但是其他都是可以设置list到参数中的。

/*** 启动流程实例的方法** @param startProcessReq 启动流程的请求对象,包含流程定义ID和动态参数等信息* @return 流程启动成功返回true,否则抛出异常* @throws BusinessException 当流程启动失败时抛出业务异常*/
@Override
public Boolean startProcess(StartProcessReq startProcessReq) {try {// 参数校验:确保请求对象不为空if (startProcessReq == null) {log.error("启动流程失败,原因:请求对象不能为空");throw new IllegalArgumentException("启动流程失败,原因:请求对象不能为空");}String processDefinitionId = startProcessReq.getProcessDefinitionId();// 参数校验:确保流程定义ID不为空或空字符串if (StringUtils.isBlank(processDefinitionId)) {log.error("启动流程失败,原因:流程定义ID不能为空或空字符串,流程定义ID:{}", processDefinitionId);throw new IllegalArgumentException("启动流程失败,原因:流程定义ID不能为空或空字符串");}// 初始化 流程参数MapMap<String, Object> vars = new HashMap<>();// 获取流程动态参数List<StartProcessDynamicParametersReq> dynamicParameters = startProcessReq.getDynamicParameters();dynamicParameters.stream().filter(Objects::nonNull).forEach(dynamicParameter -> {// 获取 动态参数名称String dynamicVariableName = dynamicParameter.getDynamicVariableName();if (!StringUtils.isBlank(dynamicVariableName)) {// dynamicVariableName 去除首尾的 ${} 符号,使用正则表达式String dynamicVariableKey = dynamicVariableName.replaceAll("^\\$\\{", "").replaceAll("\\}$", "");// 获取动态参数值List<String> dynamicVariableValue = dynamicParameter.getDynamicVariableValue();// 设置参数if (dynamicVariableValue.size() == 1) {vars.put(dynamicVariableKey, dynamicVariableValue.get(0));} else if (dynamicVariableValue.size() > 1) {vars.put(dynamicVariableKey, dynamicVariableValue);} else {vars.put(dynamicVariableKey, null);}}});// 获取当前用户信息UserInfo userInfo = userInfoUtils.getUserInfoFromAuthentication();// 设置流程发起人,获取当前用户IDif (userInfo != null) {identityService.setAuthenticatedUserId(String.valueOf(userInfo.getId()));}// 指定ID和动态参数启动流程runtimeService.startProcessInstanceById(processDefinitionId, vars);return true;} catch (IllegalArgumentException e) {log.error("启动流程失败,原因:参数错误", e);throw new BusinessException("启动流程失败,原因:参数错误", e);} catch (BusinessException e) {log.error("启动流程失败,原因:业务异常", e);throw new BusinessException("启动流程失败,原因:业务异常", e);} catch (Exception e) {log.error("启动流程失败,原因:未知异常", e);throw new BusinessException("启动流程失败,原因:未知异常", e);}
}

④ 前端:定义请求和响应

// 启动流程请求对象
export interface StartProcessReq {processDefinitionId: stringdynamicParameters: StartProcessDynamicParametersReq[]
}// 启动流程动态参数请求对象
export interface StartProcessDynamicParametersReq {dynamicVariableName: stringdynamicVariableValue: string[]
}

⑤ 前端:封装请求接口

// 带着动态参数启动流程实例
export function startProcess(data: StartProcessReq) {return request.post<any>({url: '/pm-process/api/v1/actReProcdef/startProcess',data,})
}

⑥ 前端:创建按钮

        参照本文的第四步的第⑧小步,都已经画好了,如果还是不清楚,可以在本文的后记找找到完成代码仓库地址和仓库分支。

⑦ 前端:创建开始流程方法

/*** 保存动态参数并启动流程的异步函数* 此函数首先验证动态表单的数据有效性,然后组装请求参数,最后调用后端接口启动流程*/
async function onSaveDynamicParameters() {try {// 先执行表单校验成功才能执行后续保存操作await dynamicFormRef.value.validate()// 组装查询参数,包括流程定义 ID和动态参数const startProcessDynamicParametersReq = ref<StartProcessDynamicParametersReq[]>([])dynamicForm.data.forEach((item: DynamicParametersVO) => {startProcessDynamicParametersReq.value.push({dynamicVariableName: item.dynamicVariableName,dynamicVariableValue: item.dynamicVariableValue,})})const startProcessReq: StartProcessReq = {processDefinitionId: processDefinitionId.value,dynamicParameters: startProcessDynamicParametersReq.value,}// 调用后端接口启动流程实例const result: any = await startProcess(startProcessReq)// 判断执行结果是否成功if (result.success && result.code === 200) {// 如果成功,则更新流程定义的动态参数ElMessage({message: '启动流程成功',type: 'success',})// 关闭对话框showStart.value = false}else {// 提示操作失败的错误提示信息ElMessage({message: '启动流程失败',type: 'error',})}}catch (error) {// 捕获异常并提取错误信息let errorMessage = '请检查'if (error instanceof Error) {errorMessage = error.message}// 显示操作失败的错误提示信息ElMessage({message: `必填项校验失败: ${errorMessage || '未知错误'}`,type: 'error',})}
}

⑧ 添加按钮和权限

演示

        首先我们下拉选择参数

        然后我们在后端先debug一下,可以看到将要传入的参数和前台选择的是对的上的,这里参数都用的下拉框数据的ID,后续查待办任务会更准确一些。

        验证我们的接口是执行ok的

        最后再验证一下数据库,因为我们指定了办理人,这个最优先,所有办理人会直接使用 assignee到数据库,验证是ok的,数据对的上用户任务1的办理人assignee1参数。

后记

如果本文的样例代码各位觉得有哪里不全的话,请到本专栏的第一篇文章,最后有仓库地址。

本文的后端分支是 process-8

本文的前端分支是 process-10

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79347.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣hot100——98.验证二叉搜索树

题目链接&#xff1a;98. 验证二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 首先列举一个错误代码 class Solution { public:bool isValidBST(TreeNode* root) {if(rootnullptr) return true;if(root->right){if(root->right->val<root->val) return f…

数据结构学习之顺序表

在C语言学习到一定阶段之后&#xff0c;接下来我们就进入到了数据结构的部分内容。 目录 数据结构与线性表 顺序表 顺序表分类&#xff1a; 接下来我们要写一段代码实现动态顺序表。 首先我们需要准备三个文件&#xff1a; 1.接下来我们要定义一个数据表 2.当创建号我们的…

C# wpf

学习网址&#xff1a;控件的父类们 - WPF中文网 - 从小白到大佬 控件的父类&#xff1a; 由此我们可以得出结论&#xff0c;控件的父类们(准确的说&#xff0c;应该叫父类的父类的父类)&#xff0c;至少有如下几个类型&#xff1a; DispatcherObjectDependencyObjectVisualU…

JavaEE-多线程实战02

接上 多线程编程实战01 第三个多线程程序 package thread.test;//定义了一个叫MyThread3的类&#xff0c;实现了Runable接口,所以它必须重写run()方法 class MyThread3 implements Runnable {Overridepublic void run() {//线程执行的具体内容//进入一个无限循环&#xff0c;…

【无报错,亲测有效】如何在Windows和Linux系统中查看MySQL版本

如何在Windows和Linux系统中查看MySQL版本 MySQL作为最流行的开源关系型数据库管理系统之一&#xff0c;了解如何查看其版本信息对于开发者和数据库管理员来说是常用的一个基本操作。本文将详细介绍在Windows和Linux系统中查看MySQL版本的方法。 文章目录 如何在Windows和Linu…

数字智慧方案5961丨智慧能源与运维云平台解决方案(52页PPT)(文末有下载方式)

详细资料请看本解读文章的最后内容。 资料解读&#xff1a;智慧能源与运维云平台解决方案 在当今数字化时代&#xff0c;能源管理与设备运维的智能化、高效化成为企业发展的关键。智慧能源与运维云平台解决方案应运而生&#xff0c;为企业提供了全面且先进的能源管理和运维手段…

Qt指南针

Qt写的指南针demo. 运行结果 滑动调整指针角度 实现代码 h文件 #ifndef COMPASS_H #define COMPASS_H#include <QWidget> #include <QColor>class Compass : public QWidget {Q_OBJECT// 可自定义属性Q_PROPERTY(QColor backgroundColor READ backgroundColor WRI…

北大新媒体运营黄金提示词 | 北大Deepseek系列第七弹《DeepSeek与新媒体运营》,13所大学系列一站下载

今天大师兄给大家推荐的是北京大学Deepseek系列第七弹《DeepSeek与新媒体运营》。 本文档系统介绍了DeepSeek模型在新媒体运营中的应用&#xff0c;技术原理、实践案例及行业挑战。 适用人群&#xff1a;新媒体运营人员、AI研究者、企业决策者。 思维导图 napkin生成 《老…

2025年真实面试问题汇总(一)

Spingboot中如何实现有些类是否加载 在 Spring Boot 中可以通过 条件化配置&#xff08;Conditional Configuration&#xff09; 来控制某些类是否加载。Spring Boot 提供了一系列 Conditional 注解&#xff0c;允许根据特定条件动态决定 Bean 或配置类是否生效。以下是常见的…

综合案例建模(2)

文章目录 螺旋片端盖多孔扭转环作业一作业二作业三 螺旋片端盖 上视基准面画草图&#xff0c;拉伸250&#xff0c;向外拔模15度 以地面圆&#xff08;如果不行就转换实体引用&#xff09;&#xff0c;创建螺旋线&#xff0c;锥形螺纹线15度向外 前视基准面去画草图 以上一步草图…

Qt5与现代OpenGL学习(三)纹理

把第一张图放到D盘的1文件夹里面&#xff1a;1.png triangle.h #ifndef WIDGET_H #define WIDGET_H#include <QOpenGLWidget> #include <QOpenGLFunctions> #include <QOpenGLVertexArrayObject> #include <QOpenGLShaderProgram> #include <QOpen…

这是一款好用的PDF工具!

用户习惯有时确实非常顽固&#xff0c;想要改变它可能需要漫长的时间。 比如PDF软件&#xff0c;我认为国产的福/昕、万/兴等软件都非常不错&#xff0c;它们贴合国人的使用习惯&#xff0c;操作起来非常顺手。但因为我习惯使用DC&#xff0c;所以在处理PDF文档时&#xff0c;…

轻松实现CI/CD: 用Go编写的命令行工具简化Jenkins构建

在工作中&#xff0c;随着开发维护的服务越来越多&#xff0c;在很长的一段时间里&#xff0c;我来回在多个服务之间开发、构建、查看容器是否启动成功。尤其是开发测试阶段&#xff0c;需要打开jenkins页面、搜索应用、再构建、再打开rancher页面、搜索应用&#xff0c;这一连…

第十六届蓝桥杯 2025 C/C++B组第一轮省赛 全部题解(未完结)

目录 前言&#xff1a; 试题A&#xff1a;移动距离 试题C&#xff1a;可分解的正整数 试题D&#xff1a;产值调整 试题E&#xff1a;画展布置 前言&#xff1a; 我参加的是第一轮省赛&#xff0c;说实话第一次参加还是比较紧张的&#xff0c;真到考场上看啥都想打暴力&…

Qt Creator环境编译的Release软件放在其他电脑上使用方法

本文解决的问题&#xff1a;将Qt Creator环境编译的exe可执行程序放到其他电脑上不可用情况 1、寻找windeployqt工具所在路径" D:\Qt5.12.10\5.12.10\msvc2015_64\bin" &#xff0c;将此路径配置到环境变量&#xff1b; 2、用Qt Creator环境编译出Release版本可执行…

使用skywalking进行go的接口监控和报警

安装 helm upgrade --install skywalking ./skywalking-v1 --namespace skywalking --create-namespace 查看安装结果 kubectl get pod -n skywalking NAME READY STATUS RESTARTS AGE elasticsearch-6c4ccbf99f-ng6sk 1/1 …

2025年- H16-Lc124-169.多数元素(技巧)---java版

1.题目描述 2.思路 3.代码实现 import java.util.Arrays;public class H169 {public int majorityElement(int[] nums) {Arrays.sort(nums);int nnums.length;return nums[n/2];}public static void main(String[] args){H169 test07new H169();int[] nums{2,2,1,1,1,2,2};int…

k8s术语pod

Pod概览 理解Pod Pod是kubernetes中你可以创建和部署的最小也是最简的单位,pod代表着集群中运行的进程。 Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。Pod代表着部署的一个单位:kubemetes中应用的一个实例,可能由一个…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》章节思维导图

今天看到了几本书的思维导图&#xff0c;感触颇深&#xff0c;如果思维导图只是章节安排&#xff0c;这样的思维导图有毛用。 给出《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》实质内容章节的思维导图。思维导图的优势是逻辑关系和知识…

Nacos简介—4.Nacos架构和原理二

大纲 1.Nacos的定位和优势 2.Nacos的整体架构 3.Nacos的配置模型 4.Nacos内核设计之一致性协议 5.Nacos内核设计之自研Distro协议 6.Nacos内核设计之通信通道 7.Nacos内核设计之寻址机制 8.服务注册发现模块的注册中心的设计原理 9.服务注册发现模块的注册中心的服务数…