SpringBoot項目,Springboot接入Activiti7

 2023-12-06 阅读 38 评论 0

摘要:目錄前言一、項目引入 Activiti 依賴二、 添加 Activiti 相關配置三、 啟動項目,生成數據表四、 Activiti7 整合 Spring Security五、 工作流簡單測試六、 數據表命名規則說明后記 前言 ??最近新項目要用工作流,查了幾天資料,主要集中在 Activiti7、Flo

目錄

  • 前言
  • 一、項目引入 Activiti 依賴
  • 二、 添加 Activiti 相關配置
  • 三、 啟動項目,生成數據表
  • 四、 Activiti7 整合 Spring Security
  • 五、 工作流簡單測試
  • 六、 數據表命名規則說明
  • 后記

前言

??最近新項目要用工作流,查了幾天資料,主要集中在 Activiti7、Flowable、Camunda 三個,同是 jbpm 框架發展而來,各有優劣。最終選擇了Activiti7,原因無他,僅是手頭參與的其他項目用這個,方便盡快上手,下個項目應該會試試另外兩個。
??本次項目采用 RuoYi-Vue-v3.8 開發,使用的 Springboot 版本為v2.5.8。記錄一下接入使用過程及踩坑信息,便于自己以后查看,如果對其他人有一些幫助也算意外之喜吧。以下內容主要針對于本次項目,可能一些說明不準確或解決方式不完善的地方,能力有限,只能留有遺憾了。

一、項目引入 Activiti 依賴

  1. SpringBoot項目、在 pom 文件中添加 Activiti 相關依賴:

    <dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter</artifactId><version>7.1.0.M4</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions>
    </dependency>
    <dependency><groupId>org.activiti.dependencies</groupId><artifactId>activiti-dependencies</artifactId><version>7.1.0.M4</version><type>pom</type>
    </dependency>
    <!-- Activiti生成流程圖 -->
    <dependency><groupId>org.activiti</groupId><artifactId>activiti-image-generator</artifactId><version>7.1.0.M4</version>
    </dependency>
    
  2. 項目使用 Mysql 數據庫,已引入驅動,因此這里不再添加。Activiti 默認使用 H2 數據庫,如項目未使用數據庫,應引入數據庫驅動。

二、 添加 Activiti 相關配置

  1. 修改 application.yml 配置文件,添加內容如下:

    spring:
    ...# 工作流
    activiti:deployment-mode: never-fail # 關閉 SpringAutoDeploymentcheck-process-definitions: false #自動部署驗證設置:true-開啟(默認)、false-關閉database-schema-update: true #true表示對數據庫中所有表進行更新操作。如果表不存在,則自動創建。history-level: full #full表示全部記錄歷史,方便繪制流程圖db-history-used: true #true表示使用歷史表
    main:allow-bean-definition-overriding: true #不同配置文件中存在id或者name相同的bean定義,后面加載的bean定義會覆蓋前面的bean定義
    

三、 啟動項目,生成數據表

  1. 踩坑1:不明白別人的項目為什么要加 “allow-bean-definition-overriding“ 屬性,所以沒加。結果很快就明白為什么要加了,項目啟動出現如下錯誤(錯誤信息還給出了解決提醒,我真搞笑):

    Description:
    The bean 'methodSecurityInterceptor', defined in class path resource [org/activiti/spring/boot/MethodSecurityConfig.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class] and overriding is disabled.Action:
    Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
    
  2. springboot,踩坑2:在上述步驟之后,重新運行項目,然后又出現了如下錯誤:

    16:46:33.758 [restartedMain] INFO  o.a.e.i.c.ProcessEngineConfigurationImpl - [configuratorsAfterInit,1571] - Executing configure() of class org.activiti.spring.process.conf.ProcessExtensionsConfiguratorAutoConfiguration$$EnhancerBySpringCGLIB$$3d85fc52 (priority:10000)
    16:46:33.893 [restartedMain] ERROR o.a.e.i.i.CommandContext - [logException,149] - Error while closing command context
    org.apache.ibatis.exceptions.PersistenceException: 
    ### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Table 'zhhq.act_ge_property' doesn't exist
    ### The error may exist in org/activiti/db/mapping/entity/Property.xml
    ### The error may involve org.activiti.engine.impl.persistence.entity.PropertyEntityImpl.selectProperty-Inline
    ### The error occurred while setting parameters
    ### SQL: select * from ACT_GE_PROPERTY where NAME_ = ?
    ### Cause: java.sql.SQLSyntaxErrorException: Table 'zhhq.act_ge_property' doesn't exist
    
  • 解決方式:修改 mysql 連接字符串,添加 &nullCatalogMeansCurrent=true
    // 原來的 url
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    // 修改后 url,添加了 &nullCatalogMeansCurrent=true
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    
  • 網友給出的說明:因為 mysql 使用 schema 標識庫名而不是 catalog,因此 mysql 會掃描所有的庫來找表,如果其他庫中有相同名稱的表,activiti 就以為找到了,本質上這個表在當前數據庫中并不存在。設置nullCatalogMeansCurrent=true,表示 mysql 默認當前數據庫操作,在 mysql-connector-java 5.xxx該參數默認為 true,在6.xxx以上默認為 false,因此需要設置 nullCatalogMeansCurrent=true。
  1. 經過以上修改步驟,再次運行項目。好吧,這次我成功了,項目順利啟動,且數據庫增加了25個表。
  2. 留個坑,其實這版 Activiti 自動生成的表字段不全,下面有補充說明。

四、 Activiti7 整合 Spring Security

  1. Activiti7 沒有身份管理的表,其能力依賴和Spring Security整合,新 Api 包括 TaskRuntime 和 ProcessRuntime 都會強制使用 Security 驗證用戶權限。
  2. 查看 ProcessRuntime 類的源碼可發現,需要“ACTIVITI_USER”角色權限。因此,處理思路是,在登錄驗證時,給登錄用戶增加對應權限。
  3. 在 RuoYi 里,登錄驗證的實現如下:
    public String login(String username, String password, String code, String uuid){boolean captchaOnOff = configService.selectCaptchaOnOff();// 驗證碼開關if (captchaOnOff){validateCaptcha(username, code, uuid);}// 用戶驗證Authentication authentication = null;try{// 該方法會去調用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));}catch (Exception e){if (e instanceof BadCredentialsException){AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);}
    
  4. 根據上述代碼中,需要在 UserDetailsServiceImpl.loadUserByUsername 中加入對應邏輯:
  • 原代碼:
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService
    {private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate ISysUserService userService;@Autowiredprivate SysPermissionService permissionService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)){log.info("登錄用戶:{} 不存在.", username);throw new ServiceException("登錄用戶:" + username + " 不存在");}else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())){log.info("登錄用戶:{} 已被刪除.", username);throw new ServiceException("對不起,您的賬號:" + username + " 已被刪除");}else if (UserStatus.DISABLE.getCode().equals(user.getStatus())){log.info("登錄用戶:{} 已被停用.", username);throw new ServiceException("對不起,您的賬號:" + username + " 已停用");}return createLoginUser(user);}public UserDetails createLoginUser(SysUser user){return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));}
    }
    
  • 修改后的代碼:
    // 有變更的方法,在原來的用戶信息類中加入了需要的權限信息
    public UserDetails createLoginUser(SysUser user) {Set<String> postCode = sysPostService.selectPostCodeByUserId(user.getUserId());postCode = postCode.parallelStream().map(s -> "GROUP_" + s).collect(Collectors.toSet());postCode.add("ROLE_ACTIVITI_USER");List<SimpleGrantedAuthority> collect = postCode.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user), collect);
    }
    
  1. 修改相應的用戶信息類 LoginUser
  • 原代碼:
    public class LoginUser implements UserDetails
    {private static final long serialVersionUID = 1L;/*** 用戶ID*/private Long userId;/*** 部門ID*/private Long deptId;/*** 用戶唯一標識*/private String token;/*** 登錄時間*/private Long loginTime;/*** 過期時間*/private Long expireTime;/*** 登錄IP地址*/private String ipaddr;/*** 登錄地點*/private String loginLocation;/*** 瀏覽器類型*/private String browser;/*** 操作系統*/private String os;/*** 權限列表*/private Set<String> permissions;/*** 用戶信息*/private SysUser user;// 省去 set/get 方法@Overridepublic Collection<? extends GrantedAuthority> getAuthorities(){return null;}
    }
    
  • 修改后的代碼:
    public class LoginUser implements UserDetails
    {// 去掉不變的代碼// +++++private List<SimpleGrantedAuthority> authorities;// +++++public void setAuthorities(List<SimpleGrantedAuthority> authorities) {this.authorities = authorities;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities(){return authorities; // 變更}
    }
    
  • 主要是實體類中,增加了權限屬性,且修改了對應的get方法。
  1. 現在,Activiti 就可以識別到登錄用戶,且有了新 Api 對應的權限。
  2. 總結一下,對于 Ruoyi 框架原來的代碼實現而言,修改了兩處,LoginUser 類和 UserDetailsServiceImpl 類,當然,依賴方法增加了相應的查詢崗位(postCode)的方法。
  3. 整合工作就是這樣,實在是不清不楚,馬馬虎虎。鑒于現在的時間和精力,只能緊著項目的實際問題來,希望以后有機會系統地學習一下 Activiti 吧。

五、 工作流簡單測試

  1. 準備工作流(bpmn)文件,因為之后項目要接入 web 流程設計器,所以沒在 IDE 上安裝設計器插件,在舊項目里隨便找了個bpmn文件測試用;
  2. 編寫單元測試,先進行工作流部署測試
  • 編寫單元測試代碼:
    @SpringBootTest()public class ActivitiTest {@Autowiredprivate RepositoryService repositoryService;@Testpublic void deployTest() {Deployment deployment = repositoryService.createDeployment().addClasspathResource("leave.bpmn").name("測試流程").deploy();System.out.println("部署ID:" + deployment.getId());}}
    
  • 運行后,出現缺失表字段錯誤:
    ### SQL: insert into ACT_RE_DEPLOYMENT(ID_, NAME_, CATEGORY_, KEY_, TENANT_ID_, DEPLOY_TIME_, ENGINE_VERSION_, VERSION_, PROJECT_RELEASE_VERSION_)     values(?, ?, ?, ?, ?, ?, ?, ?, ?)
    ### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'VERSION_' in 'field list'
    
  • 好吧,Activiti7.1.0.M4 這版通過自動創建的表結構是少字段的,這 BUG 之前有網友提過,自己忘記這茬了,現在補坑吧:
    -- 修復Activiti7的M4版本缺失字段Bug
    alter table ACT_RE_DEPLOYMENT add column PROJECT_RELEASE_VERSION_ varchar(255) DEFAULT NULL;
    alter table ACT_RE_DEPLOYMENT add column VERSION_ varchar(255) DEFAULT NULL;
    
  • 再次運行,測試順利通過,打印部署成功日志:
    15:59:08.035 [main] INFO  o.a.e.i.b.d.BpmnDeployer - [dispatchProcessDefinitionEntityInitializedEvent,234] - Process deployed: {id: leave:1:f67ecd7e-a047-11ec-a9cd-d45d64273150, key: leave, name: 請假流程-普通表單 }
    
  1. 進行創建流程實例測試
  • 編寫測試代碼:
    @Test
    public void startProcessTest() {ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("test").withName("請假測試").build());System.out.println("實例ID:" + processInstance.getId());
    }
    
  • 運行,如果出現以下錯誤:
    org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    
  • 是因為沒有整合SpringSecurity ,上面整合內容里描述了新提供 Apl,比如 ProcessRuntime 必須有身份驗證,看源碼可知:
    @PreAuthorize("hasRole('ACTIVITI_USER')")
    public class ProcessRuntimeImpl implements ProcessRuntime {}
    
  • 兩種解決思路:一種是整合 SpringSecurity;一種是試一下舊版 Api。那么試下舊版 Api 吧,重新修改代碼:
    @Test
    public void startProcessTest() {Map<String, Object> paramMap = new HashMap<>();paramMap.put("deptLeader", "test01");ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave", "測試請假", paramMap);System.out.println("實例ID:" + pi.getId());
    }
    
  • 再次運行,順利通過測試。
  1. 單元測試就算完了,證明 Activiti 順利接入到了項目中,等前端流程設計器接入了,才能真正方便地用起來吧。

六、 數據表命名規則說明

  1. 簡單記錄一下 Activiti 表的命名規則,留個印象吧,想知道具體信息還是得對應到每個表;
  2. Activiti 的表都以 ”ACT_“ 開頭。第二部分是表示表的用途的兩個字母標識。用途也和服務的 API 對應。
  • act_hi_*:'hi’表示 history,此前綴的表包含歷史數據,如歷史(結束)流程實例,變量,任務等等。
  • act_ge_*:'ge’表示 general,此前綴的表為通用數據,用于不同場景中。
  • act_evt_*:'evt’表示 event,此前綴的表為事件日志。
  • act_procdef_*:'procdef’表示 processdefine,此前綴的表為記錄流程定義信息。
  • act_re_*:'re’表示 repository,此前綴的表包含了流程定義和流程靜態資源(圖片,規則等等)。
  • act_ru_*:'ru’表示 runtime,此前綴的表是記錄運行時的數據,包含流程實例,任務,變量,異步任務等運行中的數據。Activiti只在流程實例執行過程中保存這些數據,在流程結束時就會刪除這些記錄。

后記

??很基礎的一些東西,算是項目使用工作流的第一步吧。接下來,就是在前端項目中接入流程設計器了,然后是實際使用中一些處理和技巧,一步步來吧。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/190481.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息