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.pswp.cn/bicheng/79347.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/79347.shtml
英文地址,請注明出處:http://en.pswp.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.服務注冊發現模塊的注冊中心的服務數…