支付系统设计二:统一开发框架

news2024/9/20 14:25:10

文章目录

  • 前言
  • 一、项目分层
  • 二、模块职责简介
    • 1. API层
    • 2. Service层
      • 2.1 操作执行服务
      • 2.2 操作器
      • 2.3 操作执行器
      • 2.4 参数校验
      • 2.5 操作器实现
    • 3. Domain层
    • 4. Infrastructure层
      • 4.1 Dal层
  • 三、对应类图
  • 四、开发内容
    • 3.1 约定请求报文格式
    • 3.2 新增交易码与操作器映射枚举类
    • 3.3 配置参数校验Yml
    • 3.4 编写操作器(核心工作内容)
    • 3.5 编写仓储和实体
  • 总结


前言

在开始一个新项目建设,进入开发环节之前,容易忽略一项非常重要的环节统一开发框架。对于代码的编写,是开发人员各展拳脚还是制定约束规范,前置的结果是项目个子系统代码风格各异,开发人员个人能力直接体现到了项目代码中,能力稍微差的项目代码惨不忍睹,后期的维护扩展令人担忧,这种结果是我们都不愿看到的。

对于后者书面的约定规范真的起作用么,也不尽然,就像马路中间树立着禁止穿越马路警示,很多人还是当做没看到,所以最直接的办法就是加栅栏,强制限制,当然翻越栅栏的也不再少数,但是我们已经阻止了多数违规。

所以,在进入开发阶段,我们对新建的项目的各个子系统的应用框架进行统一化,即在系统新建初期就定下项目的分层,以及依赖的包版本,对同一业务场景的统一处理方案,以及最基本的非业务层面的代码逻辑等等,通过层层限制,来规范统一化代码风格,让开发人员集中精力完成系统的业务编写。

本篇将讲述自己在工作中所使用的开发框架以供参考(融入DDD思想)。


一、项目分层

按照DDD分层将系统分为四层,基础设施层包含多个模块,其余三层各对应一个模块:
在这里插入图片描述
各模块之间的依赖关系如下:
在这里插入图片描述
案例项目实际模块分层如下:
在这里插入图片描述

二、模块职责简介

下面以后台配置接口(路由规则条件配置)为例简要介绍下每个模块所包含的包以及各模块职责,对应的后台管理系统页面如下:
在这里插入图片描述
输入配置信息,点击提交,请求到后台的报文如下:

 {
	  "header": {
	    "channelCode": "",
	    "channelDateTime": "",
	    "channelNo": "",
	    "transCode": "ruleConfig",
	    "transType": "add"
	  },
	  "body": {
	    "ruleScene": "routerRule",
	    "configKey": "produce_code",
	    "configName": "产品码",
	    "configType": "INPUT",
	    "isScript": "N"
	  }
}

注:
transCode:交易码(业务类型)对应到后台代码枚举如下:

public enum ManagerTransCodeEnum {
    // 后台操作
    ruleConfig("ruleConfig", "ruleConfigOperator", "规则配置"),
    // 运行时...
}

transType:交易类型(增删改查操作)对应到后台代码枚举如下:

public enum ManagerTransTypeEnum {
    ADD("add","addExecutor" ,"新增"),
    MODIFY("modify","modifyExecutor" ,"修改"),
    DELETE("delete","deleteExecutor" , "删除"),
    QUERY("query", "queryExecutor" ,"单笔查询"),
    LIST("list", "listExecutor" ,"批量查询"),
    IMPORT("import","importExecutor" , "导入"),
    EXPORT("export","exportExecutor" , "导出");
    // ... ...
}

1. API层

一个系统对外提供的接口可以归为两类,提供于后台管理系统进行配置信息配置的接口运行时对外提供服务的接口,我们对这两类接口进行收敛,即就只定义两个接口(理想情况下,具体实践还是要依据实际情况)。

接口定义如下:

在这里插入图片描述
控制层抽象类 AbstractController 定义通用处理方法 handle 以及业务方法模板 handleRequestInternal 如下:

/**
 * @author Kkk
 * @Describe: controller抽象模板
 */
public abstract class AbstractController implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(AbstractController.class);

    private ApplicationContext applicationContext;

    /**
     * 请求处理
     * @param request
     * @param response
     * @throws Exception
     */
    public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //1、前置处理
        PayCoreContext context = preHandle(request, response);

        //2、业务处理;
        handleRequestInternal(context);

        //3、后置处理;
        postHandle(request, response, context);
    }

    /**
     * 前置处理
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public PayCoreContext preHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //1.创建context
        PayCoreContext context = createContext(request, response);

        //2.填充context
        fillContext(request, response, context);

        return context;
    }

    /**
     * 后置处理
     * @param request
     * @param response
     * @param context
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, PayCoreContext context) throws Exception {
        //1.构造model
        Map<String, ?> model = buildModel(request, response, context);

        //2.解析视图
        String viewName = resolveView();
        View view = getView(viewName);

        //3.渲染视图
        view.render(model, request, response);
    }
	... ...
}

请求处理核心方法:preHandle、handleRequestInternal、postHandle

 //1、前置处理
 PayCoreContext context = preHandle(request, response);
 //2、业务处理;
 handleRequestInternal(context);
 //3、后置处理;
 postHandle(request, response, context);

preHandle:前置处理,构建请求的上下文,并解析上送报文填充到上下文中;
handleRequestInternal:具体业务处理由子类(BackstageController、RuntimeController)实现;
postHandle:后置处理,构造model,解析视图,渲染视图;

后台管理控制类实现 BackstageController 如下:

/**
 * @author Kkk
 * @Describe: 后台管理系统入口
 */
@RequestMapping(path="/backstage",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@Controller
public class BackstageController extends AbstractController {

    /**
     * 后台服务操作器
     */
    @Resource(name = "backstageOperateExecutorService")
    private OperateExecutorService backstageOperateExecutorService;

    @Override
    @RequestMapping("/service")
    public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        super.handle(request, response);
    }

    @Override
    protected void handleRequestInternal(PayCoreContext context) throws Exception {
        this.backstageOperateExecutorService.execute(context);
    }

    /**
     * 视图解析->后台接口独立视图
     * @return
     */
    @Override
    protected String resolveView() {
        return PayCoreConstant.PAYCORE_JSON_VIEW;
    }
}

2. Service层

在这里插入图片描述

2.1 操作执行服务

定义Service层操作执行服务接口如下:

/**
 * @author Kkk
 * @Description: 操作执行服务,将操作抽象为几类:
 *   1、增加;
 *   2、删除;
 *   3、修改;
 *   4、查询;
 *   5、。。。
 *   每一类操作有一个执行器负责执行,本接口只负责分发;
 */
public interface OperateExecutorService {

    /**
     * 操作执行
     * @param context
     * @throws Exception
     */
    void execute(PayCoreContext context) throws Exception;
}

后台操作执行服务接口实现 BackstageOperateExecutorServiceImpl 如下:

/**
 * @author Kkk
 * @Description: 操作执行服务,将操作抽象为几类:
 *   1、增加;
 *   2、删除;
 *   3、修改;
 *   4、查询;
 *   5、待扩展。。。
 *   每一类操作有一个执行器负责执行,本接口只负责分发;
 */
@Service("backstageOperateExecutorService")
public class BackstageOperateExecutorServiceImpl implements OperateExecutorService, ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(BackstageOperateExecutorServiceImpl.class);

    /**
     * spring 上下文
     */
    private ApplicationContext applicationContext;

    /**
     * 字段验证器
     */
    @Autowired
    private FieldValidateExecutor fieldValidateExecutor;


    @Override
    public void execute(PayCoreContext context) throws Exception {
        MessageEntity<Map<String, Object>> requestEntity = context.getRequestEntity();
        String transCode = requestEntity.getHeader().getTransCode();//交易编码
        String transType = requestEntity.getHeader().getTransType();//交易类型
        AssertUtils.isNotBlank(transCode, SystemErrorCode.TRANS_CODE_UNDEFINED, new Object[]{transCode});
        AssertUtils.isNotBlank(transType, SystemErrorCode.TRANS_TYPE_UNDEFINED, new Object[]{transType});

        //1.交易码校验
        ManagerTransCodeEnum managerTransCodeEnum = ManagerTransCodeEnum.getByCode(transCode);
        AssertUtils.isNotNull(managerTransCodeEnum, SystemErrorCode.TRANS_CODE_UNDEFINED, new Object[]{transCode});
        BackstageOperator backstageOperator = this.applicationContext.getBean(managerTransCodeEnum.getOperator(), BackstageOperator.class);
        AssertUtils.isNotNull(backstageOperator,SystemErrorCode.TRANS_CODE_UNDEFINED, new Object[]{transCode});

        //2.交易类型校验
        ManagerTransTypeEnum managerTransTypeEnum = ManagerTransTypeEnum.getByCode(transType);
        AssertUtils.isNotNull(managerTransTypeEnum,SystemErrorCode.TRANS_CODE_UNDEFINED, new Object[]{transType});

        OperateExecutor operateExecutor = this.applicationContext.getBean(managerTransTypeEnum.getExecutor(), OperateExecutor.class);
        AssertUtils.isNotNull(operateExecutor,SystemErrorCode.TRANS_CODE_UNDEFINED, new Object[]{transType});

        //3.参数格式校验
        this.fieldValidateExecutor.validate(transCode + transType, context.getRequestEntity().getBody());

        //4.交易业务处理
        operateExecutor.execute(backstageOperator, context);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

2.2 操作器

后台管理系统基础操作器 BackstageOperator 如下:

/**
 * @author kkk
 * @Description: 后台管理系统基础操作器
 */
public interface BackstageOperator {

    /**
     * 增加数据对象
     * @param context
     * @throws Exception
     */
    void add(PayCoreContext context) throws Exception;

    /**
     * 增加检测
     * @param context
     * @return
     */
    boolean addCheck(PayCoreContext context);

    /**
     * 删除数据对象
     * @param context
     * @throws Exception
     */
    void delete(PayCoreContext context) throws Exception;

    /**
     * 删除检测
     * @param context
     * @return
     */
    boolean deleteCheck(PayCoreContext context);

    /**
     * 修改数据对象
     * @param context
     * @throws Exception
     */
    void modify(PayCoreContext context) throws Exception;

    /**
     * 修改检测
     * @param context
     * @return
     */
    boolean modifyCheck(PayCoreContext context);

    /**
     * 查询
     * @param context
     */
    void query(PayCoreContext context);

    /**
     * 查询检测
     * @param context
     * @return
     */
    boolean queryCheck(PayCoreContext context);

    /**
     * 列表查询
     * @param context
     * @throws Exception
     */
    void list(PayCoreContext context) throws Exception;

    /**
     * 列表查询检测
     * @param context
     * @return
     */
    boolean listCheck(PayCoreContext context);
}

操作器定义了增删改查方法以及对应的校验方法。

2.3 操作执行器

定义操作执行器 OperateExecutor 如下:

/**
 * @author Kkk
 * @Description: 操作执行器
 */
public interface OperateExecutor {

    /**
     * @Description 操作执行
     * @Params
     * @Return void
     * @Exceptions
     */
    void execute(BackstageOperator operator, PayCoreContext requestParam) throws Exception;
}

操作执行器实现类图如下:

在这里插入图片描述

定义增加操作执行器 AddExecutor 如下:

/**
 * @author kkk
 * @Description: 增加执行器
 */
@Component("addExecutor")
public class AddExecutor implements OperateExecutor {

    @Override
    public void execute(BackstageOperator operator, PayCoreContext requestParam) throws Exception {
        operator.addCheck(requestParam);
        operator.add(requestParam);
    }
}

即在 BackstageOperateExecutorServiceImpl根据交易码获取到基础操作器,根据交易类型获取到操作执行器,进行业务处理,

//4.交易业务处理
operateExecutor.execute(backstageOperator, context);

2.4 参数校验

同时在 BackstageOperateExecutorServiceImpl 还有很重要的一部分,参数校验,因为在定义接口的时候没有在方法中定于特定的实体类,使用一些框架注解进行参数校验,所以在入口处没有进行参数校验,这里使用基于YML文件配置方式进行参数校验。

 //3.参数格式校验
 this.fieldValidateExecutor.validate(transCode + transType, context.getRequestEntity().getBody());

在main层资源配置包下配置参数校验文件:
在这里插入图片描述
如下为路由规则配置新增校验配置:

#路由规则配置新增
ruleConfigadd:
  - field: ruleScene
    fieldName: 规则场景
    enums: RuleSceneEnum
    option: true
    style: codeStyle
  - field: configType
    fieldName: 配置类型
    enums: ConfigTypeEnum
    option: false
    style: codeStyle
  - field: configKey
    fieldName: 配置属性
    option: false
    style: codeStyle
  - field: configName
    fieldName: 配置名称
    option: false
    length: 1,32
  - field: isScript
    fieldName: 是否脚本
    option: false
    enums: BooleanEnum
    regex: ^\w{1,4}$
  - field: scriptContent
    fieldName: 脚本内容
    length: 1,65536
  - field: remark
    fieldName: 备注
    style: descStyle
#路由规则配置删除
ruleConfigdelete:
  - field: id
    fieldName: id
    ints: int
    option: false
    style: idStyle
#路由规则配置修改
ruleConfigmodify:
  - field: ruleScene
    fieldName: 规则场景
    enums: RuleSceneEnum
    option: true
    style: codeStyle
  - field: id
    fieldName: 规则配置ID
    ints: int
    option: false
  - field: configType
    fieldName: 配置类型
    enums: ConfigTypeEnum
    option: false
    style: codeStyle
  - field: configKey
    fieldName: 配置属性
    option: false
    style: codeStyle
  - field: configName
    fieldName: 配置名称
    option: false
    length: 1,32
  - field: isScript
    fieldName: 是否脚本
    option: false
    enums: BooleanEnum
    regex: ^\w{1,4}$
  - field: scriptContent
    fieldName: 脚本内容
    length: 1,65536
  - field: remark
    fieldName: 备注
    style: descStyle
#路由规则配置单笔查询
ruleConfigquery:
  - field: id
    fieldName: id
    option: true
    ints: int
    style: idStyle
#路由规则配置列表查询
ruleConfiglist:
  - field: ruleScene
    fieldName: 规则场景
    enums: RuleSceneEnum
    option: true
    style: codeStyle
  - field: id
    fieldName: id
    ints: int
    style: idStyle
  - field: configKey
    fieldName: 配置编码
    style: codeStyle
  - field: configType
    fieldName: 配置属性
    style: codeStyle
  - field: configName
    fieldName: 配置名称
    length: 1,32
  - field: isScript
    fieldName: 是否脚本
    regex: ^\w{1,4}$
  - field: pageNumber
    fieldName: 页码
    style: pageStyle
  - field: pageSize
    fieldName: 页记录数
    style: pageStyle

#路由规则配置新增:ruleConfigadd
#路由规则配置删除:ruleConfigdelete
#路由规则配置修改:ruleConfigmodify
#路由规则配置单笔查询:ruleConfigquery
#路由规则配置列表查询:ruleConfiglist

本案例中报文上送header中对应字段如下

“transCode”: “ruleConfig”,
“transType”: “add”

transCode+transType"=ruleConfigadd,所以对应于Yml中的 #路由规则配置新增:ruleConfigadd

在这里插入图片描述

为了降低参数校验配置的重复工作,我们抽象出style,如果在某项配置新增、修改中有相同的参数,并且校验规则是相同的,则抽取出来,在对应的校验配置文件中只需要引入style中的对应配置即可。
在这里插入图片描述
参数/字段验证器接口实现类图,已经涵盖了日常开发所涉及的参数校验
在这里插入图片描述
关于此处使用Yml方式对接口参数进行校验的方式此处不在展开了,后期有机会单独开文章介绍。

2.5 操作器实现

后台管理系统规则条件配置操作器具体实现如下:

/**
 * @author Kkk
 * @Describe: 后台管理系统规则条件配置操作器
 */
@Service("ruleConfigOperator")
public class RuleConfigOperator extends AbstractBackstageOperator {

    @Autowired
    private RuleConfigRepository ruleConfigRepository;

    @Autowired
    private RouterRuleDomainRepository routerRuleDomainRepository;

    @Override
    public void add(PayCoreContext context) throws Exception {
        RuleConfig ruleConfig = new RuleConfig();
        Map<String, Object> requestBodyMap =  context.getRequestEntity().getBody();
        BeanUtils.populate(ruleConfig, requestBodyMap);
        Date currentDateTime = DateUtil.getCurrentDate();
        ruleConfig.setCreateTime(currentDateTime);
        ruleConfig.setUpdateTime(currentDateTime);
        ruleConfig.setIsDelete(DeleteEnum.NO.getCode());
        ruleConfigRepository.saveAndFlush(ruleConfig);
    }

    @Override
    public boolean addCheck(PayCoreContext context) {
        Map<String, Object> requestBodyMap = context.getRequestEntity().getBody();
        RuleConfig queryRuleConfig = new RuleConfig();
        queryRuleConfig.setRuleScene(String.valueOf(requestBodyMap.get(PayCoreConstant.RULE_SCENE)));
        queryRuleConfig.setConfigKey(String.valueOf(requestBodyMap.get(PayCoreConstant.CONFIG_KEY)));
        Specification<RuleConfig> specification = buildQueryCondition(queryRuleConfig);
        RuleConfig ruleConfig = ruleConfigRepository.findOne(specification);
        if(ruleConfig != null) {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"已存在该规则属性,不能新增"});
        }
        String isScript = requestBodyMap.get(PayCoreConstant.IS_SCRIPT)==null?"":requestBodyMap.get(PayCoreConstant.IS_SCRIPT).toString();
        String scriptContent = requestBodyMap.get(PayCoreConstant.SCRIPT_CONTENT)==null?"":requestBodyMap.get(PayCoreConstant.SCRIPT_CONTENT).toString();
        if(BooleanEnum.YES.getCode().equals(isScript) && StringUtils.isEmpty(scriptContent)) {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"需要填写脚本内容,不能新增"});
        }
        return false;
    }

    @Override
    public void delete(PayCoreContext context) throws Exception {
        Map<String, Object> requestBodyMap =   context.getRequestEntity().getBody();
        RuleConfig queryRuleConfig = new RuleConfig();
        queryRuleConfig.setId(Long.valueOf(String.valueOf(requestBodyMap.get(PayCoreConstant.ID))));
        Specification<RuleConfig> specification = buildQueryCondition(queryRuleConfig);
        RuleConfig ruleConfig = ruleConfigRepository.findOne(specification);
        if(ruleConfig != null) {
            deleteCheck(ruleConfig);
            ruleConfig.setIsDelete(DeleteEnum.YES.getCode());
        }else {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"要修改的数据不存在"});
        }
        ruleConfigRepository.saveAndFlush(ruleConfig);
    }

    @Override
    public boolean deleteCheck(PayCoreContext context) {
        return false;
    }

    private void deleteCheck(RuleConfig ruleConfig) throws Exception{
        RuleCondition ruleCondition = new RuleCondition();
        ruleCondition.setRuleScene(ruleConfig.getRuleScene());
        ruleCondition.setConfigKey(ruleConfig.getConfigKey());
        List<RuleCondition> ruleCondtionList= routerRuleDomainRepository.findAllRuleCondition(ruleCondition);
        if(ruleCondtionList != null && ruleCondtionList.size() > 0) {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"已被规则条件引用,不能删除"});
        }
    }

    @Override
    public void modify(PayCoreContext context) throws Exception {
        Map<String, Object> requestBodyMap =   context.getRequestEntity().getBody();
        RuleConfig queryRuleConfig = new RuleConfig();
        queryRuleConfig.setId(Long.valueOf(String.valueOf(requestBodyMap.get(PayCoreConstant.ID))));
        Specification<RuleConfig> specification = buildQueryCondition(queryRuleConfig);
        RuleConfig ruleConfig = ruleConfigRepository.findOne(specification);
        if(ruleConfig != null) {
            BeanUtils.populate(ruleConfig, requestBodyMap);
        }else {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"要修改的数据不存在"});
        }
        ruleConfigRepository.saveAndFlush(ruleConfig);
    }

    @Override
    public boolean modifyCheck(PayCoreContext context) {
        Map<String, Object> requestBodyMap =  context.getRequestEntity().getBody();

        String ruleScene = StringUtils.valueOf(requestBodyMap.get(PayCoreConstant.RULE_SCENE));
        String configKey = String.valueOf(requestBodyMap.get(PayCoreConstant.CONFIG_KEY));

        RuleConfig queryRuleConfig = new RuleConfig();
        queryRuleConfig.setRuleScene(ruleScene);
        queryRuleConfig.setConfigKey(configKey);
        Specification<RuleConfig> specification = buildQueryCondition(queryRuleConfig);
        List<RuleConfig> ruleConfigs = ruleConfigRepository.findAll(specification);
        if(ruleConfigs != null) {
            for(RuleConfig ruleConfig: ruleConfigs) {
                if(ruleConfig.getId().compareTo(Long.valueOf(StringUtils.valueOf(requestBodyMap.get(PayCoreConstant.ID)))) != 0) {
                    throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"已存在该规则属性,不能修改"});
                }
            }
        }
        String isScript = requestBodyMap.get(PayCoreConstant.IS_SCRIPT)==null?"":requestBodyMap.get(PayCoreConstant.IS_SCRIPT).toString();
        String scriptContent = requestBodyMap.get(PayCoreConstant.SCRIPT_CONTENT)==null?"":requestBodyMap.get(PayCoreConstant.SCRIPT_CONTENT).toString();
        if(BooleanEnum.YES.getCode().equals(isScript) && StringUtils.isEmpty(scriptContent)) {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR,new Object[]{"需要填写脚本内容,不能修改"});
        }
        return false;
    }

    @Override
    public void query(PayCoreContext context) {
    }

    @Override
    public boolean queryCheck(PayCoreContext context) {
        return false;
    }

    @Override
    public void list(PayCoreContext context) throws Exception {
        PageRequest pageRequest = new MessageEntityWrap(context.getRequestEntity()).getPageRequestFromRequestBody();
        Map<String, Object> requestBodyMap =   context.getRequestEntity().getBody();
        RuleConfig ruleConfig = new RuleConfig();
        BeanUtils.populate(ruleConfig, requestBodyMap);

        Specification<RuleConfig> specification = buildQueryCondition(ruleConfig);
        if(pageRequest == null) {
            List<RuleConfig> ruleList= ruleConfigRepository.findAll(specification);
            buildResponseEntity(context, MapUtils.buildMap(PayCoreConstant.PAGE_CONTENT, ruleList));
        } else {
            Page<RuleConfig> ruleList =ruleConfigRepository.findAll(specification, pageRequest);
            buildResponseEntity(context, ruleList);
        }
    }

    @Override
    public boolean listCheck(PayCoreContext context) {
        return false;
    }
    
     public Specification buildQueryCondition(final RuleConfig ruleConfig){
      return new Specification<RuleConfig>() {
          @Override
          public Predicate toPredicate(Root<RuleConfig> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
              List<Predicate> list = new ArrayList<Predicate>();
              Predicate deletePredicate = cb.equal(root.get(PayCoreConstant.IS_DELETE).as(String.class), DeleteEnum.NO.getCode());
              list.add(deletePredicate);
              if(ruleConfig.getId() != null) {
                  Predicate predicate = cb.equal(root.get(PayCoreConstant.ID).as(Long.class), Long.valueOf(ruleConfig.getId()));
                  list.add(predicate);
              }

              if(ruleConfig.getRuleScene() != null) {
                  Predicate predicate = cb.equal(root.get(PayCoreConstant.RULE_SCENE).as(String.class), ruleConfig.getRuleScene());
                  list.add(predicate);
              }

              if(ruleConfig.getConfigKey() != null) {
                  Predicate predicate = cb.equal(root.get(PayCoreConstant.CONFIG_KEY).as(String.class), ruleConfig.getConfigKey());
                  list.add(predicate);
              }

              if(ruleConfig.getConfigName() != null) {
                  Predicate predicate = cb.equal(root.get(PayCoreConstant.CONFIG_NAME).as(String.class), ruleConfig.getConfigName());
                  list.add(predicate);
              }
              Predicate[] predicates = new Predicate[list.size()];
              query.where(cb.and(list.toArray(predicates)));
              return query.getRestriction();
          }
      };
  }
}

3. Domain层

在这里插入图片描述
domain层主要包括领域模型工厂、实体类、聚合、领域模型仓储、领域服务等包。
在上面RuleConfigOperator中,我们可以看到对于持久化引入了领域模型仓储、仓储两个类:

    @Autowired
    private RuleConfigRepository ruleConfigRepository;

    @Autowired
    private RouterRuleDomainRepository routerRuleDomainRepository;

RuleConfigRepository 规则配置仓储,用于持久化配置信息;RouterRuleDomainRepository 路由领域模型仓储用于校验我们对配置的删除操作是否允许,及是否有路由引用此规则。

注意:领域模型存储仓储定义于domain层仓储定义于dal层,领域模型存储仓储包含多个仓储,如下,路由配置领域模型领域仓储包含规则仓储规则条件仓储

/**
 * @author Kkk
 * @Describe: 路由配置领域模型领域仓储
 */
@Repository
public class RouterRuleDomainRepositoryImpl implements RouterRuleDomainRepository {

    @Autowired
    private RouterRuleRepository ruleRepository;

    @Autowired
    private RuleConditionRepository ruleConditionRepository;
	
	//... ...
}

4. Infrastructure层

4.1 Dal层

对于后台管理接口在Infrastructure层主要使用到了Dal层进行数据持久化操作。
在这里插入图片描述
dal层主要包含和数据库对应的实体,以及持久化的repository类。

三、对应类图

在这里插入图片描述

四、开发内容

如上介绍了那么多内容,此时我们后台管理系统又增加了如下一项页面配置,那么开发需要做什么?
在这里插入图片描述

3.1 约定请求报文格式

{
    "header": {
        "channelCode": "",
        "channelDateTime": "",
        "channelNo": "",
        "transCode": "baseConfig",
        "transType": "add"
    },
    "body": {
        "configKey": "scene_code",
        "code": "001",
        "name": "场景码",
        "value": "对应值"
    }
}

3.2 新增交易码与操作器映射枚举类

/**
 * @author Kkk
 * @Description: 交易码与操作器映射枚举类
 */
public enum ManagerTransCodeEnum {
    baseConfig("baseConfig", "baseConfigOperator", "基础信息管理");
}

3.3 配置参数校验Yml

#基本配置新增
baseConfigadd:
  - field: configKey
    fieldName: 类型
    option: false
    length: 1,256
  - field: code
    fieldName: 编码
    option: false
    style: codeStyle
  - field: name
    fieldName: 名称
    option: false
    regex: ^[\w\-\.\u00b7\u4E00-\u9FA5]{1,256}$
  - field: value
    fieldName:length: 1,256
  - field: remark
    fieldName: 备注
    length: 1,256

3.4 编写操作器(核心工作内容)

/**
 * @author Kkk
 * @Describe: 后台管理系统基本配置操作器
 */
@Service("baseConfigOperator")
public class BaseConfigOperator extends AbstractBackstageOperator {

    @Autowired
    private BaseConfigRepository baseConfigRepository;

    @Override
    public void add(PayCoreContext context) throws Exception {
        BaseConfig baseConfig = new BaseConfig();
        Map<String, Object> requestBodyMap = context.getRequestEntity().getBody();
        BeanUtils.populate(baseConfig, requestBodyMap);
        Date currentDateTime = DateUtil.getCurrentDate();
        baseConfig.setCreateTime(currentDateTime);
        baseConfig.setUpdateTime(currentDateTime);
        baseConfig.setIsDelete(DeleteEnum.NO.getCode());
        getIsExistAdd(baseConfig.getConfigKey(), baseConfig.getCode(), baseConfig.getId());
        baseConfigRepository.saveAndFlush(baseConfig);
    }

    @Override
    public boolean addCheck(PayCoreContext context) {
        Map<String, Object> requestBodyMap = context.getRequestEntity().getBody();
        BaseConfig queryBaseConfig = new BaseConfig();
        queryBaseConfig.setCode(String.valueOf(requestBodyMap.get("code")));
        queryBaseConfig.setConfigKey(String.valueOf(requestBodyMap.get("configKey")));
        Specification<BaseConfig> specification = buildQueryCondition(queryBaseConfig);
        BaseConfig BaseConfig = baseConfigRepository.findOne(specification);
        if (BaseConfig != null) {
            throw new PayCoreException(SystemErrorCode.BIZ_CHECK_ERROR, new Object[]{"已存在该编码,不能新增"});
        }
        return false;
    }
	// ... ...
}

3.5 编写仓储和实体

/**
 * @author Kkk
 * @Describe: 基础配置表仓储接口实现
 */
@Repository
public class BaseConfigRepositoryImpl extends GeneralRepositoryImpl<BaseConfig, Long> {

}

通过如上5步骤完成了,后台页面的新增配置的功能,虽然代码量并没有减少很多,但是我们的项目整洁性以及后期迁移性得到了极大的保证。


总结

本篇主要以后台管理配置接口简要的介绍了下工作中所使用的统一开发框架结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2 # 函数柯里化

什么是函数柯里化 函数柯里化&#xff08;Currying&#xff09;是一种将接受多个参数的函数转换为一系列接受单一参数的函数的技术。 通过函数柯里化&#xff0c;我们可以将原来接受多个参数的函数&#xff0c;转换为一系列只接受单一参数的函数&#xff0c;每个函数接收一个…

前端面试题总结(初级前端:HTML + CSS + JavaScript + Ajax + Vue2全家桶)

theme: channing-cyan 求关注&#x1f62d; 壹、HTML CSS 1. 对HTML语义化的理解 去掉或者丢失样式的时候能够让页面呈现出清晰的结构&#xff1b;代码结构清晰&#xff0c;方便团队的管理和维护&#xff0c;并且语义化更具可读性&#xff1b;提升用户体验&#xff0c;在…

什么是daemon与服务(service)

什么是daemon与服务(service) 在Linux系统中&#xff0c;daemon是指一类在后台运行的服务进程&#xff0c;通常以d结尾。它们不与用户进行交互&#xff0c;也不接受用户的输入&#xff0c;而是在系统启动时自动启动并一直运行&#xff0c;为操作系统、应用程序和其他服务提供支…

Julia入门-3、Julia包管理工具

文章目录 0、Julia 的包管理工具是Pkg1、使用Julia包管理工具过慢 0、Julia 的包管理工具是Pkg Julia 的包管理工具是Pkg&#xff0c;可以用于安装、更新、卸载和管理 Julia 中的软件包。以下是一些常用的 Pkg命令&#xff1a; Pkg.add("Package")&#xff1a;安装一…

Node.js 与 WebAssembly

目录 1、简介 2、关键概念 3、生成WebAssembly模块 4、如何使用它 5、与操作系统交互 1、简介 首先&#xff0c;让我们了解为什么WebAssembly是一个很棒的工具&#xff0c;并学会自己使用它。 WebAssembly是一种类似汇编的高性能语言&#xff0c;可以从各种语言编译&…

深度学习 - 47.DIN 深度兴趣网络保姆级实现 By Keras

目录 一.引言 二.DIN 模型分析 1.Input 输入 2.Embedding & Concat 嵌入与合并 3.DIN 深度兴趣网络 4.MLP 全连接 三.DIN 模型实现 1.Input 2.DIN Layer 2.1 init 初始化 2.2 build 构建 2.3 call 调用 3.Dice Layer 3.1 init 初始化 3.2 build 构建 3.3 …

网络安全:渗透神器 kali 的安装.

网络安全&#xff1a;渗透神器 kali 的安装. Kali Linux是一款基于Debian的Linux发行版&#xff0c;专门用于渗透测试和网络安全评估。它包含了大量的渗透测试工具和网络安全工具&#xff0c;适用于各种不同的渗透测试场景和需求。 目录&#xff1a; 网络安全&#xff1a;渗透…

MS5814可选内置基准、四通道数模转换器

MS5814/5814T 是一款 12bit 四通道输出的电压型 DAC&#xff0c;集成可选内部基准&#xff0c;接口采用四线串口模式&#xff0c;可以兼容 TMS320、SPI、QSPI 和 Microwire 串口。MS5814/5814T 控制数据有 16bit&#xff0c;包括 DAC 地址&#xff0c;控制字节和 12bitDAC 数据…

MySQL——BasicKnowledge

1. Mysql Version ​​​​​​​​​​​​ 2.install & uninstall 2.1 Linux My Content 3.Configure 3.1 设置IP访问 参考链接 3.1.1 方法一&#xff1a;改表法 --登入mysql后&#xff0c;更改"mysql"数据库中"user"表里的 "host&quo…

“三问五步”落地医疗行业数据安全建设体系|盾见

个 文|龚磊 2021年6月10日&#xff0c;我国《数据安全法》颁布&#xff0c;并于2021年9月1日正式施行&#xff0c;作为数据领域的纲领性和基础性法律&#xff0c;以准确定义数据、数据处理、数据安全为出发点&#xff0c;提出解决数据全生命周期中的数据安全问题&#xff0c;达…

深入浅出,理解知识图谱技术及其应用

知识图谱是一个将各种实体之间的关系以图谱的形式呈现出来的技术。这些实体可以是人、地点、组织或任何其他类型的实体。知识图谱将这些实体与它们之间的关系链接在一起&#xff0c;从而形成一个表示知识的图形结构。本文将介绍知识图谱的基础知识&#xff0c;包括它的定义、应…

【TA100】渲染管线

粗粒度剔除&#xff1a;根据物体之间的遮挡关系&#xff0c;剔除遮挡的物体。 多顶点分配到GPU不同单元执行&#xff0c;提升速度 1 应用阶段 1.1 场景阶段 ### 1.2 粗粒度剔除 剔除与摄像机与视锥体不相交的光源或者光线照射方向与与视角不重合的光源。 1.3 渲染设置 1.4 调用…

AVL树的插入

介绍: 在二叉搜索树的前提下,左右高度差(平衡因子)的绝对值不大于1 二叉搜索数->中序排序树->极端情况下时间复杂度高 ->我博客更过不了解去翻翻 (搜索二叉树——寻找节点&#xff0c;插入节点&#xff0c;删除节点_别想闲鱼了&#xff01;快去学习的博客-CSDN博客)…

硬核推荐!3款私藏的卡通头像在线生成网站,减少撞“头”率

现在各个软件应用都会有自己的头像设置&#xff0c;肯定有很多朋友经常喜欢换头像&#xff0c;我猜大家的头像要么就是明星&#xff0c;要么就是网图&#xff0c;总是出现和别人撞“头”的现象。今天就来给大家推荐几个在线制作头像的网站&#xff0c;保证让撞“头”率减少90%。…

【MYSQL】聚合函数和单表/多表查询练习、子查询、内外连接

目录 1.聚合函数 1.1.group by子句 1.2.having语句 2.单表查询 2.2单表查询 3.多表查询 3.2.子查询 5.内链接 6.外连接 1.聚合函数 函数说明count返回查询到的数据的数量sum返回查询到的数据的总和avg返回查询到的数据的平均值max返回查询到的数据的最大值min返回查询…

WPF MVVM基础教程(五)RelativeSource属性绑定

RelativeSource属性绑定 介绍RelativeSource属性介绍RelativeSource的Mode属性有四种模式&#xff1a; 四种模式用法Self模式FindAncestor模式使用规则&#xff1a; TemplatedParent模式PreviousData模式 特殊用法绑定到其他ViewModel上的命令 介绍 RelativeSource 是一个标记…

前端HTML学习(二)

1、列表标签 列表标签概述&#xff1a;能够使用无序列表、有序列表、自定义列表标签&#xff0c;实现网页中列表结构的搭建。列表应用在网页中按照行展示关联性的内容&#xff0c;如:新闻列表、排行榜、账单等。 特点&#xff1a;按照行的方式&#xff0c;整齐显示内容 种类&a…

大模型遭泄两月后 Meta意外变赢家

一份被意外泄露的谷歌内部文件&#xff0c;将Meta的LLaMA大模型“非故意开源”事件再次推到聚光灯前。 “泄密文件”的作者据悉是谷歌内部的一位研究员&#xff0c;他大胆指出&#xff0c;开源力量正在填平OpenAI与谷歌等大模型巨头们数年来筑起的护城河&#xff0c;而最大的受…

Ae:摄像机命令

Ae 中提供了一些摄像机命令&#xff0c;可帮助更好地运用摄像机&#xff0c;比如实现跟焦、切换对焦对象&#xff08;图层&#xff09;、展现指定图层内容等等。 Ae菜单&#xff1a;图层/摄像机 Camera 这些命令也可在摄像机图层的右键菜单中选择。 基于 3D 视图生成摄像机 Cre…

MySQL基础-数据库介绍

本文介绍MySQL基础&#xff0c;包括什么是数据库&#xff0c;什么是关系型数据库&#xff0c;数据库和实例区别&#xff0c;数据库分类 文章目录 前言什么是数据库&#xff1f;什么是数据库管理系统&#xff08;DBMS&#xff09;&#xff1f;什么是关系型数据库管理系统&#x…