diff --git a/admin-ui/package.json b/admin-ui/package.json index 024f2be0..cf375011 100644 --- a/admin-ui/package.json +++ b/admin-ui/package.json @@ -6,9 +6,9 @@ "@ant-design/icons": "^5.4.0", "@ant-design/pro-components": "^2.8.7", "@babel/standalone": "^7.25.6", - "@codingapi/flow-pc": "^0.0.36", - "@codingapi/form-pc": "^0.0.36", - "@codingapi/ui-framework": "^0.0.36", + "@codingapi/flow-pc": "^0.0.41", + "@codingapi/form-pc": "^0.0.41", + "@codingapi/ui-framework": "^0.0.41", "@dnd-kit/core": "^6.2.0", "@dnd-kit/sortable": "^9.0.0", "@handsontable/react-wrapper": "^15.0.0", diff --git a/admin-ui/src/components/flow/PostponedFormView.tsx b/admin-ui/src/components/flow/PostponedFormView.tsx index 2b571a5c..23c70af3 100644 --- a/admin-ui/src/components/flow/PostponedFormView.tsx +++ b/admin-ui/src/components/flow/PostponedFormView.tsx @@ -13,10 +13,7 @@ const PostponedFormView:React.FC = (props)=>{ onCancel: () => { props.setVisible(false); }, - onClose: () => { - props.setVisible(false); - }, - destroyOnClose:true, + destroyOnHidden:true, }} onFinish={async (values) => { props.onFinish(values.hours); diff --git a/admin-ui/src/components/flow/UserSelectView.tsx b/admin-ui/src/components/flow/UserSelectView.tsx index 704e20df..82c7f7f4 100644 --- a/admin-ui/src/components/flow/UserSelectView.tsx +++ b/admin-ui/src/components/flow/UserSelectView.tsx @@ -43,9 +43,7 @@ const UserSelectView: React.FC = (props) => { onCancel: () => { props.setVisible(false); }, - onClose: () => { - props.setVisible(false); - } + destroyOnHidden:true, }} onFinish={async (values) => { const users = values.users; diff --git a/admin-ui/src/config/theme.tsx b/admin-ui/src/config/theme.tsx index 2cfb113c..ce6890db 100644 --- a/admin-ui/src/config/theme.tsx +++ b/admin-ui/src/config/theme.tsx @@ -1,15 +1,5 @@ -import {ThemeConfig} from "antd"; - -export const theme = { - token: { - colorPrimary: '#4a79d8', - } -} as ThemeConfig; - export const config = { - // 主题配置 - theme: theme, // 后台名称 title: 'Admin UI', // 后台logo diff --git a/admin-ui/src/config/variables.scss b/admin-ui/src/config/variables.scss deleted file mode 100644 index db056393..00000000 --- a/admin-ui/src/config/variables.scss +++ /dev/null @@ -1,9 +0,0 @@ -// 主题颜色 -$theme-primary-color: #4a79d8; -// 背景颜色 -$body-background-color: #e6e7ea; - -// 标题字体大小 -$title-font-size: 16px; -// 内容字体大小 -$content-font-size:14px; diff --git a/admin-ui/src/index.scss b/admin-ui/src/index.scss deleted file mode 100644 index 4109e9ba..00000000 --- a/admin-ui/src/index.scss +++ /dev/null @@ -1,10 +0,0 @@ -body { - margin: 0; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/admin-ui/src/index.tsx b/admin-ui/src/index.tsx index 3cbfbf44..80e97d1b 100644 --- a/admin-ui/src/index.tsx +++ b/admin-ui/src/index.tsx @@ -6,27 +6,34 @@ import {Provider} from "react-redux"; import store from "@/store/Redux"; import {ConfigProvider} from "antd"; import zhCN from 'antd/es/locale/zh_CN'; -import {theme} from "@/config/theme"; -import './index.scss'; +import '@/styles/index.scss'; import "@/config/register.component"; +import {CSSUtils, ThemeConfig, ThemeProvider} from "@codingapi/ui-framework"; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); +export const theme = { + token: { + colorPrimary: CSSUtils.getRootVariable('--primary-color'), + contentFontSize: CSSUtils.getRootVariable('--content-font-size'), + } +} as ThemeConfig; + + root.render( - - + - + + + - + ); diff --git a/admin-ui/src/pages/flow/user/index.tsx b/admin-ui/src/pages/flow/user/index.tsx index 4e529e2a..e1d14bc9 100644 --- a/admin-ui/src/pages/flow/user/index.tsx +++ b/admin-ui/src/pages/flow/user/index.tsx @@ -183,9 +183,6 @@ const UserPage = () => { title={"编辑用户"} open={visible} destroyOnHidden={true} - onClose={()=>{ - setVisible(false); - }} onCancel={()=>{ setVisible(false); }} diff --git a/admin-ui/src/pages/flow/user/select.tsx b/admin-ui/src/pages/flow/user/select.tsx index 16b26f09..2bfcab36 100644 --- a/admin-ui/src/pages/flow/user/select.tsx +++ b/admin-ui/src/pages/flow/user/select.tsx @@ -42,7 +42,6 @@ const UserSelect:React.FC = (props) => { width={"60%"} open={props.visible} onCancel={() => props.setVisible(false)} - onClose={() => props.setVisible(false)} destroyOnHidden={true} title={"选择用户"} onOk={() => { diff --git a/admin-ui/src/pages/flow/work/index.tsx b/admin-ui/src/pages/flow/work/index.tsx index a7ffc002..4d244a20 100644 --- a/admin-ui/src/pages/flow/work/index.tsx +++ b/admin-ui/src/pages/flow/work/index.tsx @@ -196,9 +196,6 @@ const FlowPage = () => { title="编辑流程" open={editorVisible} destroyOnHidden={true} - onClose={()=>{ - setEditorVisible(false) - }} onCancel={()=>{ setEditorVisible(false) }} diff --git a/admin-ui/src/pages/flow/work/select.tsx b/admin-ui/src/pages/flow/work/select.tsx index 85f5ab9a..c0e5405f 100644 --- a/admin-ui/src/pages/flow/work/select.tsx +++ b/admin-ui/src/pages/flow/work/select.tsx @@ -62,7 +62,6 @@ const FlowSelect: React.FC = (props) => { width={"60%"} open={props.visible} onCancel={() => props.setVisible(false)} - onClose={() => props.setVisible(false)} destroyOnHidden={true} title={"选择流程"} onOk={() => { diff --git a/admin-ui/src/styles/index.scss b/admin-ui/src/styles/index.scss new file mode 100644 index 00000000..5070073e --- /dev/null +++ b/admin-ui/src/styles/index.scss @@ -0,0 +1,25 @@ +:root { + --primary-color: #094edc; + --body-background-color: #fdfdfd; + + --content-font-size-large: 24px; + --content-font-size-middle: 16px; + --content-font-size-small: 12px; + + --content-font-size: var(--content-font-size-middle); +} + + +body { + margin: 0; + padding: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: var(--body-background-color); + font-size: var(--content-font-size); +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/admin-ui/src/styles/mixins.scss b/admin-ui/src/styles/mixins.scss new file mode 100644 index 00000000..6027f078 --- /dev/null +++ b/admin-ui/src/styles/mixins.scss @@ -0,0 +1,13 @@ +@mixin flex-center { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +@mixin flex-right { + display: flex; + justify-content: right; + align-items: center; + width: 100%; +} \ No newline at end of file diff --git a/admin-ui/src/styles/variable.scss b/admin-ui/src/styles/variable.scss new file mode 100644 index 00000000..222b64a3 --- /dev/null +++ b/admin-ui/src/styles/variable.scss @@ -0,0 +1,9 @@ +// 主题颜色 +$theme-primary-color: var(--primary-color, #4a79d8); +// 背景颜色 +$body-background-color: var(--body-background-color, #fdfdfd); + +// 标题字体大小 +$title-font-size: var(--content-font-size-middle, 16px); +// 内容字体大小 +$content-font-size:var(--content-font-size, 12px); diff --git a/example/example-app/example-app-cmd-domain/pom.xml b/example/example-app/example-app-cmd-domain/pom.xml index 5b7a1e74..5e6252ae 100644 --- a/example/example-app/example-app-cmd-domain/pom.xml +++ b/example/example-app/example-app-cmd-domain/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-app/example-app-cmd-meta/pom.xml b/example/example-app/example-app-cmd-meta/pom.xml index b2729b1b..0da17be4 100644 --- a/example/example-app/example-app-cmd-meta/pom.xml +++ b/example/example-app/example-app-cmd-meta/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-app/example-app-query/pom.xml b/example/example-app/example-app-query/pom.xml index 64072c74..a9e68348 100644 --- a/example/example-app/example-app-query/pom.xml +++ b/example/example-app/example-app-query/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-app/example-app-query/src/main/java/com/codingapi/example/app/query/service/FlowAppQueryService.java b/example/example-app/example-app-query/src/main/java/com/codingapi/example/app/query/service/FlowAppQueryService.java index 63a24e88..e4a464ab 100644 --- a/example/example-app/example-app-query/src/main/java/com/codingapi/example/app/query/service/FlowAppQueryService.java +++ b/example/example-app/example-app-query/src/main/java/com/codingapi/example/app/query/service/FlowAppQueryService.java @@ -49,7 +49,21 @@ public Page findAllByOperatorId(SearchRequest searchRequest) { public Page findTodoByOperatorId(SearchRequest searchRequest) { User user = userRepository.getUserByUsername(TokenContext.current().getUsername()); String lastId = searchRequest.getParameter("lastId"); - SQLBuilder sqlBuilder = new SQLBuilder("from FlowRecordEntity r where r.currentOperatorId = ?1 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' "); + SQLBuilder sqlBuilder = new SQLBuilder( + """ + select r from FlowRecordEntity r + LEFT JOIN (select min(m.id) as id from FlowRecordEntity m where m.currentOperatorId = ?1 and m.flowType = 'TODO' and m.flowStatus = 'RUNNING' and m.mergeable = true ) debup + on r.id = debup.id + where r.currentOperatorId = ?1 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' + and (r.mergeable !=true or debup.id is NOT null ) + """, + """ + select count(r) from FlowRecordEntity r + LEFT JOIN (select min(m.id) as id from FlowRecordEntity m where m.currentOperatorId = ?1 and m.flowType = 'TODO' and m.flowStatus = 'RUNNING' and m.mergeable = true ) debup + on r.id = debup.id + where r.currentOperatorId = ?1 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' + and (r.mergeable !=true or debup.id is NOT null ) + """); sqlBuilder.addParam(user.getId()); if(StringUtils.hasText(lastId)){ sqlBuilder.append(" and r.id < ?",Long.parseLong(lastId)); diff --git a/example/example-app/pom.xml b/example/example-app/pom.xml index 3c0e1e9b..dedf666e 100644 --- a/example/example-app/pom.xml +++ b/example/example-app/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.3.70 + 3.4.4 ../pom.xml pom diff --git a/example/example-domain/example-domain-leave/pom.xml b/example/example-domain/example-domain-leave/pom.xml index 00b62073..38ec775a 100644 --- a/example/example-domain/example-domain-leave/pom.xml +++ b/example/example-domain/example-domain-leave/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-domain - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-domain/example-domain-user/pom.xml b/example/example-domain/example-domain-user/pom.xml index 66e2ab94..10e4e34e 100644 --- a/example/example-domain/example-domain-user/pom.xml +++ b/example/example-domain/example-domain-user/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-domain - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-domain/pom.xml b/example/example-domain/pom.xml index ed671301..90f33ec8 100644 --- a/example/example-domain/pom.xml +++ b/example/example-domain/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot springboot-example - 3.3.70 + 3.4.4 ../pom.xml 4.0.0 diff --git a/example/example-infra/example-infra-flow/pom.xml b/example/example-infra/example-infra-flow/pom.xml index c3dd7a34..768ec2e1 100644 --- a/example/example-infra/example-infra-flow/pom.xml +++ b/example/example-infra/example-infra-flow/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot example-infra - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowNodeConvertor.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowNodeConvertor.java index 467d49b4..6345c52d 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowNodeConvertor.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowNodeConvertor.java @@ -21,6 +21,7 @@ public static FlowNodeEntity convert(FlowNode flowNode, long workId){ entity.setCode(flowNode.getCode()); entity.setName(flowNode.getName()); entity.setEditable(flowNode.isEditable()); + entity.setMergeable(flowNode.isMergeable()); entity.setCreateTime(flowNode.getCreateTime()); entity.setType(flowNode.getType().name()); entity.setTimeout(flowNode.getTimeout()); @@ -52,6 +53,7 @@ public static FlowNode convert(FlowNodeEntity entity){ ApprovalType.parser(entity.getApprovalType()), new OperatorMatcher(entity.getOperatorMatcher()), entity.getEditable(), + entity.getMergeable(), entity.getCreateTime(), entity.getUpdateTime(), entity.getTimeout(), diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowRecordConvertor.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowRecordConvertor.java index acaf6df8..94bc4f78 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowRecordConvertor.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/convert/FlowRecordConvertor.java @@ -21,6 +21,7 @@ public static FlowRecordEntity convert(FlowRecord flowRecord) { entity.setWorkId(flowRecord.getWorkId()); entity.setProcessId(flowRecord.getProcessId()); entity.setNodeCode(flowRecord.getNodeCode()); + entity.setMergeable(flowRecord.isMergeable()); entity.setTitle(flowRecord.getTitle()); entity.setCurrentOperatorId(flowRecord.getCurrentOperator().getUserId()); entity.setFlowType(flowRecord.getFlowType().name()); @@ -71,6 +72,7 @@ public static FlowRecord convert(FlowRecordEntity entity, FlowOperatorRepository flowRecord.setWorkId(entity.getWorkId()); flowRecord.setProcessId(entity.getProcessId()); flowRecord.setNodeCode(entity.getNodeCode()); + flowRecord.setMergeable(entity.getMergeable()); flowRecord.setTitle(entity.getTitle()); flowRecord.setCurrentOperator(flowUserRepository.getFlowOperatorById(entity.getCurrentOperatorId())); flowRecord.setFlowType(FlowType.parser(entity.getFlowType())); diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowNodeEntity.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowNodeEntity.java index aba9ca96..5f5471ae 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowNodeEntity.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowNodeEntity.java @@ -67,6 +67,11 @@ public class FlowNodeEntity { */ private Boolean editable; + /** + * 是否合并审批 + */ + private Boolean mergeable; + /** * 创建时间 */ diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowRecordEntity.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowRecordEntity.java index 5a1ec6fc..2dba9381 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowRecordEntity.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/entity/FlowRecordEntity.java @@ -41,6 +41,11 @@ public class FlowRecordEntity { */ private String nodeCode; + /** + * 是否可合并 + */ + private Boolean mergeable; + /** * 流程标题 */ diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/jpa/FlowRecordEntityRepository.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/jpa/FlowRecordEntityRepository.java index 6d8f95e0..1426dfbf 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/jpa/FlowRecordEntityRepository.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/jpa/FlowRecordEntityRepository.java @@ -23,9 +23,17 @@ public interface FlowRecordEntityRepository extends FastRepository findTodoFlowRecordByProcessId(String processId); - @Query(value = "select r from FlowRecordEntity r where r.currentOperatorId = ?1 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' order by r.id desc") + @Query(value = "select r from FlowRecordEntity r" + + " LEFT JOIN (select min(m.id) as id from FlowRecordEntity m where m.currentOperatorId = ?1 and m.flowType = 'TODO' and m.flowStatus = 'RUNNING' and m.mergeable = true ) debup " + + "on r.id = debup.id" + + " where r.currentOperatorId = ?1 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING'" + + " and (r.mergeable !=true or debup.id is NOT null ) order by r.id desc") Page findTodoByOperatorId(long operatorId, PageRequest pageRequest); + @Query(value = "select r from FlowRecordEntity r where r.currentOperatorId = ?1 and r.workCode = ?2 and r.nodeCode = ?3" + + " and r.mergeable = true and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' order by r.id desc") + List findMergeFlowRecordById(long currentOperatorId,String workCode, String nodeCode); + @Query(value = "select r from FlowRecordEntity r where r.currentOperatorId = ?1 and r.workCode = ?2 and r.flowType = 'TODO' and r.flowStatus = 'RUNNING' order by r.id desc") Page findTodoByOperatorIdAndWorkCode(long operatorId, String workCode, PageRequest pageRequest); diff --git a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/repository/FlowRecordRepositoryImpl.java b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/repository/FlowRecordRepositoryImpl.java index 96636f0b..1c11c340 100644 --- a/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/repository/FlowRecordRepositoryImpl.java +++ b/example/example-infra/example-infra-flow/src/main/java/com/codingapi/example/infra/flow/repository/FlowRecordRepositoryImpl.java @@ -48,6 +48,11 @@ public List findFlowRecordByProcessId(String processId) { return flowRecordEntityRepository.findFlowRecordEntityByProcessId(processId).stream().map(item->FlowRecordConvertor.convert(item,flowOperatorRepository)).toList(); } + @Override + public List findMergeFlowRecordById(String workCode, String nodeCode, long currentOperatorId) { + return flowRecordEntityRepository.findMergeFlowRecordById(currentOperatorId,workCode,nodeCode).stream().map(item->FlowRecordConvertor.convert(item,flowOperatorRepository)).toList(); + } + @Override public List findTodoFlowRecordByProcessId(String processId) { return flowRecordEntityRepository.findTodoFlowRecordByProcessId(processId) diff --git a/example/example-infra/example-infra-jpa/pom.xml b/example/example-infra/example-infra-jpa/pom.xml index 9a172039..b4ed72fb 100644 --- a/example/example-infra/example-infra-jpa/pom.xml +++ b/example/example-infra/example-infra-jpa/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot example-infra - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-infra/example-infra-security/pom.xml b/example/example-infra/example-infra-security/pom.xml index 1f1b053f..122e166c 100644 --- a/example/example-infra/example-infra-security/pom.xml +++ b/example/example-infra/example-infra-security/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-infra - 3.3.70 + 3.4.4 ../pom.xml diff --git a/example/example-infra/pom.xml b/example/example-infra/pom.xml index e233bd64..cee76323 100644 --- a/example/example-infra/pom.xml +++ b/example/example-infra/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.3.70 + 3.4.4 ../pom.xml pom diff --git a/example/example-interface/pom.xml b/example/example-interface/pom.xml index ac1b361e..9a41c37a 100644 --- a/example/example-interface/pom.xml +++ b/example/example-interface/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.3.70 + 3.4.4 example-interface diff --git a/example/example-interface/src/main/java/com/codingapi/example/handler/LeaveHandler.java b/example/example-interface/src/main/java/com/codingapi/example/handler/LeaveHandler.java index 25a381bf..b0b15d3f 100644 --- a/example/example-interface/src/main/java/com/codingapi/example/handler/LeaveHandler.java +++ b/example/example-interface/src/main/java/com/codingapi/example/handler/LeaveHandler.java @@ -5,8 +5,10 @@ import com.codingapi.springboot.flow.event.FlowApprovalEvent; import com.codingapi.springboot.framework.event.IHandler; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +@Slf4j @Service @AllArgsConstructor public class LeaveHandler implements IHandler { @@ -15,6 +17,7 @@ public class LeaveHandler implements IHandler { @Override public void handler(FlowApprovalEvent event) { + log.info("LeaveHandler: {}", event); if (event.isFinish() && event.match(LeaveForm.class)) { LeaveForm form = (LeaveForm) event.getBindData(); leaveService.create(form.toLeave()); diff --git a/example/example-server/pom.xml b/example/example-server/pom.xml index c4e82dbe..c2dcedab 100644 --- a/example/example-server/pom.xml +++ b/example/example-server/pom.xml @@ -5,7 +5,7 @@ springboot-example com.codingapi.springboot - 3.3.70 + 3.4.4 4.0.0 diff --git a/example/pom.xml b/example/pom.xml index e400c393..c303c80c 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ springboot-example - 3.3.70 + 3.4.4 springboot-example springboot-example project for Spring Boot diff --git a/mobile-ui/package.json b/mobile-ui/package.json index 471d1627..92710816 100644 --- a/mobile-ui/package.json +++ b/mobile-ui/package.json @@ -4,9 +4,9 @@ "private": true, "dependencies": { "@babel/standalone": "^7.25.6", - "@codingapi/flow-mobile": "^0.0.36", - "@codingapi/form-mobile": "^0.0.36", - "@codingapi/ui-framework": "^0.0.36", + "@codingapi/flow-mobile": "^0.0.41", + "@codingapi/form-mobile": "^0.0.41", + "@codingapi/ui-framework": "^0.0.41", "@logicflow/core": "^2.0.10", "@logicflow/extension": "^2.0.14", "@reduxjs/toolkit": "^2.2.7", diff --git a/mobile-ui/src/config/flows.tsx b/mobile-ui/src/config/flows.tsx index 09c61cd2..35b1fd21 100644 --- a/mobile-ui/src/config/flows.tsx +++ b/mobile-ui/src/config/flows.tsx @@ -1,4 +1,4 @@ -import LeaveForm from "@/pages/levave/form"; +import LeaveForm from "@/pages/leave/form"; export const flowViews = { "default":LeaveForm diff --git a/mobile-ui/src/config/route.tsx b/mobile-ui/src/config/route.tsx index 11db7c6d..fd8cedf9 100644 --- a/mobile-ui/src/config/route.tsx +++ b/mobile-ui/src/config/route.tsx @@ -5,11 +5,11 @@ import HomePage from "@/pages/home"; import {Route} from "react-router"; import {RouteObject} from "react-router/dist/lib/context"; import React from "react"; -import LeaveListPage from "@/pages/levave"; -import LeaveCreatePage from "@/pages/levave/create"; +import LeaveListPage from "@/pages/leave"; +import LeaveCreatePage from "@/pages/leave/create"; import FlowListPage from "@/pages/flow"; import FlowDetailPage from "@/pages/flow/detail"; -import LeaveDetailPage from "@/pages/levave/detail"; +import LeaveDetailPage from "@/pages/leave/detail"; import FormPage from "@/pages/form"; import MircoPage from "@/pages/mirco"; diff --git a/mobile-ui/src/config/theme.tsx b/mobile-ui/src/config/theme.tsx index 603e8a70..754f9057 100644 --- a/mobile-ui/src/config/theme.tsx +++ b/mobile-ui/src/config/theme.tsx @@ -10,7 +10,7 @@ export const config = { // 主题配置 theme: theme, // 后台名称 - title: 'HR-人力资源管理系统', + title: 'Admin-UI', // 后台logo logo: 'static://assets/logo.png', // 欢迎页路径 diff --git a/mobile-ui/src/index.scss b/mobile-ui/src/index.scss deleted file mode 100644 index c3bdded7..00000000 --- a/mobile-ui/src/index.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use "config/variables" as *; - -body { - margin: 0; - padding: 0; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - background-color: $body-background-color; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} diff --git a/mobile-ui/src/index.tsx b/mobile-ui/src/index.tsx index d58a42ce..9f12d458 100644 --- a/mobile-ui/src/index.tsx +++ b/mobile-ui/src/index.tsx @@ -1,7 +1,7 @@ import React, {createContext} from 'react'; import ReactDOM from 'react-dom/client'; import reportWebVitals from './reportWebVitals'; -import './index.scss'; +import '@/styles/index.scss'; import {createHashRouter, RouterProvider} from "react-router-dom"; import {routes} from "@/config/route"; import zhCN from "antd-mobile/es/locales/zh-CN"; diff --git a/mobile-ui/src/layout/Footer/index.scss b/mobile-ui/src/layout/Footer/index.scss index 37632510..7b919ec3 100644 --- a/mobile-ui/src/layout/Footer/index.scss +++ b/mobile-ui/src/layout/Footer/index.scss @@ -1,4 +1,4 @@ -@use "@/config/variables" as *; +@use "@/styles/variable" as *; .page-footer { width: 100%; diff --git a/mobile-ui/src/layout/Header/index.scss b/mobile-ui/src/layout/Header/index.scss index 94502c3c..63e5af71 100644 --- a/mobile-ui/src/layout/Header/index.scss +++ b/mobile-ui/src/layout/Header/index.scss @@ -1,4 +1,4 @@ -@use "@/config/variables" as *; +@use "@/styles/variable" as *; .page-header { background-color: white; diff --git a/mobile-ui/src/pages/flow/index.scss b/mobile-ui/src/pages/flow/index.scss index 1972909f..55d2c0bd 100644 --- a/mobile-ui/src/pages/flow/index.scss +++ b/mobile-ui/src/pages/flow/index.scss @@ -1,4 +1,4 @@ -@use "@/config/variables" as *; +@use "@/styles/variable" as *; $flow-tabs-height: 45px; diff --git a/mobile-ui/src/pages/levave/create.tsx b/mobile-ui/src/pages/leave/create.tsx similarity index 94% rename from mobile-ui/src/pages/levave/create.tsx rename to mobile-ui/src/pages/leave/create.tsx index 5c7c5dbf..f10ef3c6 100644 --- a/mobile-ui/src/pages/levave/create.tsx +++ b/mobile-ui/src/pages/leave/create.tsx @@ -1,7 +1,7 @@ import React from "react"; import Header from "@/layout/Header"; import {FlowView} from "@codingapi/flow-mobile"; -import LeaveForm from "@/pages/levave/form"; +import LeaveForm from "@/pages/leave/form"; import {useNavigate} from "react-router"; const LeaveCreatePage = () => { diff --git a/mobile-ui/src/pages/levave/detail.tsx b/mobile-ui/src/pages/leave/detail.tsx similarity index 92% rename from mobile-ui/src/pages/levave/detail.tsx rename to mobile-ui/src/pages/leave/detail.tsx index d836238d..4a7a0739 100644 --- a/mobile-ui/src/pages/levave/detail.tsx +++ b/mobile-ui/src/pages/leave/detail.tsx @@ -2,7 +2,7 @@ import React from "react"; import Header from "@/layout/Header"; import {useLocation} from "react-router"; import {Descriptions} from "@codingapi/form-mobile"; -import {fields} from "@/pages/levave/fields"; +import {fields} from "@/pages/leave/fields"; const LeaveDetailPage = () => { diff --git a/mobile-ui/src/pages/levave/fields.ts b/mobile-ui/src/pages/leave/fields.ts similarity index 100% rename from mobile-ui/src/pages/levave/fields.ts rename to mobile-ui/src/pages/leave/fields.ts diff --git a/mobile-ui/src/pages/levave/form.tsx b/mobile-ui/src/pages/leave/form.tsx similarity index 99% rename from mobile-ui/src/pages/levave/form.tsx rename to mobile-ui/src/pages/leave/form.tsx index 043aa6cd..724aaf3f 100644 --- a/mobile-ui/src/pages/levave/form.tsx +++ b/mobile-ui/src/pages/leave/form.tsx @@ -3,7 +3,7 @@ import {Form} from "@codingapi/form-mobile"; import {FlowFormViewProps} from "@codingapi/ui-framework"; import {Button, Toast} from "antd-mobile"; import {FlowViewReactContext} from "@codingapi/flow-mobile"; -import {fields} from "@/pages/levave/fields"; +import {fields} from "@/pages/leave/fields"; const LeaveForm: React.FC = (props) => { const flowViewReactContext = useContext(FlowViewReactContext); diff --git a/mobile-ui/src/pages/levave/index.tsx b/mobile-ui/src/pages/leave/index.tsx similarity index 100% rename from mobile-ui/src/pages/levave/index.tsx rename to mobile-ui/src/pages/leave/index.tsx diff --git a/mobile-ui/src/styles/index.scss b/mobile-ui/src/styles/index.scss new file mode 100644 index 00000000..05bd0fcc --- /dev/null +++ b/mobile-ui/src/styles/index.scss @@ -0,0 +1,30 @@ +:root { + --primary-color: #0f58ea; + --body-background-color: #e6e7ea; + + --content-font-size-large: 24px; + --content-font-size-middle: 16px; + --content-font-size-small: 12px; + + --content-font-size: var(--content-font-size-middle); +} + + +:root:root{ + --adm-color-primary: var(--primary-color); +} + + +body { + margin: 0; + padding: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: var(--body-background-color); + font-size: var(--content-font-size); +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/mobile-ui/src/styles/mixins.scss b/mobile-ui/src/styles/mixins.scss new file mode 100644 index 00000000..6027f078 --- /dev/null +++ b/mobile-ui/src/styles/mixins.scss @@ -0,0 +1,13 @@ +@mixin flex-center { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +@mixin flex-right { + display: flex; + justify-content: right; + align-items: center; + width: 100%; +} \ No newline at end of file diff --git a/mobile-ui/src/config/variables.scss b/mobile-ui/src/styles/variable.scss similarity index 64% rename from mobile-ui/src/config/variables.scss rename to mobile-ui/src/styles/variable.scss index f55d467c..4202a649 100644 --- a/mobile-ui/src/config/variables.scss +++ b/mobile-ui/src/styles/variable.scss @@ -1,7 +1,7 @@ // 主题颜色 -$theme-primary-color: #4a79d8; +$theme-primary-color: var(--primary-color,#4a79d8); // 背景颜色 -$body-background-color: #e6e7ea; +$body-background-color: var(--body-background-color,#e6e7ea); // header颜色 $header-background-color: white; // FormPlaceholder颜色 @@ -13,6 +13,6 @@ $page-footer-height: 50px; // 页面内容区域的高度 $page-content-height: calc(100vh - #{$page-header-height} - #{$page-footer-height}); // 标题字体大小 -$title-font-size: 16px; +$title-font-size: var(--content-font-size-middle,16px); // 内容字体大小 -$content-font-size:14px; +$content-font-size:var(--content-font-size,12px); \ No newline at end of file diff --git a/pom.xml b/pom.xml index e0c63a43..e05b1385 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ com.codingapi.springboot springboot-parent - 3.3.70 + 3.4.4 https://github.com/codingapi/springboot-framewrok springboot-parent diff --git a/springboot-starter-data-authorization/pom.xml b/springboot-starter-data-authorization/pom.xml index bc8e2931..038ac8b5 100644 --- a/springboot-starter-data-authorization/pom.xml +++ b/springboot-starter-data-authorization/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-parent - 3.3.70 + 3.4.4 springboot-starter-data-authorization diff --git a/springboot-starter-data-fast/pom.xml b/springboot-starter-data-fast/pom.xml index 656255a6..2f317b13 100644 --- a/springboot-starter-data-fast/pom.xml +++ b/springboot-starter-data-fast/pom.xml @@ -5,7 +5,7 @@ springboot-parent com.codingapi.springboot - 3.3.70 + 3.4.4 4.0.0 diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java index 020e2802..9a3aebda 100644 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java @@ -19,6 +19,10 @@ public SQLBuilder(String sql) { this(null, sql, "select count(1) from " + sql); } + public SQLBuilder(String sql,String countSql) { + this(null, sql, countSql); + } + public SQLBuilder(Class clazz, String sql) { this(clazz, sql, "select count(1) from " + sql); } diff --git a/springboot-starter-flow/pom.xml b/springboot-starter-flow/pom.xml index 5736d59c..567a4388 100644 --- a/springboot-starter-flow/pom.xml +++ b/springboot-starter-flow/pom.xml @@ -6,7 +6,7 @@ springboot-parent com.codingapi.springboot - 3.3.70 + 3.4.4 springboot-starter-flow diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java new file mode 100644 index 00000000..2648956d --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java @@ -0,0 +1,48 @@ +package com.codingapi.springboot.flow.bind; + +import com.alibaba.fastjson.JSONObject; + +import java.util.HashMap; + +/** + * 流程绑定Map数据对象,用于分布式服务下的流程对象数据传递能力 + * 该对象中,将clazzName 当做了普通的key来使用, + */ +public class FlowMapBindData extends HashMap implements IBindData { + + + /** + * 获取类名称 + * + * @return 类名称 + */ + @Override + public String getClazzName() { + return (String) this.get(CLASS_NAME_KEY); + } + + /** + * 转化为类对象 + */ + @Override + public T toJavaObject(Class clazz) { + return JSONObject.parseObject(toJsonSnapshot(), clazz); + } + + public static FlowMapBindData fromJson(String json) { + return JSONObject.parseObject(json, FlowMapBindData.class); + } + + public static FlowMapBindData fromObject(Object obj) { + return JSONObject.parseObject(JSONObject.toJSONString(obj), FlowMapBindData.class); + } + + public static FlowMapBindData fromJson(JSONObject json) { + return JSONObject.parseObject(json.toJSONString(), FlowMapBindData.class); + } + + public boolean match(String matchKey) { + String className = this.getClazzName(); + return matchKey.equals(className); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java index 4814cdcb..5c152799 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java @@ -7,6 +7,8 @@ */ public interface IBindData { + String CLASS_NAME_KEY = "clazzName"; + /** * 数据快照 * @@ -19,9 +21,27 @@ default String toJsonSnapshot() { /** * 获取类名称 + * * @return 类名称 */ default String getClazzName() { return this.getClass().getName(); } + + + /** + * 类对象匹配 + */ + default boolean match(String dataKey) { + String className = this.getClazzName(); + return dataKey.equals(className); + } + + + /** + * 转化为类对象 + */ + default T toJavaObject(Class clazz) { + return (T) this; + } } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java index b9908f14..e8a07925 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java @@ -73,43 +73,47 @@ public FlowWork build() { public class Nodes { - public Nodes node(String id, String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, TitleGenerator titleGenerator, ErrTrigger errTrigger, boolean editable, List buttons) { - FlowNode node = new FlowNode(id, name, code, view, NodeType.parser(code), approvalType, titleGenerator, operatorMatcher, timeout, errTrigger, editable, buttons); + public Nodes node(String id, String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, TitleGenerator titleGenerator, ErrTrigger errTrigger, boolean editable, boolean mergeable, List buttons) { + FlowNode node = new FlowNode(id, name, code, view, NodeType.parser(code), approvalType, titleGenerator, operatorMatcher, timeout, errTrigger, editable,mergeable, buttons); work.addNode(node); return this; } public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable, null); + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,false, null); } - public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable, List buttons) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable, buttons); + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, null); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, buttons); } - public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable, null); + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, null); } - public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable, List buttons) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable, buttons); + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, buttons); } public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, List buttons) { - return node(name, code, view, approvalType, operatorMatcher, true, buttons); + return node(name, code, view, approvalType, operatorMatcher, true,false, buttons); } public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher) { - return node(name, code, view, approvalType, operatorMatcher, true, null); + return node(name, code, view, approvalType, operatorMatcher, true,false, null); } - public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable, List buttons) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable, buttons); + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable,mergeable, buttons); } - public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable) { - return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable, null); + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable,mergeable, null); } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java index f169b75e..09217db6 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java @@ -48,6 +48,7 @@ private void loadNodes() { String titleGenerator = properties.getString("titleGenerator"); String name = properties.getString("name"); boolean editable = properties.getBoolean("editable"); + boolean mergeable = properties.getBoolean("mergeable"); String view = properties.getString("view"); String type = properties.getString("type"); String approvalType = properties.getString("approvalType"); @@ -59,7 +60,7 @@ private void loadNodes() { buttons = properties.getJSONArray("buttons").toJavaList(FlowButton.class); } FlowNode flowNode = new FlowNode(id, name, code, view, NodeType.parser(type), ApprovalType.parser(approvalType), new TitleGenerator(titleGenerator), - new OperatorMatcher(operatorMatcher), timeout, StringUtils.hasLength(errTrigger) ? new ErrTrigger(errTrigger) : null, editable, buttons); + new OperatorMatcher(operatorMatcher), timeout, StringUtils.hasLength(errTrigger) ? new ErrTrigger(errTrigger) : null, editable,mergeable, buttons); flowNodes.add(flowNode); } } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java index fd78d2a3..4dffa302 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java @@ -77,6 +77,13 @@ public class FlowNode { */ private boolean editable; + /** + * 是否合并记录 + *

+ * 如果为true,则表示该节点可以合并记录 + */ + private boolean mergeable; + /** * 创建时间 */ @@ -147,6 +154,7 @@ public FlowNodeSerializable toSerializable() { this.approvalType, this.operatorMatcher.getScript(), this.editable, + this.mergeable, this.createTime, this.updateTime, this.timeout, @@ -167,6 +175,7 @@ public FlowNode(String id, long timeout, ErrTrigger errTrigger, boolean editable, + boolean mergeable, List buttons) { this.id = id; this.code = code; @@ -181,6 +190,7 @@ public FlowNode(String id, this.errTrigger = errTrigger; this.timeout = timeout; this.editable = editable; + this.mergeable = mergeable; this.buttons = buttons; } @@ -229,6 +239,7 @@ public FlowRecord createRecord(long workId, FlowRecord record = new FlowRecord(); record.setProcessId(processId); record.setNodeCode(this.code); + record.setMergeable(this.mergeable); record.setCreateTime(System.currentTimeMillis()); record.setWorkId(workId); record.setWorkCode(workCode); diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java index ba6665ad..d6b8f406 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java @@ -34,6 +34,8 @@ public class FlowApprovalEvent implements ISyncEvent { public static final int STATE_URGE = 8; // 抄送 public static final int STATE_CIRCULATE = 9; + // 保存 + public static final int STATE_SAVE = 10; private final int state; @@ -52,8 +54,23 @@ public FlowApprovalEvent(int state, FlowRecord flowRecord, IFlowOperator operato } - public boolean match(Class bindDataClass) { - return bindDataClass.isInstance(bindData); + public boolean match(String matchKey) { + return bindData.match(matchKey); + } + + /** + * 匹配类名 + * 当前bingData下的clazzName变成了普通的key字段了,推荐使用match(String matchKey)方法 + * @param clazz 类名 + * @return 是否匹配 + */ + @Deprecated + public boolean match(Class clazz) { + return bindData.match(clazz.getName()); + } + + public T toJavaObject(Class clazz) { + return bindData.toJavaObject(clazz); } public boolean isUrge() { @@ -64,6 +81,10 @@ public boolean isTodo() { return state == STATE_TODO; } + public boolean isSave() { + return state == STATE_SAVE; + } + public boolean isCreate() { return state == STATE_CREATE; } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java index 5ecaa2d1..91e850c2 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java @@ -5,6 +5,7 @@ import com.codingapi.springboot.flow.domain.FlowNode; import com.codingapi.springboot.flow.domain.FlowWork; import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.record.FlowMerge; import com.codingapi.springboot.flow.record.FlowRecord; import com.codingapi.springboot.flow.user.IFlowOperator; import lombok.Getter; @@ -63,8 +64,14 @@ public class FlowDetail { */ private final boolean canHandle; + /** + * 合并记录 + */ + private final List mergeRecords; + public FlowDetail(FlowRecord flowRecord, + List mergeRecords, BindDataSnapshot snapshot, FlowWork flowWork, List historyRecords, @@ -72,6 +79,7 @@ public FlowDetail(FlowRecord flowRecord, boolean canHandle) { this.operators = operators; this.flowRecord = flowRecord; + this.mergeRecords = mergeRecords; this.flowWork = flowWork; this.bindData = snapshot.toBindData(); this.historyRecords = historyRecords; @@ -91,6 +99,7 @@ public FlowDetail(FlowWork flowWork, this.operators = operators; this.flowCreateTime = 0; this.flowRecord = null; + this.mergeRecords = null; this.historyRecords = null; this.bindData = null; this.opinions = null; diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java new file mode 100644 index 00000000..5ef07031 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java @@ -0,0 +1,14 @@ +package com.codingapi.springboot.flow.record; + +import com.codingapi.springboot.flow.bind.IBindData; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FlowMerge { + + private final FlowRecord flowRecord; + private final IBindData bindData; + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java index 2f91d341..d8f11670 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java @@ -49,6 +49,11 @@ public class FlowRecord { */ private String nodeCode; + /** + * 是否可合并 + */ + private boolean mergeable; + /** * 流程标题 */ @@ -401,6 +406,7 @@ public FlowRecord copy() { record.setWorkCode(this.workCode); record.setProcessId(this.processId); record.setNodeCode(this.nodeCode); + record.setMergeable(this.mergeable); record.setTitle(this.title); record.setCurrentOperator(this.currentOperator); record.setFlowType(this.flowType); diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java index 49b23716..192f9f9c 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java @@ -49,6 +49,16 @@ public interface FlowRecordRepository { List findFlowRecordByProcessId(String processId); + /** + * 获取合并的流程记录 + * @param workCode 流程编码 + * @param nodeCode 节点编码 + * @param currentOperatorId 当前操作者ID + * @return List of FlowRecord + */ + List findMergeFlowRecordById(String workCode,String nodeCode,long currentOperatorId); + + /** * 查询所有未完成的流程记录 * diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java index d6e80dea..97e7418d 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java @@ -69,6 +69,11 @@ public class FlowNodeSerializable implements Serializable { */ private boolean editable; + /** + * 是否可合并审批 + */ + private boolean mergeable; + /** * 创建时间 */ @@ -95,6 +100,6 @@ public class FlowNodeSerializable implements Serializable { public FlowNode toFlowNode() { return new FlowNode(id, code, name, new TitleGenerator(titleGenerator), type, view, approvalType, - new OperatorMatcher(operatorMatcher), editable, createTime, updateTime, timeout, errTrigger == null ? null : new ErrTrigger(errTrigger),buttons); + new OperatorMatcher(operatorMatcher), editable,mergeable, createTime, updateTime, timeout, errTrigger == null ? null : new ErrTrigger(errTrigger),buttons); } } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java index b2827d3c..5cd7eebb 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java @@ -162,7 +162,7 @@ public void loadCustomBackNode(FlowNode flowNode, long parentRecordId) { } this.nextNode = nextNode; this.nextOperator = flowOperator; - this.backOperator = flowOperator; + this.backOperator = null; } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java index c51f8249..2402b47a 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java @@ -5,6 +5,7 @@ import com.codingapi.springboot.flow.domain.FlowNode; import com.codingapi.springboot.flow.domain.FlowWork; import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowMerge; import com.codingapi.springboot.flow.record.FlowRecord; import com.codingapi.springboot.flow.repository.*; import com.codingapi.springboot.flow.service.FlowRecordVerifyService; @@ -45,6 +46,16 @@ public FlowDetail detail(long recordId, IFlowOperator currentOperator) { FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + List mergeRecords = null; + if(flowRecord.isTodo() && flowRecord.isMergeable()){ + List flowRecords = flowRecordRepository.findMergeFlowRecordById(flowRecord.getWorkCode(),flowRecord.getNodeCode(),currentOperator.getUserId()); + if(!flowRecords.isEmpty()){ + mergeRecords = flowRecords.stream().map(record->{ + BindDataSnapshot bindDataSnapshot = flowBindDataRepository.getBindDataSnapshotById(record.getSnapshotId()); + return new FlowMerge(record,bindDataSnapshot.toBindData()); + }).toList(); + } + } BindDataSnapshot snapshot = flowBindDataRepository.getBindDataSnapshotById(flowRecord.getSnapshotId()); List flowRecords = @@ -63,7 +74,7 @@ public FlowDetail detail(long recordId, IFlowOperator currentOperator) { } } - return new FlowDetail(flowRecord, snapshot, flowWork, flowRecords, operators, currentOperator != null && flowRecord.isTodo() && flowRecord.isOperator(currentOperator)); + return new FlowDetail(flowRecord,mergeRecords, snapshot, flowWork, flowRecords, operators, currentOperator != null && flowRecord.isTodo() && flowRecord.isOperator(currentOperator)); } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java index 95631d51..85698ace 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java @@ -2,11 +2,14 @@ import com.codingapi.springboot.flow.bind.BindDataSnapshot; import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowWork; import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; import com.codingapi.springboot.flow.record.FlowRecord; import com.codingapi.springboot.flow.repository.*; import com.codingapi.springboot.flow.service.FlowRecordVerifyService; import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; import lombok.AllArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -45,6 +48,15 @@ public void save(long recordId, IFlowOperator currentOperator, IBindData bindDat flowRecord.setOpinion(opinion); flowRecordRepository.update(flowRecord); + + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_SAVE, + flowRecord, + flowRecord.getCurrentOperator(), + flowWork, + snapshot.toBindData()), + true); } } diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java index 99a6fd1a..d035a5e4 100644 --- a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java @@ -8,7 +8,6 @@ import com.codingapi.springboot.flow.em.FlowSourceDirection; import com.codingapi.springboot.flow.event.FlowApprovalEvent; import com.codingapi.springboot.flow.pojo.FlowResult; -import com.codingapi.springboot.flow.pojo.FlowSubmitResult; import com.codingapi.springboot.flow.record.FlowBackup; import com.codingapi.springboot.flow.record.FlowProcess; import com.codingapi.springboot.flow.record.FlowRecord; @@ -190,6 +189,7 @@ public FlowResult startFlow() { for (FlowRecord record : records) { this.pushEvent(FlowApprovalEvent.STATE_CREATE, record); this.pushEvent(FlowApprovalEvent.STATE_TODO, record); + this.pushEvent(FlowApprovalEvent.STATE_SAVE, record); } // 当前的审批记录 return new FlowResult(flowWork, records); diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java index 2d02fed0..1a19f3a1 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java @@ -1,11 +1,15 @@ package com.codingapi.springboot.flow.flow; import com.codingapi.springboot.flow.bind.IBindData; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter +@NoArgsConstructor +@AllArgsConstructor public class Leave implements IBindData { private long id; diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java new file mode 100644 index 00000000..c6612bc4 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.flow.flow; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Leave2 { + + private long id; + private String title; + private int days; + + public Leave2(String title) { + this(title,0); + } + + public Leave2(String title, int days) { + this.title = title; + this.days = days; + } + +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java index 74038f2b..9c7f66ea 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java @@ -49,6 +49,17 @@ public List findFlowRecordByProcessId(String processId) { .toList(); } + @Override + public List findMergeFlowRecordById(String workCode, String nodeCode, long operatorId) { + return cache.stream() + .filter(record -> record.isTodo() && record.getCurrentOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + && record.getNodeCode().equals(nodeCode) + && record.isMergeable() + ) + .toList(); + } + @Override public List findTodoFlowRecordByProcessId(String processId) { return cache.stream().filter(record -> record.isTodo() && record.getProcessId().equals(processId)).toList(); diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java index dd4f50f1..7d97dcef 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java @@ -1,6 +1,7 @@ package com.codingapi.springboot.flow.repository; import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.flow.Leave2; import java.util.ArrayList; import java.util.List; @@ -15,4 +16,9 @@ public void save(Leave leave) { leave.setId(cache.size()); } } + + public void save(Leave2 leave2) { + Leave leave = new Leave(leave2.getId(), leave2.getTitle(), leave2.getDays()); + this.save(leave); + } } diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java index 97010832..d04a3124 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java @@ -53,7 +53,7 @@ void errorMatcherOperatorTest(){ .title("请假流程") .nodes() .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) - .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createOperatorErrTrigger("+dept.getId()+")}"), true) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createOperatorErrTrigger("+dept.getId()+")}"), true,false) .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) .relations() @@ -145,7 +145,7 @@ void errorMatcherNodeTest(){ .title("请假流程") .nodes() .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) - .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createNodeErrTrigger('manager')}"), true) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createNodeErrTrigger('manager')}"), true,false) .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) .relations() diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java new file mode 100644 index 00000000..a14c7e98 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java @@ -0,0 +1,127 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.FlowMapBindData; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave2; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FlowMapTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository, userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository, flowProcessRepository, flowBackupRepository); + + /** + * map数据绑定对象测试 + */ + @Test + void mapFlowTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave2 leave = new Leave2("我要出去看看"); + FlowMapBindData bindData = FlowMapBindData.fromObject(leave); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, bindData, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + bindData = FlowMapBindData.fromObject(leave); + flowService.save(userTodo.getId(), user, bindData, "暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", (flowDetail.getBindData().toJavaObject(Leave2.class)).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, bindData, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, bindData, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, bindData, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java index fb30aa70..b8496b0e 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java @@ -386,7 +386,7 @@ void saveDisableTest() { .title("请假流程") .nodes() .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) - .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId()), false) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId()), false,false) .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) .relations() diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java index 314524f3..963afde1 100644 --- a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java @@ -14,7 +14,7 @@ public class ScriptBuildTest { @Test void copy() { User user = new User("张三"); - String script = "{\"nodes\":[{\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"type\":\"start-node\",\"x\":593,\"y\":96,\"properties\":{\"name\":\"开始节点\",\"code\":\"start\",\"type\":\"START\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"type\":\"node-node\",\"x\":620,\"y\":239,\"properties\":{\"name\":\"流程节点\",\"code\":\"flow\",\"type\":\"APPROVAL\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '8899-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"SIGN\",\"timeout\":10,\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"custom\",\"errTriggerType\":\"custom\"}},{\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"type\":\"over-node\",\"x\":828,\"y\":582,\"properties\":{\"name\":\"结束节点\",\"code\":\"over\",\"type\":\"OVER\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"type\":\"circulate-node\",\"x\":839,\"y\":409,\"properties\":{\"name\":\"抄送节点\",\"code\":\"circulate\",\"type\":\"CIRCULATE\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCreateOperator().getUserId()];}\",\"editable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"CIRCULATE\",\"timeout\":0,\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"width\":200,\"height\":45}}],\"edges\":[{\"id\":\"b68837fb-dca8-41d2-908c-dc079a7f61de\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"targetNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"startPoint\":{\"x\":593,\"y\":118.5},\"endPoint\":{\"x\":620,\"y\":216.5},\"pointsList\":[{\"x\":593,\"y\":118.5},{\"x\":593,\"y\":218.5},{\"x\":620,\"y\":116.5},{\"x\":620,\"y\":216.5}]},{\"id\":\"73e04b95-50f6-44cc-a960-d3007d27fd48\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":2,\"back\":false},\"sourceNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"targetNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"startPoint\":{\"x\":720,\"y\":239},\"endPoint\":{\"x\":739,\"y\":409},\"pointsList\":[{\"x\":720,\"y\":239},{\"x\":820,\"y\":239},{\"x\":639,\"y\":409},{\"x\":739,\"y\":409}]},{\"id\":\"f6929c79-b168-4c3c-9f8f-9dc21fcaf29d\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"targetNodeId\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"startPoint\":{\"x\":839,\"y\":431.5},\"endPoint\":{\"x\":828,\"y\":559.5},\"pointsList\":[{\"x\":839,\"y\":431.5},{\"x\":839,\"y\":531.5},{\"x\":828,\"y\":459.5},{\"x\":828,\"y\":559.5}]}]}"; + String script = "{\"nodes\":[{\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"type\":\"start-node\",\"x\":593,\"y\":96,\"properties\":{\"name\":\"开始节点\",\"code\":\"start\",\"type\":\"START\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"type\":\"node-node\",\"x\":620,\"y\":239,\"properties\":{\"name\":\"流程节点\",\"code\":\"flow\",\"type\":\"APPROVAL\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '8899-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"SIGN\",\"timeout\":10,\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"custom\",\"errTriggerType\":\"custom\"}},{\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"type\":\"over-node\",\"x\":828,\"y\":582,\"properties\":{\"name\":\"结束节点\",\"code\":\"over\",\"type\":\"OVER\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"type\":\"circulate-node\",\"x\":839,\"y\":409,\"properties\":{\"name\":\"抄送节点\",\"code\":\"circulate\",\"type\":\"CIRCULATE\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCreateOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"CIRCULATE\",\"timeout\":0,\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"width\":200,\"height\":45}}],\"edges\":[{\"id\":\"b68837fb-dca8-41d2-908c-dc079a7f61de\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"targetNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"startPoint\":{\"x\":593,\"y\":118.5},\"endPoint\":{\"x\":620,\"y\":216.5},\"pointsList\":[{\"x\":593,\"y\":118.5},{\"x\":593,\"y\":218.5},{\"x\":620,\"y\":116.5},{\"x\":620,\"y\":216.5}]},{\"id\":\"73e04b95-50f6-44cc-a960-d3007d27fd48\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":2,\"back\":false},\"sourceNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"targetNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"startPoint\":{\"x\":720,\"y\":239},\"endPoint\":{\"x\":739,\"y\":409},\"pointsList\":[{\"x\":720,\"y\":239},{\"x\":820,\"y\":239},{\"x\":639,\"y\":409},{\"x\":739,\"y\":409}]},{\"id\":\"f6929c79-b168-4c3c-9f8f-9dc21fcaf29d\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"targetNodeId\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"startPoint\":{\"x\":839,\"y\":431.5},\"endPoint\":{\"x\":828,\"y\":559.5},\"pointsList\":[{\"x\":839,\"y\":431.5},{\"x\":839,\"y\":531.5},{\"x\":828,\"y\":459.5},{\"x\":828,\"y\":559.5}]}]}"; FlowWork flowWork = FlowWorkBuilder.builder(user) .title("请假流程") .schema(script) diff --git a/springboot-starter-security/pom.xml b/springboot-starter-security/pom.xml index 7357d48d..6cc96320 100644 --- a/springboot-starter-security/pom.xml +++ b/springboot-starter-security/pom.xml @@ -6,7 +6,7 @@ springboot-parent com.codingapi.springboot - 3.3.70 + 3.4.4 springboot-starter-security diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java index 2f40a953..fdc348df 100644 --- a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java @@ -2,6 +2,8 @@ import com.codingapi.springboot.security.configurer.HttpSecurityConfigurer; import com.codingapi.springboot.security.controller.VersionController; +import com.codingapi.springboot.security.customer.DefaultHttpSecurityCustomer; +import com.codingapi.springboot.security.customer.HttpSecurityCustomer; import com.codingapi.springboot.security.dto.request.LoginRequest; import com.codingapi.springboot.security.dto.response.LoginResponse; import com.codingapi.springboot.security.filter.*; @@ -21,6 +23,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -89,28 +92,24 @@ public AuthenticationTokenFilter authenticationTokenFilter() { }; } - @Bean @ConditionalOnMissingBean - public SecurityFilterChain filterChain(HttpSecurity security, TokenGateway tokenGateway, SecurityLoginHandler loginHandler, - CodingApiSecurityProperties properties, AuthenticationTokenFilter authenticationTokenFilter) throws Exception { - //disable basic auth - security.httpBasic(AbstractHttpConfigurer::disable); - - //before add addCorsMappings to enable cors. - security.cors(httpSecurityCorsConfigurer -> { - if (properties.isDisableCors()) { - httpSecurityCorsConfigurer.disable(); - } - }); - - security.csrf(httpSecurityCsrfConfigurer -> { - if (properties.isDisableCsrf()) { - httpSecurityCsrfConfigurer.disable(); - } - }); + public HttpSecurityCustomer httpSecurityCustomer(CodingApiSecurityProperties properties){ + return new DefaultHttpSecurityCustomer(properties); + } + @Bean + @ConditionalOnMissingBean + public SecurityFilterChain filterChain(HttpSecurity security, + HttpSecurityCustomer httpSecurityCustomer, + TokenGateway tokenGateway, + SecurityLoginHandler loginHandler, + CodingApiSecurityProperties properties, + AuthenticationTokenFilter authenticationTokenFilter) throws Exception { + httpSecurityCustomer.customize(security); + + //authentication filter security.with(new HttpSecurityConfigurer(tokenGateway, loginHandler, properties, authenticationTokenFilter), Customizer.withDefaults()); security.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(new MyUnAuthenticationEntryPoint()) diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java new file mode 100644 index 00000000..dfff8208 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java @@ -0,0 +1,51 @@ +package com.codingapi.springboot.security.customer; + +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; +import lombok.AllArgsConstructor; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; + +@AllArgsConstructor +public class DefaultHttpSecurityCustomer implements HttpSecurityCustomer { + + private final CodingApiSecurityProperties properties; + + @Override + public void customize(HttpSecurity security) throws Exception { + + //disable basic auth + if (properties.isDisableBasicAuth()) { + security.httpBasic(AbstractHttpConfigurer::disable); + } + + //disable frame options + if (properties.isDisableFrameOptions()) { + security.headers(new Customizer>() { + @Override + public void customize(HeadersConfigurer httpSecurityHeadersConfigurer) { + httpSecurityHeadersConfigurer.frameOptions(new Customizer.FrameOptionsConfig>() { + @Override + public void customize(HeadersConfigurer.FrameOptionsConfig frameOptionsConfig) { + frameOptionsConfig.disable(); + } + }); + } + }); + } + + //before add addCorsMappings to enable cors. + security.cors(httpSecurityCorsConfigurer -> { + if (properties.isDisableCors()) { + httpSecurityCorsConfigurer.disable(); + } + }); + + security.csrf(httpSecurityCsrfConfigurer -> { + if (properties.isDisableCsrf()) { + httpSecurityCsrfConfigurer.disable(); + } + }); + } +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java new file mode 100644 index 00000000..ddc35dd6 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java @@ -0,0 +1,9 @@ +package com.codingapi.springboot.security.customer; + +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +public interface HttpSecurityCustomer { + + void customize(HttpSecurity security) throws Exception; + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java index 4a33d91b..1798bc09 100644 --- a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java @@ -39,6 +39,16 @@ public class CodingApiSecurityProperties { private String aseIv = "QUNYRkdIQEVEUyNYQ1phcw=="; + /** + * 禁用Basic Auth + */ + private boolean disableBasicAuth = true; + + /** + * 禁用FrameOptions + */ + private boolean disableFrameOptions = true; + /** * 启用禁用CSRF */ diff --git a/springboot-starter/pom.xml b/springboot-starter/pom.xml index d8df9f02..fd60a122 100644 --- a/springboot-starter/pom.xml +++ b/springboot-starter/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot springboot-parent - 3.3.70 + 3.4.4 springboot-starter diff --git a/springboot-starter/src/main/resources/banner.txt b/springboot-starter/src/main/resources/banner.txt index 861f409d..cb247fec 100644 --- a/springboot-starter/src/main/resources/banner.txt +++ b/springboot-starter/src/main/resources/banner.txt @@ -1,4 +1,4 @@ ------------------------------------------------------ -CodingApi SpringBoot-Starter 3.3.70 +CodingApi SpringBoot-Starter 3.4.4 springboot version (${spring-boot.version}) ------------------------------------------------------