流水线模式介绍
通过流水线模式使代码具有职责单一、移植性高、扩展性强、代码复用性强、代码清晰。
将一个业务逻辑拆分为若干个小的逻辑单元,之后将这些小的逻辑单元按顺序添加到管道中,这样就组成一个有顺序的管道。
职责单一:将一个大的业务逻辑拆分为若干个小的逻辑单元。
移植性高:每个小的逻辑单元可以通过可拔插方式添加移除到管道中。
扩展性强:增强业务逻辑,只需向管道添加小的逻辑单元即可。
代码复用性强:将业务逻辑封装在若干个小的逻辑单元中,使得每一个单元块只聚合某一点功能,复用性大大增加。
代码清晰:将业务逻辑封装在若干个小的逻辑单元,使得代码聚合,如果代码有改动只需要改动某一个小的单元,而避免影响这个业务逻辑。
工作使用场景
功能简介
列表查询/查询详情/业务系统用户菜单权限/业务系统查询全部菜单 ,他们都共用到MenuEntityToMenuDtoList这个单元块,这里体现到代码复用性强,又因为MenuEntityToMenuDtoList中职责单一和聚合才体现代码复用性强。
列表查询
列表查询功能:首先从库中查询出菜单集合 ,之后将数据库实体对象MenuEntity转换为MenuDto,之后是对MenuDto的属性设置(SetResourceToMenuList/AddRecouceButtonToMenuList),接下来是校验用户是否对此采用有操作权限,最后是将MenuDto转换为菜单树。
QueryMenuList(从库中查询出菜单列表) -> MenuEntityToMenuDtoList(将MenuEntity实体转换为 MenuDto) -> SetResourceToMenuList(设置MenuDto关联表Resource额外属性,增强MenuDto) -> AddRecouceButtonToMenuList(添加资源按钮,增强MenuDto) -> CheckMenuResoucePerimissionList (校验用户是否对此菜单有操作权限) -> MenuListToTree(MenuDto转换菜单树)
查询详情
MenuById(从库中通过id查询出菜单) -> MenuEntityToMenuDtoList(将MenuEntity实体转换为
MenuDto) -> SetResourceToMenuList(设置MenuDto关联表Resource额外属性,增强MenuDto)
保存
业务系统用户菜单权限
业务系统查询全部菜单
代码设计
流水线代码
public class Pipeline<I,O> {
private final Handler<I,O> currentHandler;
public Pipeline(Handler<I, O> currentHandler) {
this.currentHandler = currentHandler;
}
public <K> Pipeline<I, K> addHandler(Handler<O, K> newHandler) {
return new Pipeline<>(input -> newHandler.process(currentHandler.process(input)));
}
public boolean before(Object o){
return true;
}
public void after(O o){
}
public O execute(I input) {
return currentHandler.process(input);
}
}
public interface Handler<I,O> {
O process(I input);
}
菜单列表查询代码
public class QueryMenuList implements Handler<String, List<SyMenu>> {
@Override
public List<SyMenu> process(String input) {
SyMenuMapper syMenuMapper = SpringApplicationUtils.getBean(SyMenuMapper.class);
QueryWrapper qw = Wrappers.query().eq("service_id", CurrentUserContext.getServiceId());
qw.eq("menu_source",input);
List<SyMenu> menus = syMenuMapper.selectList(qw);
return menus;
}
}
public class MenuEntityToMenuDtoList implements Handler<List<SyMenu>, List<SyMenuDto>> {
@Override
public List<SyMenuDto> process(List<SyMenu> input) {
if (CollectionUtils.isEmpty(input)){
return Collections.emptyList();
}
List<SyMenuDto> dtos = input.stream().map(v ->{
SyMenuDto dto = new SyMenuDto();
BeanUtils.copyProperties(v,dto);
dto.setResourceId(v.getResourceId());
dto.setId(v.getId());
dto.setParentId(v.getParentId());
return dto;
}).collect(Collectors.toList());
return dtos;
}
}
public class SetResourceToMenuList implements Handler<List<SyMenuDto>, List<SyMenuDto>> {
@Override
public List<SyMenuDto> process(List<SyMenuDto> input) {
if (CollectionUtils.isEmpty(input)){
return Collections.emptyList();
}
ResourceMapper resourceMapper = SpringApplicationUtils.getBean(ResourceMapper.class);
for (SyMenuDto dto : input){
if (dto.getResourceId() != null && dto.getResourceId() != -1){
SyResource resource = resourceMapper.selectById(dto.getResourceId());
if (resource != null){
dto.setResourceName(resource.getResourceName());
}
}
}
return input;
}
}
/**
*添加资源按钮,查找资源下按钮,生成一条按钮menu记录
* Created by Jiyang.Zheng on 2022/4/19 13:29.
*/
@Slf4j
public class AddRecouceButtonToMenuList implements Handler<List<SyMenuDto>, List<SyMenuDto>> {
@Override
public List<SyMenuDto> process(List<SyMenuDto> input) {
ResourceMapper resourceMapper = SpringApplicationUtils.getBean(ResourceMapper.class);
if (CollectionUtils.isEmpty(input)){
return Collections.emptyList();
}
List<Integer> resourceIds = input.stream().filter(v -> v.getResourceId() != null).map(v -> v.getResourceId()).collect(Collectors.toList());
if (CollectionUtils.isEmpty(resourceIds)){
return Collections.emptyList();
}
List<SyMenuDto> newMenuDto = Lists.newArrayList();
newMenuDto.addAll(input);
Map<Integer,List<Integer>> resouceUseMenuIds = getResouceUseMenuIds(input);
List<SyResource> resouces = resourceMapper.selectBatchIds(resourceIds);
for (SyResource resource : resouces){
QueryWrapper qw = Wrappers.query().eq("parent_id",resource.getId()).eq("resource_type","button");
List<SyResource> buttonResouces = resourceMapper.selectList(qw);
if (CollectionUtils.isEmpty(buttonResouces)){
continue;
}
List<Integer> menuIds = resouceUseMenuIds.get(resource.getId());
if (CollectionUtils.isEmpty(menuIds)){
continue;
}
for (Integer parentId : menuIds){
double i = 0;
for (SyResource br : buttonResouces){
SyMenuDto bn = new SyMenuDto();
BeanUtils.copyProperties(br,bn);
bn.setParentId(parentId);
bn.setMenuName(br.getResourceName());
bn.setMenuType("button");
bn.setSortNum(i);
bn.setWatermark(0);
bn.setResourceId(br.getId());
newMenuDto.add(bn);
i++;
}
}
}
return newMenuDto;
}
private Map<Integer,List<Integer>> getResouceUseMenuIds(List<SyMenuDto> input){
//标识资源拥有菜单
Map<Integer,List<Integer>> resouceUseMenuIds = Maps.newHashMap();
for (SyMenuDto md : input){
List<Integer> l = resouceUseMenuIds.get(md.getResourceId());
if (l == null){
l = new ArrayList<>();
resouceUseMenuIds.put(md.getResourceId(),l);
}
l.add(md.getId());
}
return resouceUseMenuIds;
}
}
public class CheckMenuResoucePerimissionList implements Handler<List<SyMenuDto> , List<SyMenuDto> > {
@Override
public List<SyMenuDto> process(List<SyMenuDto> input) {
if(CollectionUtils.isEmpty(input)){
return Collections.emptyList();
}
Handler<Void, List<SyResourceDto>> handler = new MenuResouceQueryList(MenuResoucePerimissionDataList.QUERY_PERIMISSION_BACK);
Pipeline pipeline = new Pipeline<>(handler);
List<SyResourceDto> list = (List<SyResourceDto>) pipeline.execute(null);
if (CollectionUtils.isNotEmpty(list)){
Map<Integer,SyResourceDto> map = list.stream().distinct().collect(Collectors.toMap(v -> v.getId(), v ->v));
input.stream().forEach(v ->{
if (!StringUtils.equals(v.getMenuType(),"page")){
v.setIsUsePerimission(true);
}else {
SyResourceDto syResourceDto = map.get(v.getResourceId());
if (syResourceDto != null){
v.setIsUsePerimission(true);
}
}
});
}
return input;
}
}
public class MenuListToTree implements Handler<List<SyMenuDto>, List<TreeModel>> {
@Override
public List<TreeModel> process(List<SyMenuDto> input) {
List<TreeModel> treeModels = AbstarctGenerateTree.buidTree(input);
return treeModels;
}
}
public static void main(String[] args) {
String menuSource = "";
Handler<String, List<SyMenu>> handler = new QueryMenuList();
Pipeline pipeline = new Pipeline<>(handler)
// .addHandler(new RecursionParentMenuList())
.addHandler(new MenuEntityToMenuDtoList())
.addHandler(new SetResourceToMenuList())
.addHandler(new AddRecouceButtonToMenuList())
.addHandler(new CheckMenuResoucePerimissionList())
.addHandler(new MenuListToTree());
List<TreeModel> treeModels = (List<TreeModel>) pipeline.execute(menuSource);
}
特殊需求扩展代码
继承Pipeline重新Pipeline内部方法before
public class EditResouceMenuPipeline<I, O> extends Pipeline<I, O> {
private final Handler<I, O> currentHandler;
public EditResouceMenuPipeline(Handler<I, O> currentHandler) {
super(currentHandler);
this.currentHandler = currentHandler;
}
@Override
public <K> EditResouceMenuPipeline<I, K> addHandler(Handler<O, K> newHandler) {
return new EditResouceMenuPipeline<>(input -> newHandler.process(currentHandler.process(input)));
}
@Override
public boolean before(Object o) {
SyResource resource = (SyResource) o;
boolean b = checkEdit(resource);
if (b) {
throw BusinessException.withErrorMessage(resource.getResourceName() + ":已存在");
}
return false;
}
private boolean checkEdit(SyResource syResource) {
ResourceMapper resourceMapper = SpringApplicationUtils.getBean(ResourceMapper.class);
QueryWrapper qw = Wrappers.query()
.eq("resource_name", syResource.getResourceName())
.eq("resource_type", ResourceServiceImpl.RESOURCE_TYPE_PAGE)
.eq("service_id", CurrentUserContext.getServiceId());
SyResource selectOne = resourceMapper.selectOne(qw);
if (selectOne != null) {
boolean flag = true;
if (syResource.getId() != null && ObjectUtils.equals(selectOne.getId(), syResource.getId())) {
flag = false;
}
return flag;
}
return false;
}
}
public Integer save(SyResource resource) {
Handler<SyResource, SyResource> handler = new EditAppIcon();
Pipeline pipeline = new EditResouceMenuPipeline<>(handler)
.addHandler(new EditMenuName())
.addHandler(new EditWebIcon())
.addHandler(new EditMenuResouce());
if (pipeline.before(resource)) {
throw BusinessException.withErrorMessage(resource.getResourceName() + ":已存在");
}
Integer id = (Integer) pipeline.execute(resource);
return id ;
}
pipeline日志扩展
记录每个流水的入参和出参做日志打印
package com.geely.grms.rms.service.service.pipeline;
import cn.evun.sweet.framework.core.mvc.BusinessException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
/**
*
* Created by Jiyang.Zheng on 2022/4/19 11:09.
*/
@Slf4j
public class Pipeline<I,O> {
private final Handler<I,O> currentHandler;
public Pipeline(Handler<I, O> currentHandler) {
this.currentHandler = currentHandler;
}
public static ThreadLocal<StringBuilder> CurrentLog = new ThreadLocal<>();
public <K> Pipeline<I, K> addHandler(Handler<O, K> newHandler) {
return new Pipeline<>(input -> {
O o = currentHandler.process(input);
StringBuilder sb = CurrentLog.get();
K k;
try {
k = newHandler.process(o);
if (sb == null){
sb = new StringBuilder();
CurrentLog.set(sb);
}
log(sb,currentHandler,input,o);
sb.append(" -> \n");
log(sb,newHandler,o,k);
return k;
}catch (Exception e){
String name = newHandler.getClass().getName();
PipelineHandleName nextat = newHandler.getClass().getAnnotation(PipelineHandleName.class);
if (nextat != null) {
name = nextat.name();
}
if (sb != null){
sb.append(" -> "+e.getMessage());
}
String errorMsg = "执行【"+name+"】异常,param:"+JSONObject.toJSONString(o)+",ErrorMsg:"+e.getMessage();
// log.error(errorMsg,e);
throw new BusinessException(errorMsg,e);
}
});
}
public boolean before(Object o){
return true;
}
public void after(O o){
if (CurrentLog.get() != null){
log.info("pipeline execute log {}",CurrentLog.get().toString());
}
CurrentLog.remove();
}
public O execute(I input) {
O o = currentHandler.process(input);
after(null);
return o;
}
private void log(StringBuilder sb,Object newHandler,Object o,Object k){
PipelineHandleName nextat = newHandler.getClass().getAnnotation(PipelineHandleName.class);
if (nextat != null){
sb.append(nextat.order()+":"+nextat.name());
if (nextat.isParamLog()){
String inStr = JSONObject.toJSONString(o);
String ouStr = JSONObject.toJSONString(k);
sb.append(","+"input【"+inStr+"】,output【"+ouStr+"】");
}
}
}
}
package com.geely.grms.rms.service.service.pipeline;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PipelineHandleName {
/**
* 顺序
* @return
*/
int order() default 1;
/**
* 名称
* @return
*/
String name();
/**
* 是否记录请求参数和返回参数日志
* @return
*/
boolean isParamLog() default true;
}