feat:提交审批逻辑

This commit is contained in:
wangyu 2021-12-10 21:29:56 +08:00
parent 9035681b05
commit 935db3fe59
9 changed files with 339 additions and 67 deletions

View File

@ -0,0 +1,31 @@
package com.flyfish.framework.approval.auditor;
import com.flyfish.framework.approval.domain.ApprovalDomain;
import com.flyfish.framework.approval.enums.ApproveStatus;
import com.flyfish.framework.auditor.ReactiveBeanAuditor;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 审批拦截审计器
* 在用户首次提交审核
*
* @author wangyu
*/
@Component
public class ApproveAuditor implements ReactiveBeanAuditor<ApprovalDomain> {
/**
* 对实体进行审查并补全相关字段
*
* @param data 原数据
* @return 结果
*/
@Override
public Mono<ApprovalDomain> audit(ApprovalDomain data) {
if (null == data.getApproveStatus()) {
data.setApproveStatus(ApproveStatus.DRAFT);
}
return Mono.just(data);
}
}

View File

@ -0,0 +1,88 @@
package com.flyfish.framework.approval.controller;
import com.flyfish.framework.approval.domain.ApprovalDomain;
import com.flyfish.framework.approval.domain.record.ApproveRecord;
import com.flyfish.framework.approval.domain.record.ApproveRecordListVo;
import com.flyfish.framework.approval.domain.record.ApproveRecordQo;
import com.flyfish.framework.approval.domain.submit.ApprovalSubmitDto;
import com.flyfish.framework.approval.domain.todo.ApprovalDto;
import com.flyfish.framework.approval.domain.todo.ApprovalListVo;
import com.flyfish.framework.approval.service.ApproveRecordService;
import com.flyfish.framework.approval.service.ModuleDelegateService;
import com.flyfish.framework.bean.Result;
import com.flyfish.framework.configuration.annotations.CurrentUser;
import com.flyfish.framework.configuration.annotations.PagedQuery;
import com.flyfish.framework.domain.base.Vo;
import com.flyfish.framework.domain.po.User;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 审批专用controller
*
* @author wangyu
*/
@RestController
@RequestMapping("approves")
public class ApprovalController {
@Resource
private ApproveRecordService approveRecordService;
@Resource
private ModuleDelegateService moduleDelegateService;
/**
* 查询视图
*
* @return 结果
*/
@GetMapping("records")
public Mono<Result<List<Vo<ApproveRecord>>>> views(@PagedQuery ApproveRecordQo qo) {
return approveRecordService.getPageList(qo)
.map(Result::accept)
.map(result -> result.mapList((ApproveRecord item) -> new ApproveRecordListVo().from(item)));
}
/**
* 查询待办列表
*
* @param qo 查询实体
* @param user 用户
* @return 结果
*/
@GetMapping("todos")
public Mono<Result<List<Vo<ApproveRecord>>>> todos(@PagedQuery ApproveRecordQo qo, @CurrentUser User user) {
qo.setApprover(user.getId());
qo.setApproved(false);
return approveRecordService.getPageList(qo)
.map(Result::accept)
.map(result -> result.mapList((ApproveRecord item) -> new ApprovalListVo().from(item)));
}
/**
* 提交审批
* 需要指定提交候选人按顺序审批
*
* @return 结果
*/
@PostMapping("submit")
public Mono<Result<ApprovalDomain>> submit(@Valid @RequestBody ApprovalSubmitDto body) {
return moduleDelegateService.submit(body).map(Result::ok);
}
/**
* 审批数据
*
* @param approval 数据
* @return 结果
*/
@PostMapping("approval")
public Mono<Result<ApprovalDomain>> approval(@RequestBody ApprovalDto approval) {
return moduleDelegateService.approval(approval).map(Result::ok);
}
}

View File

@ -1,22 +1,10 @@
package com.flyfish.framework.approval.controller; package com.flyfish.framework.approval.controller;
import com.flyfish.framework.approval.domain.ApprovalDomain;
import com.flyfish.framework.approval.domain.record.ApproveRecord; import com.flyfish.framework.approval.domain.record.ApproveRecord;
import com.flyfish.framework.approval.domain.record.ApproveRecordListVo;
import com.flyfish.framework.approval.domain.record.ApproveRecordQo; import com.flyfish.framework.approval.domain.record.ApproveRecordQo;
import com.flyfish.framework.approval.domain.todo.ApprovalListVo;
import com.flyfish.framework.approval.service.ModuleDelegateService;
import com.flyfish.framework.bean.Result;
import com.flyfish.framework.configuration.annotations.CurrentUser;
import com.flyfish.framework.configuration.annotations.PagedQuery;
import com.flyfish.framework.controller.reactive.ReactiveBaseController; import com.flyfish.framework.controller.reactive.ReactiveBaseController;
import com.flyfish.framework.domain.base.Vo; import org.springframework.web.bind.annotation.RequestMapping;
import com.flyfish.framework.domain.po.User; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
/** /**
* 审批记录控制器 * 审批记录控制器
@ -24,52 +12,7 @@ import java.util.List;
* @author wangyu * @author wangyu
*/ */
@RestController @RestController
@RequestMapping("approves") @RequestMapping("approve-records")
public class ApproveRecordController extends ReactiveBaseController<ApproveRecord, ApproveRecordQo> { public class ApproveRecordController extends ReactiveBaseController<ApproveRecord, ApproveRecordQo> {
@Resource
private ModuleDelegateService moduleDelegateService;
/**
* 查询视图
*
* @return 结果
*/
@GetMapping("records")
public Mono<Result<List<Vo<ApproveRecord>>>> views(@PagedQuery ApproveRecordQo qo) {
return reactiveService.getPageList(qo)
.map(Result::accept)
.map(result -> result.mapList((ApproveRecord item) -> new ApproveRecordListVo().from(item)));
}
/**
* 查询待办列表
*
* @param qo 查询实体
* @param user 用户
* @return 结果
*/
@GetMapping("todos")
public Mono<Result<List<Vo<ApproveRecord>>>> todos(@PagedQuery ApproveRecordQo qo, @CurrentUser User user) {
qo.setApprover(user.getId());
qo.setApproved(false);
return reactiveService.getPageList(qo)
.map(Result::accept)
.map(result -> result.mapList((ApproveRecord item) -> new ApprovalListVo().from(item)));
}
/**
* 通用审批
*
* @param module 模块
* @param dataId 数据id
* @return 结果
*/
@PostMapping("approval/{module}/{dataId}")
public Mono<Result<ApprovalDomain>> approval(@PathVariable String module, @PathVariable String dataId,
@RequestBody ApproveRecord record) {
record.setModule(module);
record.setDataId(dataId);
return moduleDelegateService.approval(record).map(Result::ok);
}
} }

View File

@ -23,5 +23,5 @@ public abstract class ApprovalDomain extends AuthorizedDomain {
private List<String> approvers; private List<String> approvers;
// 下个审批人 // 下个审批人
private String nextApprover; private Integer next;
} }

View File

@ -0,0 +1,28 @@
package com.flyfish.framework.approval.domain.submit;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 审批提交数据
*
* @author wangyu
*/
@Data
public class ApprovalSubmitDto {
// 数据主键
@NotBlank(message = "数据主键不可为空!")
private String id;
// 数据模块
@NotBlank(message = "数据模块不可为空!")
private String module;
// 待审批人列表
@NotEmpty(message = "待审批人不可为空!")
private List<String> approvers;
}

View File

@ -0,0 +1,25 @@
package com.flyfish.framework.approval.domain.todo;
import com.flyfish.framework.approval.enums.ApproveAction;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 审批dto
*
* @author wangyu
*/
@Data
public class ApprovalDto {
@NotBlank(message = "审批记录id不可为空")
private String id;
@NotNull(message = "审批操作不可为空!")
private ApproveAction action;
@NotNull(message = "审批意见不可为空!")
private String opinion;
}

View File

@ -6,13 +6,14 @@ import lombok.Getter;
/** /**
* 审批状态 * 审批状态
*
* @author wangyu * @author wangyu
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum ApproveStatus implements NamedEnum { public enum ApproveStatus implements NamedEnum {
PENDING("待审批"), APPROVING("审批中"), APPROVED("已审批"), REJECTED("已拒绝"); DRAFT("草稿"), PENDING("待审批"), APPROVING("审批中"), APPROVED("已审批"), REJECTED("已拒绝");
private final String name; private final String name;
} }

View File

@ -2,14 +2,24 @@ package com.flyfish.framework.approval.service;
import com.flyfish.framework.approval.domain.ApprovalDomain; import com.flyfish.framework.approval.domain.ApprovalDomain;
import com.flyfish.framework.approval.domain.record.ApproveRecord; import com.flyfish.framework.approval.domain.record.ApproveRecord;
import com.flyfish.framework.approval.domain.submit.ApprovalSubmitDto;
import com.flyfish.framework.approval.domain.todo.ApprovalDto;
import com.flyfish.framework.approval.enums.ApproveAction;
import com.flyfish.framework.approval.enums.ApproveStatus;
import com.flyfish.framework.beans.meta.RestBean;
import com.flyfish.framework.domain.base.DomainService; import com.flyfish.framework.domain.base.DomainService;
import com.flyfish.framework.exception.biz.InvalidBusinessException;
import com.flyfish.framework.service.BaseReactiveService; import com.flyfish.framework.service.BaseReactiveService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -21,6 +31,9 @@ import java.util.stream.Collectors;
@Service @Service
public class ModuleDelegateService { public class ModuleDelegateService {
@Resource
private ApproveRecordService approveRecordService;
// 审批的服务们 // 审批的服务们
private Map<String, BaseReactiveService<ApprovalDomain>> approvalServices; private Map<String, BaseReactiveService<ApprovalDomain>> approvalServices;
@ -41,12 +54,154 @@ public class ModuleDelegateService {
} }
/** /**
* 提交审批 * 审批数据
* @param record 记录 *
* @param approval 记录
* @return 结果 * @return 结果
*/ */
public Mono<ApprovalDomain> approval(ApproveRecord record) { @Transactional(rollbackFor = Exception.class)
getService(record.getModule()).getById(record.getDataId()) public Mono<ApprovalDomain> approval(ApprovalDto approval) {
.map(domain -> domain) return approveRecordService.getById(approval.getId())
// 保存记录
.flatMap(record -> {
if (record.getApproved()) {
return Mono.error(new InvalidBusinessException("该审批已被处理,请勿重复操作!"));
}
record.setApproved(true);
record.setAction(approval.getAction());
record.setOpinion(approval.getOpinion());
return approveRecordService.updateById(record);
})
// 修改主记录
.flatMap(record -> {
BaseReactiveService<ApprovalDomain> service = getService(record.getModule());
return service.getById(record.getDataId())
.flatMap(domain -> {
// 如果已经审批过直接返回
if (domain.getApproveStatus() == ApproveStatus.APPROVED) {
return Mono.error(new InvalidBusinessException("该审批已被处理,请勿重复操作!"));
}
// 尚未提交审批不处理
if (domain.getApproveStatus() == ApproveStatus.DRAFT || domain.getApproveStatus() == ApproveStatus.REJECTED) {
return Mono.error(new InvalidBusinessException("该数据尚未提交审批,请确认后再操作!"));
}
// 终止标记
boolean end = false;
// 审批人
String approver = null;
// 尝试流转
switch (record.getAction()) {
case AGREE:
String next = determineNext(domain);
// 下一个为空代表审批完了改状态
if (null == next) {
domain.setApproveStatus(ApproveStatus.APPROVED);
domain.setNext(-1);
end = true;
} else {
domain.setApproveStatus(ApproveStatus.APPROVING);
domain.setNext(domain.getNext() + 1);
approver = next;
}
break;
case REJECT:
// 拒绝如果已经是第一步了设置拒绝状态
if (domain.getNext() == 0) {
domain.setApproveStatus(ApproveStatus.REJECTED);
end = true;
} else {
domain.setNext(domain.getNext() - 1);
approver = domain.getApprovers().get(domain.getNext());
}
break;
case REJECT_ALL:
// 直接打回拒绝状态需要重新修改提交
domain.setNext(0);
domain.setApproveStatus(ApproveStatus.REJECTED);
end = true;
break;
}
// 没结束
if (!end) {
// 构建下一步操作
ApproveRecord nextRecord = new ApproveRecord();
nextRecord.setModule(record.getModule());
nextRecord.setDataId(record.getId());
nextRecord.setApproved(false);
nextRecord.setModuleName(moduleName(domain.getClass()));
nextRecord.setName(operation(approval.getAction()));
nextRecord.setApprover(approver);
return approveRecordService.create(nextRecord)
.flatMap(r -> service.updateById(domain));
}
return service.updateById(domain);
});
});
}
/**
* 提交审核
*
* @param data 数据
* @return 结果
*/
public Mono<ApprovalDomain> submit(ApprovalSubmitDto data) {
BaseReactiveService<ApprovalDomain> service = getService(data.getModule());
return service.getById(data.getId())
.flatMap(domain -> {
domain.setApprovers(data.getApprovers());
domain.setApproveStatus(ApproveStatus.PENDING);
domain.setNext(0);
return service.updateById(domain);
})
.flatMap(domain -> {
ApproveRecord record = new ApproveRecord();
record.setName("提交审核");
record.setModule(data.getModule());
record.setDataId(data.getId());
record.setApproved(false);
record.setApprover(domain.getApprovers().get(domain.getNext()));
record.setModuleName(moduleName(domain.getClass()));
return approveRecordService.create(record).thenReturn(domain);
});
}
/**
* 获取模块名称
*
* @param clazz
* @return 结果
*/
private String moduleName(Class<?> clazz) {
return Optional.ofNullable(AnnotationUtils.findAnnotation(clazz, RestBean.class))
.map(RestBean::name).orElse("未知模块");
}
/**
* 决定下一位审批人
*
* @param domain 审批实体
* @return 结果
*/
private String determineNext(ApprovalDomain domain) {
// 获取当前位置
int index = domain.getNext();
int next = index + 1;
// 下一个位置
if (next < domain.getApprovers().size()) {
return domain.getApprovers().get(next);
}
return null;
}
/**
* 操作中文名
*
* @param action 操作
* @return 结果
*/
private String operation(ApproveAction action) {
return "将审批进行了\"" + action.getName() + "\"操作";
} }
} }

View File

@ -74,6 +74,7 @@ public class BeanController {
BeanInfo info = new BeanInfo(); BeanInfo info = new BeanInfo();
if (clazz.isAnnotationPresent(RestBean.class)) { if (clazz.isAnnotationPresent(RestBean.class)) {
RestBean annotation = clazz.getAnnotation(RestBean.class); RestBean annotation = clazz.getAnnotation(RestBean.class);
info.setType(clazz.getSuperclass().getSimpleName());
info.setName(annotation.name()); info.setName(annotation.name());
info.setCode(annotation.value()); info.setCode(annotation.value());
info.setSearch(BeanProperty.from(annotation.queryClass())); info.setSearch(BeanProperty.from(annotation.queryClass()));