业务场景
普通的申报审批流程,申报提交,后由其他人进行决定通过,不通过,退回等操作,不同业务执行不同的程序,有的还会跨服务去调取其他系统等等,
问题
之前的老项目实在是难以看,到处是if-else,代码十分冗余,写到一个service导致后期业务越来越繁多时变得难以阅读,上面注入的mapper和servcie可达十几种。
现在新项目也有类似功能,因为这种套路以后基本上也是大差不差的,这里记录一下刚刚测试的思路,感觉这种模式可以应对大部分业务情况。
流程
前台或者其他系统调取接口,传来需要查询的业务类型(比如A31,A38这种)和申报的ID,并且携带一个ID,指定这是哪个库,哪个业务下的ID等等
大致结构
approval是审批的接口,就是有多少个业务就有多少种实现接口,A31这是数据库名称
entity是实体类,后台根据数据库创建一些对应的实体类
mapper是相关myabtis-plus查询接口
report是申报,由于不同表或者别的系统里的业务的字段名都不一样,统一用接口管理方法
service是代替之前写了大量业务流程的服务
代码
1.审批
首先是关于审批的部分,所有审批都有的功能:提交某个申报,通过某个申报,拒绝某个申报,退回某个申报,根据面对接口编程思想(开放-关闭原则),创建Approval接口
考虑到以后要判断携带的code状态来判断执行哪个流程,可以再创建一个申报接口,再实现四种申报实现类,但这样做又不合理,申报种类十几种,管理起来也会费劲,这里干脆直接通过模板方法统一判断执行就可以。
所以修改Approval为抽象类,通过public和抽象类的默认 protected,对外只需暴露出execute这一个方法去执行即可,其他内部方法无需给外界展示。
@Data
public abstract class Approval {
public void execute(Report report){
switch (report.getCode()){
case "1":
pass(report);
break;
case "2":
noPass(report);
break;
case "3":
back(report);
break;
case "4":
submit(report);
break;
}
}
abstract void pass(Report report);
abstract void noPass(Report report);
abstract void back(Report report);
abstract void submit(Report report);
}
A31申报的实现接口,这里加入一个SysUserMapper是随意加的,意味着不同业务他注入的mapper、service也是不同的,然后根据自己的情况调用服务执行业务即可。
@Component
public class A31Approval extends Approval {
@Autowired
private SysUserMapper sysUserMapper;
@Override
void pass(Report report) {
System.out.println("A31通过了报告");
}
@Override
void noPass(Report report) {
System.out.println("A31拒绝通过了报告");
}
@Override
void back(Report report) {
System.out.println("A31退回了报告");
}
@Override
void submit(Report report) {
System.out.println("A31提交了报告");
}
}
同理这是另外的A38业务的申报,里面注入了不同的服务,用两个来对比。
@Component
public class A38Approval extends Approval {
private A31Mapper a31Mapper;
private A38Mapper a38Mapper;
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private FileBaseMapper fileBaseMapper;
@Override
void pass(Report report) {
System.out.println("A38通过了报告");
}
@Override
void noPass(Report report) {
System.out.println("A38拒绝通过了报告");
}
@Override
void back(Report report) {
System.out.println("A38退回了报告");
}
@Override
void submit(Report report) {
System.out.println("A38提交了报告");
}
}
2.申报类
因为不仅本系统的申报,还有其他系统里的申报有时候也需要进行处理,不同数据库,不通字段,多的让人分不清哪个是哪个,干脆统一继承Report接口,统一都用接口的方法返回值,下次再取值的时候就不会分不清字段了,当然这里还能更加细分,现在只是随意举例出几个熟悉。
public interface Report {
String getTableName();
String getId();
String getTypeId();
String getTypeDetailId();
String getCode();
}
既然只是前置组织数据,分开写到每个service里不太合适吧,因为前台跟服务器是同一调用一个接口来返回数据的,都需要根据传来的参数进行数据查询,组织好对象后进行进一步判断,这里就创建一个工厂类同一组织数据,注意,这里距离只是返回一个Report声明,但是实际情况可能是很大不同,比如有的可能需要额外的List数据,这个时候只需要修改Report的继承类,添加新的属性,对外同一返回接口声明,不影响其他业务。
@Component
public class ReportFactory {
private A31Mapper a31Mapper;
private A38Mapper a38Mapper;
public Report getReport(AccessEntity accessEntity){
String tableName = accessEntity.getTableName();
String id = accessEntity.getId();
switch (tableName){
case "A38":
//A38 a38 = a38Mapper.selectById(id);
A38 a38 = new A38();
a38.setId("1");
a38.setA3804("a38_A3804");
a38.setA3808("a38_a3808");
a38.setCode(accessEntity.getCode());
return a38;
case "A31":
//A31 a31 = a31Mapper.selectById(id);
A31 a31 = new A31();
a31.setId("2");
a31.setA3104("a31_A3104");
a31.setA3108("a31_a3108");
a31.setCode(accessEntity.getCode());
return a31;
default:
return null;
}
}
}
3.实体类
根据数据库表创建不同的实体对象,没什么可说的,这里直接继承Report接口,实现接口的对象,不管是本数据库还是其他系统的对象,均通过Report里返回:业务上相同,命名上不同的属性。
package com.test.restructure.entity;
import com.test.restructure.report.Report;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class A38 implements Serializable, Report {
private String code;
private String id;
private String orgId;
private BigDecimal rState;
private BigDecimal a01State;
public BigDecimal getA01State() {
return a01State;
}
public void setA01State(BigDecimal a01State) {
this.a01State = a01State;
}
private BigDecimal sSort;
private BigDecimal aState;
private Date aTime;
private Date createtime;
private String operator;
private Date updatetime;
private String updater;
private String a3801;
private String a3802;
private Date a3803;
private String a3804;
private String a3805;
private String a3806;
private String a3807;
private String a3809;
private String a3808;
private String a3899;
private String a3803String;
private static final long serialVersionUID = 1L;
@Override
public String getTableName() {
return "A38";
}
@Override
public String getTypeId() {
return a3804;
}
@Override
public String getTypeDetailId() {
return a3808;
}
}
同理,创建另外一个用来对比
@Data
public class A31 implements Serializable, Report {
private String code;
private String id;
private String orgId;
private String m01Id;
private String a3101;
private String a3102;
private Date a3103;
private String a3104;
private String a3105;
private String a3106;
private String a3107;
private BigDecimal rState;
private BigDecimal rSort;
private BigDecimal aState;
private Date aTime;
private Date createtime;
private String operator;
private Date updatetime;
private String updater;
private String a3109;
private String a3108;
private String a3199;
private static final long serialVersionUID = 1L;
@Override
public String getTableName() {
return "A31";
}
@Override
public String getTypeId() {
return a3104;
}
@Override
public String getTypeDetailId() {
return a3108;
}
}
这个是用来接收数据的,没别的意义
package com.test.restructure.entity;
import lombok.Data;
@Data
public class AccessEntity {
private String id;
private String tableName;
private String code;
}
4.流程
如果是后台rpc服务,换一些注解就行了,反正都是接收数据用的。
@RestController
@RequestMapping("/controller")
public class TestController {
@Autowired
private ApprovalExecuteService approvalExecuteService;
@RequestMapping("/approval")
public void approval(AccessEntity accessEntity){
approvalExecuteService.execute(accessEntity);
}
}
在这个 ApprovalExecuteService 统一对外接收数据,统一管理各个审批单例,不关心每个业务的内部情况,对外只暴露出execute方法,若有新的业务出现只需要再添加Approval实现,修改也只需要修改每个Approval实现类里的各自功能,方便管理。这里直接用swtich返回
根据常见的策略模式,这里就是根据情况new的,例子没考虑到实际情况肯定不是new的也得是从容器里用原型复制出来的吧,但是这样似乎就是多例了,我虽然不了解原型模式效率如何,但是应该也会继承动态注入组建的,这样直接判断执行其实也OK吧。
@Component
public class ApprovalExecuteService {
@Autowired
private A31Approval a31Approval;
@Autowired
private A38Approval a38Approval;
@Autowired
private ReportFactory reportFactory;
public void execute(AccessEntity accessEntity){
Report report = reportFactory.getReport(accessEntity);
executeApproval(report);
}
void executeApproval(Report report){
switch (report.getTableName()){
case "A38":
a38Approval.execute(report);
break;
case "A31":
a31Approval.execute(report);
break;
default:
break;
}
}
}
测试
打开浏览器,输入:http://localhost:8080/controller/approval?tableName=A31&code=2
结果打印:A31拒绝通过了报告
输入:http://localhost:8080/controller/approval?tableName=A38&code=3
结果打印:A38退回了报告
符合预期目标,以后按照此套路可以优化很多类似场景,只需要对业务进行抽象归纳。