Financial rules of product costs relative to the money, dimensions and financial plan dimensions is the smallest unit of the particle size, financial product system, it can provide the decision-making car company is financial product diversification and differential ground support, constitutes the financial products financial scheme is the core part of the trial, to support the business scenario rapid development and lay a foundation to carry out the policy adjustments fall to the ground.

Warm tips: the full text is more than 3200 words, it is not easy to write code words on the way to work, thank you for your attention. Draft on 27 November 2020, final draft on 5 December 2020.

Car finance | financial product center of incarnations

Financial | car GPS incarnations of the audit system

Car financial | basic data platform of incarnations

Car incarnations of financial center | contract system

Car financial | financial product rules engine incarnations (last)

Car financial | financial product rules engine incarnations (medium)

Car financial | financial product rules engine incarnations (next)

Car financial | I M in the two years

@[toc]

A, takeaway

In the first two chapters, we explained the sequential rules and capital access rules of the rule engine of financial products. In this chapter, we will focus on the most important core component of the rule engine — financial scheme fee rules.

Through this chapter, you will learn more business concepts in financial products, including financial products, dealer stores, expense rules, expense calculation formulas, etc. At the same time, you will understand the relationship between them, how they serve financial products, and what their responsibilities are.

I believe that through this article, you will have a comprehensive understanding of financial products. I would like to emphasize that our financial products are for car finance.

Ii. Development history

When I first joined M Company, I gradually took over and took charge of the financial product system and led the team to split the financial product platform and financial product service from the old system through technical reconstruction. Later, I led the team to split the business of the database, thus realizing the independent deployment, operation and maintenance of the business module of financial products, laying a foundation for the stable development of auto finance business.

Although the financial product system has achieved business separation, the whole business model has not changed substantially, but changed from the original SpringMVC+JSP architecture into the technology architecture of front and back end separation SpringBoot+VueJs. Although this is not enough to prove the business value brought by technical reconfiguration, from the perspective of long-term business development, it lays a foundation for the subsequent platform system optimization and business model remodeling. Without the initial small step, there will not be all the subsequent research and development achievements.

3. Research and innovation

In terms of total loan composition, financial product system includes vehicle insurance, life insurance, theft and rescue, insurance renewal deposit, platform fee, GPS fee and a series of fees. For whether to be included in the total loan of financial plan, financial product provides zero-grade choice, such as life insurance, theft and rescue, insurance renewal deposit, vehicle insurance and so on.

The initial system design from the business model, only platform cost rules implementation associated with financial products and the dealer stores double configuration, and a similar vehicle insurance, vehicle discount only associated with financial products to achieve the configuration, other cost items (including interest rate rules, GPS, etc.) implementation associated with dealer stores only configuration.

From the perspective of business model analysis, financial products, dealer stores and expense rules are all independent business model units. In order to realize the relevance of business, there are correlation relations among business entities. However, through a review of the above situation, we found that although the fee rules are the same, there are still great differences in the implementation of system functions. I was very surprised at this when I first came into contact with financial products, and I didn’t know why they were designed in this way.

With the development of car finance business, it has its own urgent requirements for the operation of various cities. For two cities in different places, for example, for the same operating financial products, if want to achieve a GPS fee pricing is different, because of financial products on the system, from system design, the GPS fee was not associated with urban (dealer stores) configuration, while the financial product implements associated with urban configuration, if on the premise of this indirect correlation relationship, The realization is in line with the requirements of actual business scenarios, but it is really difficult for the product operation students of the financial product platform.

1. Internal medicine “Surgery”

By analyzing the insufficient support of the existing system for business scenarios, we decided to carry out a medical “operation” on the financial product system from the business model and system interaction. The goal of the “operation” is to support the expense rules and realize the double-layer associated configuration with financial products and dealer stores.

In order to smoothly carry out the operation and observe the effect after the operation, we split the overall operation into two stages to plan and execute it.

  • In the first stage, interest rate rules and GPS fee rules are associated with financial products and dealer stores as platform fee rules are.
  • In the second stage, other costs (deferred premiums, insurance premiums, etc.) are associated with financial products and dealer stores.

In the first stage, due to the small scope of the change, we conducted independent research and development and discussed with the product operation students before the “operation”, expecting that the financial product platform after the “operation” could meet their pain points.

In terms of business technology, we introduce an association table, which contains the primary key ID of expense rule, the primary key ID of financial product, and the type of expense to realize the maintenance of association between expense rule and financial product. At the same time, the expense type field provides expansibility for the subsequent implementation of such associated configuration of other expense rules.

Then, we adjust the SYSTEM engineering source code mapper SQL query logic, without modifying the external interface to achieve the “operation” of the first stage of the goal. In fact, after the first stage of “operation”, for the product operation students, it is nothing more than to increase the entrance configuration of the association relationship; For client sales, there is no difference in perception of interaction, but it is effectively improved in terms of interface data stability.

2. APP interaction

From the point of interaction of the sales end, the financial scheme is determined in the pre-loan link. The initial design of APP was in the access link. As the order did not pass the review requirements before the pre-loan link, the financial plan needed to be re-selected when the loan intention information was modified when the order was returned. In order to improve the efficiency of the order processing process, the financial plan was moved to the pre-loan link in the later business optimization.

From the perspective of APP interaction, the UI presents users with a top-down drop-down selection of financial products, interest rate gear, platform rate gear, GPS gear and other cost gear.

Because we designed to realize the double association with financial products and dealer stores, in this link, as long as users determine the financial products, the system can determine the relevant cost stalls that users can choose. At first, since there was no correlation in the design of our financial product system, the system interaction could only return the corresponding data set of expense stalls when the corresponding drop-down selection of expense stalls was triggered. The financial product system is “surgically” packaged in a single interface to return a series of fee slots.

“Surgery”, therefore, good results on interface design also conducive to the financial product center, from the scalability and maintainability, compared to before “surgery” is optimized for many, this means that the follow-up business scenarios to increase or reduce the cost of a financial plan, adjust only in the interface can meet the demand expansion.

3. Platform interaction

With the development of business, the amount of related table data of financial product system increases sharply. For product operation students, for example, a new kaesong city means at least one new store, which requires heavy workload in the configuration of product cost rules.

Due to the simple functions of the platform system, we carried out a series of interactive optimization of platform functions in order to support the daily operation demands of the product operation students.

We have realized the association between the fee rule module and dealer stores. Subsequently, we have added the entrance configuration associated with financial products. At the same time, for dealer stores, we also increased the interaction with financial products and fee rules, so as to facilitate and quickly support the demands of users.

The above configuration panel can be uniformly configured and managed from both dealer stores and financial products. All expense rules can be displayed through TAB page, and quick batch entry for association/unbinding can be provided to realize daily operation configuration.

The above configuration panel not only supports all the cost rules to achieve batch operation store configuration, but also provides fast retrieval of multiple conditions.

The batch association setting panel is to solve the daily product operation batch online or offline costs, and support fast association binding from the store or product dimensions, and assist product diversified operation decisions.

Fourth, core interpretation

1. Configuration of expense items

For example, platform rates are configured according to platform fee rules. When configuring these fee rules, you need to configure applicable business scenarios, including repayment period, vehicle type, down payment ratio range, and loan amount range.

Cost rules from the point of view of product operation, can be distinguished by the name of the cost rules, or by setting labels for data classification, if the label is used well, for thousands of rules can be said to provide a sword, can help you quickly search and support daily work operation.

In addition cost rule configuration scenarios, from the choice if you configured with a financial plan, financial plan itself has the scene condition, but for the cost rules, you need to configure the scene condition should have to meet the financial plan the configuration of the interval, which means that the charge rule, from the business scenario is smallest division for financial solutions business unit.

2. Priority of expenses

Initially these costs rule is a priority, priority means that data back to the client’s order, although the priority in the design has certain extensibility, but for financial plan trial scenario, the cost center first order no to the rules of the primary key id, this means that the data returned to the show will lead to cost items for priority adjustment amount is different.

Later, to solve this problem, when storing data, we required the order center to store the id of related fee rules of financial products to support APP data preservation and display.

3. The stalls of expenses

For an order, if the customer’s down payment ratio or total loan amount is different, or different provinces and cities have different regional policies, the business needs to be divided by financial products. Different financial products mean that the amount of a certain fee of the total loan amount is also different.

For example, GPS fee, platform fee, personal insurance plan, etc., it is based on the gear to meet the business scene.

4. Calculation of expenses

Within the financial product system, for the auto finance business line, the trial calculation provided by the financial product is actually to calculate the total loan amount for the customer’s loan intention. For a vehicle with an actual sales price, in the installment repayment scenario, the customer makes a first payment for the vehicle, and the remaining payment needs to be amortized to the monthly payment for repayment.

Therefore, in terms of business, the calculation of total loans and the generation of repayment plans are the core service capabilities for financial products. However, the financial product system of auto finance only calculates total loans, while repayment plans are not its responsibility, which is provided by the accounting system.

From the perspective of auto finance business, the calculation formula of the cost items of financial products and the total loan amount remains the same no matter what kind of financial plan or investor.

In terms of the processing value of the cost item, the carry mode and precision processing Settings are currently provided. Carry way is round up, round down, round three ways, and precision processing is decimal or integer processing.

Five, core code

1. Associated configuration of expense items – Abstract code design

QueryLink

/ * * *@description: Interface for querying the product association of the cost rule *@Date: 2018/11/20 2:07 PM *@Author: Shi Dongdong -Seig Heil */
public interface QueryLink<E extends BaseEntity.F extends PdRuleProductForm> {
    /** * query associated collection *@param form
     * @return* /
    List<E> queryLink(PageForm<F> form);
    /** * Query the number of associated items *@param form
     * @return* /
    int queryLinkCount(PageForm<F> form);
}
Copy the code

AbstractQueryLinkHandler

/ * * *@descriptionAbstract query associated product handler *@Date: 2018/11/20 2:14 PM *@Author: Shi Dongdong -Seig Heil */
public abstract class AbstractQueryLinkHandler<E extends BaseRule> implements QueryLink {
    /** * Processor name */
    protected String handlerName;
    /** * relies on the query service interface */
    protected PdRuleProductService pdRuleProductService;
    /** * relies on the generic Settings interface */
    protected CommonComponent commonComponent;
    /** * Query the number */
    protected int count;
    /** * Query entity object */
    protected List<E> entities;
    /** * VO entity set object */
    protected List<BaseVo> voList;
    /** * Service type */
    protected TagConstant.BuzTypeEnum buzTypeEnum;
    /** ** query condition */
    protected PageForm<PdRuleProductForm> form;

    /** * Default constructor */
    public AbstractQueryLinkHandler(a) {}/** * constructor *@paramHandlerName Processor name *@paramBuzTypeEnum Associated or unassociated service type */
    public AbstractQueryLinkHandler(String handlerName, TagConstant.BuzTypeEnum buzTypeEnum) {
        this.handlerName = handlerName;
        this.buzTypeEnum = buzTypeEnum;
    }

    /** * Initializes the associated member variable *@param context
     */
    protected void prepare(QueryLinkContext context){
        this.pdRuleProductService = context.getPdRuleProductService();
        this.commonComponent = context.getCommonComponent();
        this.form = context.getForm();
    }

    /** * Query associated data */
    protected void query(a){
        entities = queryLink(form);
        count = queryLinkCount(form);
    }

    /** * related Settings call */
    protected void call(QueryLinkContext context){
        context.setCount(count);
        voList = convertVo();
        context.setVoList(voList);
    }

    /** * entities converted to VO *@return* /
    abstract List<BaseVo> convertVo(a);

    /** * bind the label field */ to the queried VO collection
    protected void bindTags(a){
        if(null == voList || voList.isEmpty()){
            return;
        }
        commonComponent.bindTags(TwoTuple.newInstance(buzTypeEnum.getIndex(),voList),
                (rule) -> Integer.valueOf(((BaseRuleVo)rule).getRuleSeq()),(rule, tags) -> ((BaseRuleVo)rule).setTags(tags));
    }

    /** * External call public method *@param context
     */
    public final void execute(QueryLinkContext context){ prepare(context); query(); call(context); bindTags(); }}Copy the code

RateQueryLinkHandler

/ * * *@description: Interest rate rule query product association processor *@Date2018/11/20 5:47 PM *@Author: Shi Dongdong -Seig Heil */
public class RateQueryLinkHandler extends AbstractQueryLinkHandler {
    /** * constructor */
    public RateQueryLinkHandler(a) {
        super(Interest rate rule, TagConstant.BuzTypeEnum.RATE_RULE);
    }

    @Override
    public List<RateRule> queryLink(PageForm form) {
        return pdRuleProductService.queryRateRules(form);
    }

    @Override
    public int queryLinkCount(PageForm form) {
        return pdRuleProductService.queryRateRulesCount(form);
    }

    @Override
    List<BaseVo> convertVo(a) {
        return newRateRuleVoConvertor().convertList(entities); }}Copy the code

Summary, the cost rule binding query implementation code is further abstracted and encapsulated. In terms of code expansibility, adding a subclass to realize the corresponding query method can realize the extension of the scene.

QueryLinkHandlerFactory

/ * * *@description: Queries the associated product processor factory *@Date: 2018/11/20 6:03 PM *@Author: Shi Dongdong -Seig Heil */
public final class QueryLinkHandlerFactory {
    final static int SECOND_YEAR = 2;
    final static int THIRD_YEAR = 3;
    /** * Create handler *@param buzTypeEnum
     * @return* /
    public static final AbstractQueryLinkHandler create(BuzTypeEnum buzTypeEnum){
        switch (buzTypeEnum){
            case SER_FIN_RULE:
                return new SerFinQueryLinkHandler();
            case RATE_RULE:
                return new RateQueryLinkHandler();
            case GPS_RULE:
                return new GpsQueryLinkHandler();
            case INSURANSE_SECOND_YEAR:
                return new InsuranceQueryLinkHandler(SECOND_YEAR);
            case INSURANSE_THIRD_YEAR:
                return new InsuranceQueryLinkHandler(THIRD_YEAR);
            case ACCOUNT_RULE:
                return new AccountQueryLinkHandler();
            default:
                throw new BizException("illegal buzTypeEnum index = "+ buzTypeEnum.getIndex()); }}}Copy the code

2, batch association Settings – abstract code design

The above is the UI interaction prototype. Each input box is the ID of a different table. Each input box is not mandatory.

UML diagrams

LinkProcess

/ * * *@description: Data association processing interface *@Date2018/10/17 6:34 PM *@Author: Shi Dongdong -Seig Heil */
public interface LinkProcess {
    /** * Rule ID or product ID * This interface needs to subclass to implement the query library, according to the page input rule ID condition, and return the query object set to obtain the corresponding primary key ID set *@return* /
    List<Integer> dataList(a);
    /** * process data *@paramC1 calls back the valid rule ID to the consumer using *@paramC2 calls back the invalid rule ID and operation log object to the consumer using */
    void handleData(Consumer<List<Integer>> c1, BiConsumer<List<Integer>,JSONObject> c2);
    /** * is associated with store dimension - data binding *@paramDealerCodes dealerCodes to be associated with@paramLinkList ID of the fee rule or product ID */ to be associated
    void bindDealer(List<Integer> dealerCodes, List<Integer> linkList);
    /** * is associated with product dimension - data binding *@paramProductIds Associated product ID collection *@paramLinkList ID of the fee rule or product ID */ to be associated
    void bindProduct(List<Integer> productIds, List<Integer> linkList);
    /** * Whether to perform the jump *@return* /
    default boolean skip(a){return false;}
}
Copy the code

AbstractLinkProcessor

/ * * *@descriptionAbstract data association processing interface *@Date2018/10/17 6:45pm *@Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractLinkProcessor implements LinkProcess.BiConsumer<List<Integer>, JSONObject>{
    protected final String LOG_TITLE = "Batch Associate Settings";
    /** * indicate the cost type * {@link TagConstant.BuzTypeEnum#getIndex()}
     */
    protected Integer classify;
    /** * Processor name */
    protected String processorName;
    / * * * * /
    protected BaseService ruleService;
    / * * * * /
    protected BaseService ruleDealerService;
    /** * Input data (rule ID or product ID) */
    protected List<Integer> sourceList;
    /** * The actual legal operation of the store to deal with */
    protected List<Integer> dealerProcesses;
    /** * The valid product ID */ to be processed
    protected List<Integer> productProcesses;
    /** * context object */
    protected LinkProcessContext context;
    /** * encapsulates request parameters */
    protected BatchLinkParam batchLinkParam;
    /** * rule query Form object */
    protected BaseRuleForm ruleForm;
    /** * Service type */
    protected LinkProcessBuzType buzType;
    / * * * * /
    protected DealerFeeRuleVo feeRuleVo;

    /** * constructor *@param processorName
     */
    public AbstractLinkProcessor(String processorName) {
        this.processorName = processorName;
    }

    /** * subclass initialization */
    protected abstract void init(a);
    /** * empty item processing */
    protected abstract void emptyInputItem(a);
    /** * initializes */
    protected void prepare(LinkProcessContext ctx){
        context = ctx;
        if(null == context.getOperateLog()){
            context.setOperateLog(new JSONObject(true));
        }
        feeRuleVo = context.getFeeRuleVo();
        buzType = context.getBuzType();
        batchLinkParam = context.getBatchLinkParam();
        dealerProcesses = context.getDealerScope();
        productProcesses = context.getProductScope();
    }

    /** * unified entry for external calls */
    public final void execute(LinkProcessContext ctx){
        prepare(ctx);
        init();
        if(skip()){
            log.info(Skip this process [{}],buzType={}",processorName,buzType);
            return;
        }
        if(null! = sourceList && ! sourceList.isEmpty()){ handleData(processList -> {if(batchLinkParam.isBindDealer()){
                    bindDealer(dealerProcesses,processList);
                }
                if(batchLinkParam.isBindProduct()){
                    bindProduct(productProcesses,processList);
                }
            },(noExits,json) -> accept(noExits,json));
        }else{
            emptyInputItem();
        }
        ctx.getOperateLog().putAll(context.getOperateLog());
    }

    @Override
    public void handleData( Consumer<List<Integer>> c1, BiConsumer<List<Integer>, JSONObject> c2) {
        // Invalid rule ID
        Set<Integer> difference = Sets.difference(Sets.newHashSet(sourceList),Sets.newHashSet(dataList()));
        // The ID of the actual rule to process
        Set<Integer> intersection = Sets.intersection(Sets.newHashSet(sourceList),Sets.newHashSet(dataList()));
        List<Integer> processList = Lists.newArrayList(intersection);
        c1.accept(processList);
        c2.accept(Lists.newArrayList(difference),context.getOperateLog());
        if(batchLinkParam.isBindDealer()){
            context.getOperateLog().put("dealerCodes",Optional.ofNullable(dealerProcesses).orElse(Collections.emptyList()).toString());
        }
        if(batchLinkParam.isBindProduct()){
            context.getOperateLog().put("productIds", Optional.ofNullable(productProcesses).orElse(Collections.emptyList()).toString());
        }
        context.getOperateLog().put(processorName,processList.toString());
    }

    @Override
    public void bindDealer(List<Integer> dealerCodes, List<Integer> linkList) {
        if(CollectionsTools.isEmpty(dealerCodes) || CollectionsTools.isEmpty(linkList)){
            return;
        }
        log.info({}, [{}],linkList={},dealerCodes={},buzType={}", LOG_TITLE,processorName, linkList.toString(), dealerCodes.toString(), buzType);
        dealerCodes.forEach(dealerCode -> {
            List<BaseDealerRes> ruleDealers = new ArrayList<>(linkList.size());
            Date now = TimeTools.createNowTime();
            BaseDealerRes ruleDealer;
            for(Integer ruleId : linkList){
                ruleDealer = new BaseDealerRes();
                ruleDealer.setRuleSeq(ruleId);
                ruleDealer.setDealerCode(dealerCode);
                ruleDealer.setUpdateTime(now);
                ruleDealers.add(ruleDealer);
            }
            if(buzType == LinkProcessBuzType.INSERT){
                ruleDealerService.batchInsertIgnore(ruleDealers);
            }
            if(buzType == LinkProcessBuzType.DELETE){ ruleDealerService.batchDelete(ruleDealers); }}); }@Override
    public void bindProduct(List<Integer> productIds, List<Integer> linkList) {
        if(CollectionsTools.isEmpty(productIds) || CollectionsTools.isEmpty(linkList)){
            return;
        }
        log.info({}, [{}],linkList={},productIds={},buzType={}", LOG_TITLE,processorName, linkList.toString(), productProcesses.toString(), buzType);
        productIds.forEach(productId -> {
            List<PdRuleProduct> batchList = new ArrayList<>(linkList.size());
            linkList.forEach(eachId -> batchList.add(PdRuleProduct.builder().classify(classify.byteValue()).productId(productId).ruleId(eachId).build()));
            if(buzType == LinkProcessBuzType.INSERT){
                context.getServiceMap().get(RuleServiceEnum.RULE_PRODUCT).batchInsertIgnore(batchList);
            }
            if(buzType == LinkProcessBuzType.DELETE){ context.getServiceMap().get(RuleServiceEnum.RULE_PRODUCT).batchDelete(batchList); }}); }}Copy the code

AbstractFeeRuleLinkProcessor

package com.creditease.heil.core.bindlink.feerule;

import com.alibaba.fastjson.JSONObject;
import com.creditease.heil.core.bindlink.AbstractLinkProcessor;
import com.creditease.heil.core.bindlink.LinkProcessBuzType;
import com.creditease.heil.core.bindlink.RuleServiceEnum;
import com.creditease.heil.entity.PdFeeDealer;
import com.creditease.heil.form.PdFeeDealerForm;
import com.creditease.heil.form.PdFeeProductForm;
import com.creditease.heil.form.PdFeeRuleForm;
import com.creditease.heil.service.rule.PdFeeDealerService;
import com.creditease.heil.service.rule.PdFeeProductService;
import com.creditease.heil.entity.PdFeeProduct;
import com.creditease.heil.service.rule.PdFeeRuleService;
import com.creditease.util.CollectionsTools;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/ * * *@description: Abstract expense rule association handler * {@link CarInsuranceRuleLinkProcessor}
 * {@link CarDiscountRuleLinkProcessor}
 * {@link LifeInsuranceRuleLinkProcessor}
 * {@link RentalCommissionRuleLinkProcessor}
 * {@link TheftProtectionRuleLinkProcessor}
 * {@link EnjoyPackRuleLinkProcessor}
 * @Date11:39 am *@Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractFeeRuleLinkProcessor extends AbstractLinkProcessor {
    /** * constructor *@param processorName
     */
    public AbstractFeeRuleLinkProcessor(String processorName) {
        super(processorName);
    }

    /** * get page input rule ID *@return* /
    abstract List<Integer> getInputRuleIds(a);

    @Override
    protected void init(a) {
        sourceList = getInputRuleIds();
        ruleService = context.getServiceMap().get(RuleServiceEnum.FEE_RULE);
        ruleDealerService = context.getServiceMap().get(RuleServiceEnum.FEE_RULE_DEALER);
    }

    @Override
    public List<Integer> dataList(a) {
        ruleForm = new PdFeeRuleForm();
        ruleForm.setRuleIds(sourceList);
        List<Integer> des = ((PdFeeRuleService)ruleService).queryList((PdFeeRuleForm) ruleForm).stream().map(rule -> rule.getId()).collect(Collectors.toList());
        return des;
    }

    @Override
    public void accept(List<Integer> integers, JSONObject jsonObject) {
        jsonObject.put("buzType",buzType);
    }

    @Override
    public void bindDealer(List<Integer> dealerCodes, List<Integer> linkList) {
        if(CollectionsTools.isEmpty(dealerCodes) || CollectionsTools.isEmpty(linkList)){
            return;
        }
        log.info({}, [{}],linkList={},dealerCodes={},buzType={}", LOG_TITLE,processorName, linkList.toString(), dealerCodes.toString(), buzType);
        dealerCodes.forEach(dealerCode -> {
            List<PdFeeDealer> ruleDealers = new ArrayList<>(linkList.size());
            for(Integer ruleId : linkList){
                ruleDealers.add(new PdFeeDealer(ruleId,dealerCode));
            }
            if(buzType == LinkProcessBuzType.INSERT){ ruleDealerService.batchInsertIgnore(ruleDealers); }});if(buzType == LinkProcessBuzType.DELETE){ PdFeeDealerService pdFeeDealerService = (PdFeeDealerService)ruleDealerService; pdFeeDealerService.batchDelete(PdFeeDealerForm.builder().dealerScopes(dealerCodes).ruleIdScope(linkList).build()); }}@Override
    public void bindProduct(List<Integer> productIds, List<Integer> linkList) {
        if(CollectionsTools.isEmpty(productIds) || CollectionsTools.isEmpty(linkList)){
            return;
        }
        log.info({}, [{}],linkList={},productIds={},buzType={}", LOG_TITLE,processorName, linkList.toString(), productProcesses.toString(), buzType);
        productIds.forEach(productId -> {
            List<PdFeeProduct> batchList = new ArrayList<>(linkList.size());
            linkList.forEach(eachId -> batchList.add(PdFeeProduct.builder().productId(productId).resId(eachId).build()));
            if(buzType == LinkProcessBuzType.INSERT){
                context.getServiceMap().get(RuleServiceEnum.FEE_PRODUCT).batchInsertIgnore(batchList);
            }
            if(buzType == LinkProcessBuzType.DELETE){ PdFeeProductForm pdFeeProductForm = PdFeeProductForm.builder().productId(productId).ruleIdList(linkList).build(); ((PdFeeProductService)context.getServiceMap().get(RuleServiceEnum.FEE_PRODUCT)).batchDelete(pdFeeProductForm); }}); }}Copy the code

CarInsuranceRuleLinkProcessor

/ * * *@description: Association processing of vehicle insurance rules *@Date2018/11/13 5:23 PM *@Author: Shi Dongdong -Seig Heil */
public class CarInsuranceRuleLinkProcessor extends AbstractFeeRuleLinkProcessor {

    public CarInsuranceRuleLinkProcessor(a) {
        super("Vehicle Insurance Rules");
    }

    @Override
    List<Integer> getInputRuleIds(a) {
        return batchLinkParam.getCarInsuranceRuleIds();
    }

    @Override
    protected void emptyInputItem(a) {
        feeRuleVo.setCarInsuranceNotExist(sourceList);
    }

    @Override
    public void accept(List<Integer> integers, JSONObject jsonObject) {
        super.accept(integers, jsonObject); feeRuleVo.setCarInsuranceNotExist(integers); }}Copy the code

GpsRuleLinkProcessor

/ * * *@description: GPS rule association handler *@Date2018/10/19 6:04 PM *@Author: Shi Dongdong -Seig Heil */
public class GpsRuleLinkProcessor extends AbstractLinkProcessor {
    /** * constructor */
    public GpsRuleLinkProcessor(a) {
        super("GPS rules");
    }

    @Override
    protected void init(a) {
        classify = TagConstant.BuzTypeEnum.GPS_RULE.getIndex();
        sourceList = batchLinkParam.getGpsRuleIds();
        ruleService = context.getServiceMap().get(RuleServiceEnum.GPS);
        ruleDealerService = context.getServiceMap().get(RuleServiceEnum.GPS_DEALER);
    }

    @Override
    protected void emptyInputItem(a) {
        feeRuleVo.setGpsNotExist(sourceList);
    }

    @Override
    public List<Integer> dataList(a) {
        ruleForm = new GpsRuleForm();
        ruleForm.setRuleIds(batchLinkParam.getGpsRuleIds());
        List<Integer> des = ((GpsRuleService)ruleService).queryList((GpsRuleForm) ruleForm).stream().map(rule -> rule.getRuleSeq()).collect(Collectors.toList());
        return des;
    }

    @Override
    public void accept(List<Integer> integers, JSONObject jsonObject) {
        feeRuleVo.setGpsNotExist(integers);
        jsonObject.put("buzType",buzType); }}Copy the code

AbstractBatchLinkProcessMediator

/ * * *@descriptionAbstract batch association setup processing mediator *@Date: 2018/11/12 3:44 PM *@Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractBatchLinkProcessMediator {
    protected final String LOG_TITLE = "Batch Associate Settings";
    /** * context object */
    protected LinkProcessContext context;
    /** * processor set */
    protected List<AbstractLinkProcessor> processorList;

    public AbstractBatchLinkProcessMediator(a) {}/** * constructor *@param context
     */
    public AbstractBatchLinkProcessMediator(LinkProcessContext context) {
        this.context = context;
    }

    /** * initializes */
    protected void init(a){
        processorList = new ArrayList<AbstractLinkProcessor>(){{
            add(new ProductLinkProcessor());
            add(new DealerLinkProcessor());
            add(new RateRuleLinkProcessor());
            add(new GpsRuleLinkProcessor());
            add(new SerFinRuleLinkProcessor());
            add(new AccountRuleLinkProcessor());
            add(new SecondYearPremiumRuleLinkProcessor());
            add(new ThirdYearPremiumRuleLinkProcessor());
            add(new LifeInsuranceRuleLinkProcessor());
            add(new RentalCommissionRuleLinkProcessor());
            add(new CarInsuranceRuleLinkProcessor());
            add(new CarDiscountRuleLinkProcessor());
            add(new TheftProtectionRuleLinkProcessor());
            add(new EnjoyPackRuleLinkProcessor());
        }};
    }

    /** * handle store or product ID */
    abstract void handleBatchSources(a);

    /** * Execute */
    public final void execute(a){
        init();
        handleBatchSources();
        processorList.forEach(each -> each.execute(context));
    }

    public void setContext(LinkProcessContext context) {
        this.context = context; }}Copy the code

BatchLinkDealerProcessMediator

/ * * *@description: Batch association Settings - Store code association - Process intermediary objects *@Date: 2018/11/12 3:43 PM *@Author: Shi Dongdong -Seig Heil */
@Slf4j
public class BatchLinkDealerProcessMediator extends AbstractBatchLinkProcessMediator {
    public BatchLinkDealerProcessMediator(a) {}public BatchLinkDealerProcessMediator(LinkProcessContext context) {
        super(context);
    }

    @Override
    void handleBatchSources(a) {
        log.info("{}- store association-params :{}", LOG_TITLE,JSON.toJSON(context.getBatchLinkParam()));
        List<Integer> dealerScope = context.getBatchLinkParam().getDealerCodes();
        // Query the store
        TransferClient transferClient = context.getTransferClient();
        List<BaseDealerRe> dealerReList = transferClient.queryBaseDealerList(DealerDTO.builder().dealerScopes(dealerScope).build());
        Assert.notEmpty(dealerReList, "Bulk store code set is empty");
        List<Integer> sources = dealerReList.stream().map(BaseDealerRe::getDealerCode).collect(Collectors.toList());
        // Invalid data
        Set<Integer> difference = Sets.difference(Sets.newHashSet(dealerScope),Sets.newHashSet(sources));
        // Valid data
        Set<Integer> intersection = Sets.intersection(Sets.newHashSet(dealerScope),Sets.newHashSet(sources));
        List<Integer> noExits = Lists.newArrayList(difference);
        context.getFeeRuleVo().setDealerNotExist(noExits);
        List<Integer> handleDealerScope = Lists.newArrayList(intersection);
        context.setDealerScope(handleDealerScope);
        log.info("{}- store association - Legal data :{}, illegal data :{}", LOG_TITLE,JSON.toJSON(handleDealerScope), JSON.toJSON(noExits)); }}Copy the code

BatchLinkProductProcessMediator

/ * * *@description: Batch association Settings - Product ID association - Process intermediary object *@Date: 2019/2/12 5:12 PM *@Author: Shi Dongdong -Seig Heil */
@Slf4j
public class BatchLinkProductProcessMediator extends AbstractBatchLinkProcessMediator {
    public BatchLinkProductProcessMediator(a) {}public BatchLinkProductProcessMediator(LinkProcessContext context) {
        super(context);
    }

    @Override
    void handleBatchSources(a) {
        log.info("{}- Params :{}", LOG_TITLE,JSON.toJSON(context.getBatchLinkParam()));
        List<Integer> productScope = context.getBatchLinkParam().getpIds();
        // Query the store
        ProductService productService = (ProductService)context.getServiceMap().get(RuleServiceEnum.PRODUCT);
        // Query the set of product objects to be processed through the database filter
        List<Product> queryList = productService.queryList(ProductForm.builder().productIdList(productScope).build());
        Assert.notEmpty(queryList,"Batch product ID set is empty");
        List<Integer> sources = queryList.stream().map(each -> each.getpId()).collect(Collectors.toList());
        // Invalid data
        Set<Integer> difference = Sets.difference(Sets.newHashSet(productScope),Sets.newHashSet(sources));
        // Valid data
        Set<Integer> intersection = Sets.intersection(Sets.newHashSet(productScope),Sets.newHashSet(sources));
        List<Integer> noExits = Lists.newArrayList(difference);
        context.getFeeRuleVo().setPIdNotExist(noExits);
        List<Integer> handleScope = Lists.newArrayList(intersection);
        context.setProductScope(handleScope);
        log.info("{}- Product association - Legal data :{}, illegal data :{}", LOG_TITLE,JSON.toJSON(handleScope), JSON.toJSON(noExits)); }}Copy the code

BatchLinkProcessMediatorFactory

/ * * *@description: Batch associate setup processing broker factory *@Date: 2019/2/12 5:39 PM *@Author: Shi Dongdong -Seig Heil */
public final class BatchLinkProcessMediatorFactory {
    /** * returns the process-specific intermediary class *@param buzType
     * @return* /
    public static AbstractBatchLinkProcessMediator create(BatchLinkParam.ChooseTypeEnum buzType){
        switch (buzType){
            case dealerScope:
                return new BatchLinkDealerProcessMediator();
            case productScope:
                return new BatchLinkProductProcessMediator();
        }
        return null; }}Copy the code

3. Investor access attribute filtering – abstract code design

AbstractAccessPropHandler

/ * * *@descriptionAbstract access property handler *@Date: 2019/7/4 lies *@AuthorShang Lingyu. Shang */
public abstract class AbstractAccessPropHandler {

    /** * context object */
    FundAccessContext context;

    /** * Set of valid values */
    List<String> validList;

    /** * Parameter set */
    List<String> paramList;

    /** * check *@param context
     * @return* /
    public abstract boolean check(FundAccessContext context);

    /** * Parameter filter *@return* /
    protected void paramFilter(a) { List<String> illegalList = paramList.stream().filter(p -> ! validList.contains(p)).collect(Collectors.toList());if(CollectionsTools.isNotEmpty(illegalList)) {
            paramList.removeAll(illegalList);
            String paramValue = paramList.stream().collect(Collectors.joining(",")); context.getProp().setPropValue(paramValue); }}}Copy the code

DbAccessPropHandler

/ * * *@description: Configures type access attribute handlers *@Date: I will 2019/7/4 *@AuthorShang Lingyu. Shang */
public class DbAccessPropHandler extends AbstractAccessPropHandler {
    @Override
    public boolean check(FundAccessContext context) {
        this.context = context;
        FundAccessConstant.FundPropNameEnum fundPropNameEnum = context.getFundPropNameEnum();
        String value = context.getValue();
        if(value == null) {
            context.setMsg(MessageFormat.format("{0} attribute value cannot be null", fundPropNameEnum.name()));
            return false;
        }
        this.paramList = Arrays.stream(value.split(",")).collect(Collectors.toList());
        Map<String, List<String>> dictMap = context.getDictMap();
        this.validList = dictMap.get(fundPropNameEnum.name());
        paramFilter();
        if(CollectionsTools.isEmpty(paramList)) {
            context.setMsg(MessageFormat.format("{0} attribute value is invalid", fundPropNameEnum.name()));
            return false;
        }
        return true; }}Copy the code

EnumAccessPropHandler

/ * * *@description: enumerates type access property handlers *@Date: 2019/7/4 * also@AuthorShang Lingyu. Shang */
public class EnumAccessPropHandler extends AbstractAccessPropHandler {
    @Override
    public boolean check(FundAccessContext context) {
        this.context = context;
        FundAccessConstant.FundPropNameEnum fundPropNameEnum = context.getFundPropNameEnum();
        String value = context.getValue();
        if(value == null) {
            context.setMsg(MessageFormat.format("{0} attribute value cannot be null", fundPropNameEnum.name()));
            return false;
        }
        this.paramList = Arrays.stream(value.split(",")).collect(Collectors.toList());
        FundAccessConstant.EnumType enumType = fundPropNameEnum.getEnumType();
        EnumValue[] enumValues = fundPropNameEnum.getEnumValues();
        switch (enumType) {
            case INDEX:
                this.validList = Arrays.stream(enumValues).map(v -> v.getIndex()).map(i -> i.toString()).collect(Collectors.toList());
                break;
            case NAME:
                this.validList = Arrays.stream(enumValues).map(v -> v.getName()).collect(Collectors.toList());
                break;
            case DESC:
                this.validList = Arrays.stream(enumValues).map(v -> ((EnumDesc) v).getDesc()).collect(Collectors.toList());
                break;
            default:
                context.setMsg("Enumeration fetch field configuration error, please contact developer");
                return false;
        }
        paramFilter();
        if(CollectionsTools.isEmpty(paramList)) {
            context.setMsg(MessageFormat.format("{0} attribute value is invalid", fundPropNameEnum.name()));
            return false;
        }
        return true; }}Copy the code

NumberAccessPropHandler

/ * * *@description: Capital access attribute numeric type processor *@Date: 2019/7/4 comest *@AuthorShang Lingyu. Shang */
public class NumberAccessPropHandler extends AbstractAccessPropHandler {
    @Override
    public boolean check(FundAccessContext context) {
        boolean success = StringUtils.isNumeric(context.getValue());
        if(! success) { context.setMsg(context.getFundPropNameEnum().name() +"It must be a number.");
        }
        returnsuccess; }}Copy the code

Six, tail

Through the previous three articles, we comprehensively explained the relevant business model design and technical solution implementation of the rule engine of financial products. Rules Engine From the beginning of design, we introduced the open source Google Aviator component to achieve our cost formula configuration, at the same time in code implementation, by following good design principles and the introduction of relevant design patterns, thus improving the system’s scalability and scalability.

The financial product system has accompanied me from entry to resignation. It has accompanied my two-year career in M Company, and I have also witnessed its growth. Thanks to such a platform, I have been able to redesign a financial product platform for business development from scratch. Through the technical and innovation capabilities of myself and my team, we have finally created a financial product platform for our car finance business line. Please remember that it has a unique name — Taiji.