In the second-hand car business line, the application of vehicle, person and dealer information in the business audit process cannot be realized at the present stage. Therefore, in order to achieve this goal, the business side comes up with the relationship mapping service based on data collection and data query.

This paper is divided into two parts because of too many words.

The technical scheme design of relational Graph service – part ii

I. System architecture

From the above system architecture diagram, we can see:

  • 1. The relationship mapping service mainly provides two capabilities: data collection and data replay.
  • 2. Data collection is based on access message, consuming message notification inside business line, analyzing and storing based on metadata.
  • 3. Data replay Based on THE HTTP service, it provides the data replay service for business lines.
  • 4. The internal engine of the graph service mainly includes data acquisition, data processing, data segmentation and data storage.
  • 5. The phase I target of the RELATIONAL Atlas service is based on the current business volume and MySQL database storage.

Ii. Business Overview

From the above business flow chart, we can see:

  • 1. Data acquisition, based on access MQ messages, then business logic layer analysis of data, data processing and fragmentation, and storage in the database, data fragmentation into business tables, mainly including (B_person_INFO and B_VEHICLE_INFO).
  • 2. Data check: Based on HTTP interface, external according to the protocol specification of relational graph, define the check parameters, and return the matched data.

Iii. Program design

The figure above is the data that can be obtained by sorting out different scenarios according to the business. From the figure above, we can draw the following conclusions

  • 1. The first section is the classification of personal information, including: principal lender, spouse, guarantor, etc.
  • 2. The second column is data dimension, where different colors of cells indicate that data can be obtained at different scene stages.
  • 3. Based on MQ message notification, we can obtain the order number from the message, and then query the data of three parties to obtain relevant data items.

3.1. Data collection

Data collection is mainly used to synchronize MQ message notifications and historical data (required when online), and then the data is processed, segmented and stored in the database.

Above is a UML class diagram of the entire code design, as can be seen from the above diagram:

  • 1. The layer 1 “application scenario module” consists of two classes, one for message consumption and one for data synchronization.
  • 2. The “event listener module” of the second layer is divided according to business scenarios. For different business scenarios, listeners can notify corresponding processing classes to handle them.
  • 3. The third layer’s “data processing module” abstracts abstract classes and derived subclasses according to data types (i.e., principal lender, spouse, etc.).

3.1.1, SourceInfoContext

This class mainly encapsulates data processor context data information.

/ * * *@description: Source information context object *@Date : 2020/12/2 5:33 PM
 * @Author: Shi Dongdong -Seig Heil */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SourceInfoContext {
    /** * Order number */
    private String appCode;
    /** * id */
    private Long dealerId;
    /** * Service scenario */
    private SceneEnum scene;
    /** * Vehicle dealer submits information */
    private List<CarDealerInfoDTO> carDealers;
}

/ * * *@description: 1001-second batch submission; 1002- Access submission; 1003- Pre-credit submission; 1004- Vehicle evaluation Notes; 2001- Auto dealer account submission; *@Date : 2020/11/27 4:08 PM
 * @Author: Shi Dongdong -Seig Heil */
public enum SceneEnum implements EnumValue {

	SECOND_BATCH_SUBMIT(1001."Second batch submission"),
	ADMITTANCE_SUBMIT(1002."Submission of access"),
	BEFORE_LOAN_SUBMIT(1003."Pre-credit submission"),
	VEHICLE_EVALUATE_REMARK(1004."Vehicle Assessment Notes"),
	MERCHANT_ACCOUNT_SUBMIT(2001."Auto dealer Account Submission"),;private int index;
	private String value;

	SceneEnum(int index, String value ){
		this.value = value;
		this.index = index;
	}

	@Override
	public int getIndex(a) {
		return index;
	}

	@Override
	public String getName(a) {
		return value;
	}

	/** * get object * by index@param index
	 * @return* /
	public static SceneEnum getByIndex(int index){
		return Stream.of(SceneEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
	}
	/** * get the name by index *@param index
	 * @return* /
	public static String getNameByIndex(int index){
		SceneEnum find = Stream.of(SceneEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
		return null == find ? "": find.getName(); }}Copy the code

3.1.2, AbstractInfoHandler

This class mainly abstracts and encapsulates data processing, and is the upper abstract class of information processing module of vehicles, people and car dealers.


/ * * *@descriptionSource information data processing interface *@Date : 2020/12/2 6:34 PM
 * @Author: Shi Dongdong -Seig Heil */
public interface SourceInfoHandle {
    /** * Data assembly and data processing *@param context
     */
    void handle(SourceInfoContext context);
}


/ * * *@descriptionAbstract information processor *@Date : 2020/12/2 5:29 PM
 * @Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractInfoHandler<T> implements SourceInfoHandle{

    protected final static int NORMAL_DATA_STATUS_INDEX = DataStatusEnum.NORMAL.getIndex();
    protected final static int DELETED_DATA_STATUS_INDEX = DataStatusEnum.DELETED.getIndex();

    protected final static String EMPTY_VALUE = "-";
    /** * context container */
    protected static final ThreadLocal<SourceInfoContext> CONTEXT = new ThreadLocal<>();
    /** * Source type */
    protected final SourceTypeEnum sourceType;
    /** * Whether to flag to delete duplicate records */
    protected boolean removeDuplicate;

    @Autowired
    protected DiamondConfigProxy diamondConfigProxy;

    @Autowired
    protected SerialNoGenerator serialNoGenerator;

    @Autowired
    protected SourceInfoQuerier sourceInfoQuerier;

    public AbstractInfoHandler(SourceTypeEnum sourceType,boolean removeDuplicate) {
        this.sourceType = sourceType;
        this.removeDuplicate = removeDuplicate;
    }

    /** * execute *@param ctx
     */
    public void execute(SourceInfoContext ctx){
        try {
            CONTEXT.set(ctx);
            log.info("[execute]appCode={},Handler={},sourceType={}",ctx.getAppCode(),this.getClass().getSimpleName(),sourceType);
            handle(ctx);
        } catch (Exception e) {
            log.error("[execute]appCode={},Handler={},sourceType={},ex",ctx.getAppCode(),this.getClass().getSimpleName(),sourceType,e);
        }finally{ CONTEXT.remove(); }}/** * Set of existing records *@param sourceRecord
     * @param context
     * @return* /
    protected abstract List<T> queryExistsList(T sourceRecord, SourceInfoContext context);

    /** * Write the map record to the library *@paramSourceRecord Tripartite query data *@paramContext Context object */
    protected abstract void store(T sourceRecord,SourceInfoContext context);

    /** * Gets the object property field * from the configuration@return* /
    protected List<String> getPropertiesFromConfig(a){
        return getPropertiesFromConfigWithScene(CONTEXT.get().getScene());
    }

    /** * Gets the object property field * from the configuration@return* /
    protected List<String> getPropertiesFromConfigWithScene(SceneEnum scene){
        Map<SourceTypeEnum,Map<SceneEnum,List<String>>> mapping = JSON.parseObject(diamondConfigProxy.configGatherRules(),
                new TypeReference<Map<SourceTypeEnum,Map<SceneEnum,List<String>>>>(){});
        List<String> properties = mapping.get(sourceType).get(scene);
        return properties;
    }

    /** * Check whether the related fields of two objects are all the same *@paramSource Data source object *@paramTarget Graph database object *@return* /
    protected boolean isSameWithAnyFields(T source,T target){
        List<String> properties = getPropertiesFromConfig();
        if(CollectionsTools.isEmpty(properties)){
            return true;
        }
        Class sourceClazz = source.getClass();
        Class targetClazz = target.getClass();
        Field sourceFiled,targetFiled;
        Object sourceFieldValue,targetFieldValue;
        try {
            for (String property : properties) {
                sourceFiled = sourceClazz.getDeclaredField(property);
                targetFiled = targetClazz.getDeclaredField(property);
                sourceFiled.setAccessible(true);
                targetFiled.setAccessible(true);
                sourceFieldValue = sourceFiled.get(source);
                targetFieldValue = targetFiled.get(target);
                if(! Objects.equals(sourceFieldValue,targetFieldValue)) {return false; }}}catch (NoSuchFieldException e) {
            log.error(e.getMessage(), e);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage(), e);
        }
        return true;
    }

    /** * Copy from the target object to *@paramSource Data source object *@paramTarget Graph database object */
    protected void copy(T source,T target){
        List<String> properties = getPropertiesFromConfig();
        copy(source, target, properties);
    }

    protected void copy(Object source, Object target, List<String> properties){
        if(CollectionsTools.isEmpty(properties)){
            return;
        }
        Class sourceClazz = source.getClass();
        Class targetClazz = target.getClass();
        Field sourceFiled,targetFiled;
        try {
            for (String property : properties) {
                sourceFiled = sourceClazz.getDeclaredField(property);
                targetFiled = targetClazz.getDeclaredField(property);
                sourceFiled.setAccessible(true);
                targetFiled.setAccessible(true); targetFiled.set(target,sourceFiled.get(source)); }}catch (NoSuchFieldException e) {
            log.error(e.getMessage(), e);
        } catch(IllegalAccessException e) { log.error(e.getMessage(), e); }}/** * returns the processor class name *@return* /
    protected String getHandlerClassName(a){
        return this.getClass().getSimpleName();
    }
    /** * Get the business scenario * from the context container@return* /
    protected SceneEnum getSceneFromContext(a){
        return null == CONTEXT.get() ? null: CONTEXT.get().getScene(); }}Copy the code

3.1.3, AbstractVehicleInfoHandler

This class mainly abstracts the data processing that encapsulates the vehicle information, with only a derived class, VehicleInfoHandler.

/ * * *@descriptionAbstract natural person information processor *@Date : 2020/12/3 11:32 AM
 * @Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractVehicleInfoHandler extends AbstractInfoHandler<VehicleInfo> {


    @Resource
    protected VehicleInfoService vehicleInfoService;

    public AbstractVehicleInfoHandler(SourceTypeEnum sourceType) {
        super(sourceType,Boolean.FALSE);
    }


    @Override
    protected void store(VehicleInfo record,SourceInfoContext context) {
        if(Objects.nonNull(record)){
            List<VehicleInfo> recordList = queryExistsList(record,context);
            if(CollectionsTools.isNotEmpty(recordList)){
                for (VehicleInfo sourceRecord : recordList) {
                    if(isSameWithAnyFields(sourceRecord.with(),record.with())){
                        return;
                    }
                }
            }

            record.setDataStatus(NORMAL_DATA_STATUS_INDEX);
            record.setDataCode(serialNoGenerator.generalVehicleInfoDataCode());
            record.setScene(context.getScene().getIndex());
            record.setSourceType(sourceType.getIndex());
            log.info("[store][insertRecord]appCode={},Handler={},sourceType={}",record.getAppCode(),getHandlerClassName(),sourceType); vehicleInfoService.insertRecord(record.with()); }}@Override
    protected List<VehicleInfo> queryExistsList(VehicleInfo sourceRecord,SourceInfoContext context) {
        VehicleInfoForm queryForm = VehicleInfoForm.builder()
                .appCode(context.getAppCode())
                .sourceType(sourceType.getIndex())
                //.scene(context.getScene().getIndex())
                .dataStatus(NORMAL_DATA_STATUS_INDEX)
                .build();
        returnvehicleInfoService.queryList(queryForm); }}/ * * *@description: Vehicle information processor *@Date : 2020/12/2 6:22 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
@Slf4j
public class VehicleInfoHandler extends AbstractVehicleInfoHandler{

    public VehicleInfoHandler(a) {
        super(SourceTypeEnum.VEHICLE_INFO);
    }

    @Override
    public void handle(SourceInfoContext context) {
        VehicleInfo sourceRecord = sourceInfoQuerier.queryVehicleInfo(context.getAppCode());
        SceneEnum scene = context.getScene();
        if(SceneEnum.ADMITTANCE_SUBMIT == scene){
            super.store(sourceRecord,context);
        }
        if(SceneEnum.BEFORE_LOAN_SUBMIT == scene || SceneEnum.VEHICLE_EVALUATE_REMARK == scene){
            List<VehicleInfo> recordList = queryExistsList(sourceRecord,context);
            Map<String,CarEvaluationBo> vinMapping = sourceInfoQuerier.queryVinMapping(context.getAppCode());
            log.info("[EvaluateRemark],appCode={},vinMapping={}",context.getAppCode(), JSONObject.toJSONString(vinMapping));
            recordList.forEach(each -> {
                CarEvaluationBo apply = vinMapping.get(each.getVin());
                if(Objects.nonNull(apply)){
                    log.info("[EvaluateRemark][Update],appCode={},vin={},remark={}",context.getAppCode(),each.getVin(),apply.getEvaluationRemarks()); each.setEvaluateRemark(StringTools.isNotEmpty(apply.getEvaluationRemarks()) ? apply.getEvaluationRemarks() : EMPTY_VALUE); each.setModifiedTime(TimeTools.createNowTime()); vehicleInfoService.updateByPrimaryKeySelective(each); }}); }}}Copy the code

3.1.4, AbstractCarDealerInfoHandler

The main abstract encapsulation dealer information data processing, there are three derived class CarDealerLegalPersonInfoHandler, CarDealerPayeeInfoHandler, CarDealerPrincipalInfoHandler.

/ * * *@descriptionAbstract dealer information processor *@Date : 2020/12/7 11:19 AM
 * @Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractCarDealerInfoHandler extends AbstractInfoHandler<DealerInfo> {

    @Resource
    DealerInfoService dealerInfoService;

    public AbstractCarDealerInfoHandler(SourceTypeEnum sourceType) {
        super(sourceType, Boolean.TRUE);
    }
    /** * query auto dealer information *@param dealerId
     * @return* /
    protected abstract List<DealerInfo> queryList(Long dealerId);

    /** * whether * should be processed@param record
     * @return* /
    boolean shouldStore(DealerInfo record){
        returnObjects.nonNull(record) && StringTools.isNotEmpty(record.getName()) && ! Objects.equals(record.getName(),EMPTY_VALUE); }@Override
    protected void store(DealerInfo record,SourceInfoContext context) {
        if(shouldStore(record)){
            // Whether to delete historical similarity records
            if(removeDuplicate){
                List<DealerInfo> recordList = queryExistsList(record,context);
                if(CollectionsTools.isNotEmpty(recordList)){
                    for (DealerInfo sourceRecord : recordList) {
                        if(isSameWithAnyFields(sourceRecord.with(),record.with())){
                            return; }}}}// Save a new record
            record.setDataCode(serialNoGenerator.generalDealerInfoDataCode());
            record.setDataStatus(NORMAL_DATA_STATUS_INDEX);
            record.setScene(context.getScene().getIndex());
            record.setSourceType(sourceType.getIndex());
            dealerInfoService.insertRecord(record.with());
        }else{
            log.debug("[execute]store NullObject,Handler={},sourceType={},context={}".this.getClass().getSimpleName(),sourceType, JSONObject.toJSONString(context)); }}@Override
    public void handle(SourceInfoContext context) {
        List<DealerInfo> listWithoutSameFields = new ArrayList<>();
        List<DealerInfo> list;
        // If the message is submitted by the car dealer account, it can be directly obtained without inquiry; Otherwise, query the information based on the dealer ID.
        if(CollectionsTools.isEmpty(context.getCarDealers())){
            list = queryList(context.getDealerId());
        } else{ list = context.getCarDealers().stream().filter(each -> each.getSourceType().intValue() == sourceType.getIndex()) .map(  source -> DealerInfo.builder() .externalId(Objects.toString(source.getExternalId())) .appCode(Objects.toString(source.getMerchantId())) .sourceType(source.getSourceType()) .name(source.getName()) .idNo(source.getIdNo()) .primaryMobile(source.getCellphone()) .companyAddressDetail(source.getDealerAddress()) .companyName(source.getDealerName()) .creditCardNo(source.getCreditCardNo()) .build() ).collect(Collectors.toList()); }// Remove some items with similar field values
        for (DealerInfo person : list) {
            if(listWithoutSameFields.stream().filter(r -> isSameWithAnyFields(r, person)).count() > 0) {continue;
            }
            listWithoutSameFields.add(person);
        }
        log.info("[storeWithBatch]dealerId={},Handler={},sourceType={},list={}",context.getDealerId(),getHandlerClassName(),sourceType, JSONObject.toJSONString(list));
        this.storeWithBatch(listWithoutSameFields,context);
    }

    @Override
    protected List<DealerInfo> queryExistsList(DealerInfo sourceRecord, SourceInfoContext context) {
        DealerInfoForm queryForm = DealerInfoForm.builder()
                .sourceType(sourceType.getIndex())
                .scene(context.getScene().getIndex())
                .appCode(sourceRecord.getAppCode())
                .dataStatus(NORMAL_DATA_STATUS_INDEX)
                .build();
        List<DealerInfo> recordList = dealerInfoService.queryList(queryForm);
        return recordList;
    }

    /** * Batch insert *@param recordList
     */
    protected void storeWithBatch(List<DealerInfo> recordList,SourceInfoContext context){
        if(CollectionsTools.isEmpty(recordList)){
            return;
        }
        for(DealerInfo record : recordList) { store(record, context); }}}/ * * *@description: Car dealer legal person (name, ID number, mobile phone number) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class CarDealerLegalPersonInfoHandler extends AbstractCarDealerInfoHandler {

    public CarDealerLegalPersonInfoHandler(a) {
        super(SourceTypeEnum.CAR_DEALER_LEGAL_PERSON);
    }

    @Override
    protected List<DealerInfo> queryList(Long dealerId) {
        returnsourceInfoQuerier.queryCarDealerLegalInfo(dealerId); }}/ * * *@description: Car dealer payee (name, ID number, mobile phone number, bank card number) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class CarDealerPayeeInfoHandler extends AbstractCarDealerInfoHandler {

    public CarDealerPayeeInfoHandler(a) {
        super(SourceTypeEnum.CAR_DEALER_PAYEE);
    }

    @Override
    protected List<DealerInfo> queryList(Long dealerId) {
        returnsourceInfoQuerier.queryCarDealerPayeeInfo(dealerId); }}/ * * *@description: Vehicle dealer responsible person information (name, ID number, mobile phone number, company name, company address) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class CarDealerPrincipalInfoHandler extends AbstractCarDealerInfoHandler {

    public CarDealerPrincipalInfoHandler(a) {
        super(SourceTypeEnum.CAR_DEALER_PRINCIPAL);
    }

    @Override
    protected List<DealerInfo> queryList(Long dealerId) {
        returnsourceInfoQuerier.queryCarDealerPrincipalInfo(dealerId); }}Copy the code

3.1.5, AbstractPersonInfoHandler

This class mainly abstracts and encapsulates the data processing of human information. It has three derived classes CreditorInfoHandler and MateInfoHandler, etc.

/ * * *@descriptionAbstract natural person information processor *@Date : 2020/12/3 11:32 AM
 * @Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractPersonInfoHandler extends AbstractInfoHandler<PersonInfo>{


    @Autowired
    protected PersonInfoService personInfoService;

    public AbstractPersonInfoHandler(SourceTypeEnum sourceType, boolean removeDuplicate) {
        super(sourceType, removeDuplicate);
    }

    /** * whether * should be processed@param record
     * @return* /
    protected boolean shouldStore(PersonInfo record){
        returnObjects.nonNull(record) && StringTools.isNotEmpty(record.getName()) && ! Objects.equals(record.getName(),EMPTY_VALUE); }@Override
    protected void store(PersonInfo record,SourceInfoContext context) {
        if(shouldStore(record)){
            // Whether to delete historical similarity records
            if(removeDuplicate){
                List<PersonInfo> recordList = queryExistsList(record,context);
                if(CollectionsTools.isNotEmpty(recordList)){
                    for (PersonInfo sourceRecord : recordList) {
                        if(isSameWithAnyFields(sourceRecord.with(),record.with())){
                            return; }}}}// Save a new record
            record.setDataCode(serialNoGenerator.generalPersonInfoDataCode());
            record.setDataStatus(NORMAL_DATA_STATUS_INDEX);
            record.setScene(context.getScene().getIndex());
            record.setSourceType(sourceType.getIndex());
            personInfoService.insertRecord(record.with());
        }else{
            log.debug("[execute]store NullObject,Handler={},sourceType={},context={}".this.getClass().getSimpleName(),sourceType, JSONObject.toJSONString(context)); }}@Override
    protected List<PersonInfo> queryExistsList(PersonInfo sourceRecord, SourceInfoContext context) {
        PersonInfoForm queryForm = PersonInfoForm.builder()
                .sourceType(sourceType.getIndex())
                .scene(context.getScene().getIndex())
                .appCode(context.getAppCode())
                .dataStatus(NORMAL_DATA_STATUS_INDEX)
                .build();
        List<PersonInfo> recordList = personInfoService.queryList(queryForm);
        return recordList;
    }

    /** * Batch insert *@param recordList
     */
    protected void storeWithBatch(List<PersonInfo> recordList,SourceInfoContext context){
        if(CollectionsTools.isEmpty(recordList)){
            return;
        }
        recordList.forEach(each -> {
            log.info("[storeWithBatch]appCode={},Handler={},sourceType={}",each.getAppCode(),getHandlerClassName(),sourceType); each.setDataCode(serialNoGenerator.generalPersonInfoDataCode()); each.setDataStatus(NORMAL_DATA_STATUS_INDEX); each.setSourceType(sourceType.getIndex()); each.setScene(context.getScene().getIndex()); each.with(); }); personInfoService.batchInsert(recordList); }}/ * * *@description: Main lender information (name, ID number, mobile phone number, company name, company address, landline number, household address, residential address, residential telephone number, bank card number) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class CreditorInfoHandler extends AbstractPersonInfoHandler {

    public CreditorInfoHandler(a) {
        super(SourceTypeEnum.CREDITOR_INFO, Boolean.FALSE);
    }

    @Override
    public void handle(SourceInfoContext context) {
        // Query new modification records
        PersonInfo record = sourceInfoQuerier.queryCreditorInfo(context.getAppCode());
        if(! shouldStore(record)){return;
        }
        record.with();

        // Delete [subset data] from [current scene] [previous scene]
        List<String> propertiesSecond = super.getPropertiesFromConfigWithScene(SceneEnum.SECOND_BATCH_SUBMIT);
        PersonInfoForm propertiesSecondForm = PersonInfoForm.builder().appCode(context.getAppCode())
                .sourceType(sourceType.getIndex())
                .scene(SceneEnum.SECOND_BATCH_SUBMIT.getIndex())
                .dataStatus(NORMAL_DATA_STATUS_INDEX)
                .build();
        List<String> propertiesAdmittance = super.getPropertiesFromConfigWithScene(SceneEnum.ADMITTANCE_SUBMIT);
        PersonInfoForm propertiesAdmittanceForm = PersonInfoForm.builder().appCode(context.getAppCode())
                .sourceType(sourceType.getIndex())
                .scene(SceneEnum.ADMITTANCE_SUBMIT.getIndex())
                .dataStatus(NORMAL_DATA_STATUS_INDEX)
                .build();
        List<String> propertiesBeforeLoan = super.getPropertiesFromConfigWithScene(SceneEnum.BEFORE_LOAN_SUBMIT); PersonInfoForm propertiesBeforeLoanForm = PersonInfoForm.builder().appCode(context.getAppCode()) .sourceType(sourceType.getIndex()) .scene(SceneEnum.BEFORE_LOAN_SUBMIT.getIndex()) .dataStatus(NORMAL_DATA_STATUS_INDEX)  .build();switch (context.getScene()){
            case SECOND_BATCH_SUBMIT:
                // Soft delete subsets
                copy(record, propertiesSecondForm, propertiesSecond);
                personInfoService.updateByQuery(propertiesSecondForm);
                break;
            case ADMITTANCE_SUBMIT:
                // Soft delete subsets
                copy(record, propertiesSecondForm, propertiesSecond);
                personInfoService.updateByQuery(propertiesSecondForm);
                // Soft delete subsets of access submission
                propertiesAdmittance.addAll(propertiesSecond);
                copy(record, propertiesAdmittanceForm, propertiesAdmittance);
                personInfoService.updateByQuery(propertiesAdmittanceForm);
                break;
            case BEFORE_LOAN_SUBMIT:
                // Soft delete subsets
                copy(record, propertiesSecondForm, propertiesSecond);
                personInfoService.updateByQuery(propertiesSecondForm);
                // Soft delete subsets of access submission
                propertiesAdmittance.addAll(propertiesSecond);
                copy(record, propertiesAdmittanceForm, propertiesAdmittance);
                personInfoService.updateByQuery(propertiesAdmittanceForm);
                // Soft delete subsets
                propertiesBeforeLoan.addAll(propertiesAdmittance);
                copy(record, propertiesBeforeLoanForm, propertiesBeforeLoan);
                personInfoService.updateByQuery(propertiesBeforeLoanForm);
                break;
            default:
                break;
        }

        // Add a new modification record
        super.store(record,context); }}/ * * *@description: Spouse information (name, ID number, mobile phone number, company name) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class MateInfoHandler extends AbstractPersonInfoHandler {

    public MateInfoHandler(a) {
        super(SourceTypeEnum.MATE, Boolean.TRUE);
    }

    @Override
    public void handle(SourceInfoContext context) {
        PersonInfo record = sourceInfoQuerier.queryMateInfo(context.getAppCode());
        super.store(record,context); }}/ * * *@description: Guarantor information (name, ID number, mobile phone number, company name, company address, landline number, household address) *@Date : 2020/12/2 5:26 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class GuarantorInfoHandler extends AbstractPersonInfoHandler {

    public GuarantorInfoHandler(a) {
        super(SourceTypeEnum.GUARANTOR_INFO, Boolean.TRUE);
    }

    @Override
    public void handle(SourceInfoContext context) {
        PersonInfo record = sourceInfoQuerier.queryGuarantorInfo(context.getAppCode());
        super.store(record,context); }}Copy the code

3.1.6, SourceInfoQuerier

This class abstracts the retrieval of service query data and encapsulates the poJOs required within the processor.

/ * * *@description: Source information finder *@Date : 2020/12/3 10:55 AM
 * @Author: Shi Dongdong -Seig Heil */
@Component
@Slf4j
public class SourceInfoQuerier {
    
    // Omit dependency injection Service
    
    /** ** query the main lender information *@param appCode
         * @return* /
        public PersonInfo queryCreditorInfo(String appCode){
            CustomerPersonInfo record = customerPersonInfoService.queryByAppCode(appCode);
            if(Objects.isNull(record)){
                return null;
            }
            List<CustomerCardInfo> customerCardInfos = customerCardInfoService.queryList(CustomerCardInfoForm.builder().appCode(appCode).build());
            CustomerCardInfo customerCardInfo = CollectionsTools.isNotEmpty(customerCardInfos) ? customerCardInfos.get(0) : customerCardInfo_NULL;
            return PersonInfo.builder()
                    .externalId(Objects.toString(record.getId()))
                    .appCode(appCode)
                    .name(record.getName())
                    .idNo(record.getIdNumber())
                    .primaryMobile(record.getMobile())
                    .secondMobile(record.getMobile2())
                    .companyName(record.getNowCompany())
                    .companyAddressOrigin(record.getNowUnitAddress())
                    .companyAddressProvince(record.getNowUnitProvinceName())
                    .companyAddressCity(record.getNowUnitCityName())
                    .companyAddressDistrict(record.getNowUnitDistrictName())
                    .companyAddressDetail(record.getNowUnitAddress())
                    .companyTelephone(record.getNowUnitTel())
                    .censusAddressOrigin(record.getHometownAddress())
                    .censusAddressProvince(record.getHometownProvinceName())
                    .censusAddressCity(record.getHometownCityName())
                    .censusAddressDistrict(record.getHometownDistrictName())
                    .censusAddressDetail(record.getHometownAddress())
                    .residenceAddressOrigin(record.getResidenceAddress())
                    .residenceAddressProvince(record.getResidenceProvinceName())
                    .residenceAddressCity(record.getResidenceCityName())
                    .residenceAddressDistrict(record.getResidenceDistrictName())
                    .residenceAddressDetail(record.getResidenceAddress())
                    .residenceTelephone(record.getResidenceTel())
                    .creditCardNo(Objects.nonNull(customerCardInfo) ? customerCardInfo.getRepAccountNo() : EMPTY_VALUE)
                    .build();
        }
    
        /** * query guarantor information *@param appCode
         * @return* /
        public PersonInfo queryGuarantorInfo(String appCode){
            CustomerRelatedInfo record = customerRelatedInfoService.queryByAppCode(appCode);
            if(Objects.isNull(record)){
                return null;
            }
            return PersonInfo.builder()
                    .externalId(Objects.toString(record.getId()))
                    .appCode(appCode)
                    .name(record.getDbName())
                    .idNo(record.getDbIdNo())
                    .primaryMobile(record.getDbMobile())
                    .companyName(record.getDbNowCompany())
                    .companyAddressOrigin(record.getDbNowUnitAddress())
                    .companyAddressProvince(record.getDbNowUnitProvinceName())
                    .companyAddressCity(record.getDbNowUnitCityName())
                    .companyAddressDistrict(record.getDbNowUnitDistrictName())
                    .companyAddressDetail(record.getDbNowUnitAddress())
                    //.companyTelphone()
                    .censusAddressOrigin(record.getDbAddress())
                    .censusAddressProvince(record.getDbProvinceName())
                    .censusAddressCity(record.getDbCityName())
                    .censusAddressDistrict(record.getDbDistrictName())
                    .censusAddressDetail(record.getDbAddress())
                    .build();
        }
        
        // omit other member methods
}
Copy the code

3.1.7, SubmitEventContext

/ * * *@description: Submits the event context object *@Date : 2020/12/2 6:29 PM
 * @Author: Shi Dongdong -Seig Heil */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SubmitEventContext {
    /** * Order number */
    @NotNull
    private String appCode;
    /** * ID */
    @NotNull
    private Long merchantId;
    /** * Service scenario */
    @NotNull
    private SceneEnum scene;
    /** * Vehicle dealer submits information */
    private List<CarDealerInfoDTO> carDealers;
}

Copy the code

3.1.8, AbstractSubmitListener

Listener abstract class that maintains data handler members for listener notifications.

/ * * *@description: submits the event listening interface *@Date : 2020/12/2 6:29 PM
 * @Author: Shi Dongdong -Seig Heil */
public interface SubmitEventListen {
    /** * subscribe to events *@param context
     */
    void subscribe(SubmitEventContext context);
}

/ * * *@descriptionAbstract submit event listener *@Date : 2020/12/2 5:19 PM
 * @Author: Shi Dongdong -Seig Heil */
@Slf4j
public abstract class AbstractSubmitListener implements SubmitEventListen {
    /** * pay attention to the scene */
    protected SceneEnum sceneEnum;
    /** * processor set */
    protected List<AbstractInfoHandler> handlers = Lists.newArrayListWithExpectedSize(20);

    public AbstractSubmitListener(SceneEnum sceneEnum) {
        this.sceneEnum = sceneEnum;
    }

    @Override
    public void subscribe(SubmitEventContext context) {
        String appCode = context.getAppCode();
        if(context.getScene() == sceneEnum){
            log.info("[SubmitEventListen],appCode={},handlers={}",appCode,handlers.toArray()); handlers.forEach(each -> { SourceInfoContext sourceInfoContext = SourceInfoContext.builder() .appCode(appCode) .dealerId(context.getMerchantId()) .carDealers(context.getCarDealers()) .scene(context.getScene()) .build(); each.execute(sourceInfoContext); }); }}}/ * * *@description: Access commit event listener *@Date : 2020/12/2 5:17 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class AdmittanceSubmitListener extends AbstractSubmitListener {

    @Resource
    CreditorInfoHandler creditorInfoHandler;

    @Resource
    MateInfoHandler mateInfoHandler;

    @Resource
    GuarantorInfoHandler guarantorInfoHandler;

    @Resource
    SellerInfoOfOldCarHandler sellerInfoOfOldCarHandler;

    @Resource
    EmergencyContactOneInfoHandler emergencyContactOneInfoHandler;

    @Resource
    EmergencyContactTwoInfoHandler emergencyContactTwoInfoHandler;

    @Resource
    VehicleInfoHandler vehicleInfoHandler;

    public AdmittanceSubmitListener(a) {
        super(SceneEnum.ADMITTANCE_SUBMIT);
    }

    @PostConstruct
    void init(a){
        this.handlers.add(creditorInfoHandler);
        this.handlers.add(mateInfoHandler);
        this.handlers.add(guarantorInfoHandler);
        this.handlers.add(sellerInfoOfOldCarHandler);
        this.handlers.add(emergencyContactOneInfoHandler);
        this.handlers.add(emergencyContactTwoInfoHandler);
        this.handlers.add(vehicleInfoHandler); }}/ * * *@description: Pre-loan commit event listener *@Date : 2020/12/2 5:17 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class BeforeLoanSubmitListener extends AbstractSubmitListener {

    @Resource
    CreditorInfoHandler creditorInfoHandler;

    @Resource
    VehicleInfoHandler vehicleInfoHandler;

    public BeforeLoanSubmitListener(a) {
        super(SceneEnum.BEFORE_LOAN_SUBMIT);
    }

    @PostConstruct
    void init(a){
        this.handlers.add(creditorInfoHandler);
        this.handlers.add(vehicleInfoHandler); }}/ * * *@description: Auto dealer account submits event listener *@Date : 2020/12/2 5:17 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class MerchantSubmitListener extends AbstractSubmitListener {

    @Resource
    CarDealerLegalPersonInfoHandler carDealerLegalPersonInfoHandler;

    @Resource
    CarDealerPrincipalInfoHandler carDealerPrincipalInfoHandler;

    @Resource
    CarDealerPayeeInfoHandler carDealerPayeeInfoHandler;

    public MerchantSubmitListener(a) {
        super(SceneEnum.MERCHANT_ACCOUNT_SUBMIT);
    }

    @PostConstruct
    void init(a){
        this.handlers.add(carDealerLegalPersonInfoHandler);
        this.handlers.add(carDealerPrincipalInfoHandler);
        this.handlers.add(carDealerPayeeInfoHandler); }}/ * * *@description: second batch commit event listener *@Date : 2020/12/2 5:17 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class SecondBatchSubmitListener extends AbstractSubmitListener {

    @Resource
    CreditorInfoHandler creditorInfoHandler;

    @Resource
    SellerInfoHandler sellerInfoHandler;

    @Resource
    SalesManagerInfoHandler salesManagerInfoHandler;

    public SecondBatchSubmitListener(a) {
        super(SceneEnum.SECOND_BATCH_SUBMIT);
    }

    @PostConstruct
    void init(a){
        this.handlers.add(creditorInfoHandler);
        this.handlers.add(sellerInfoHandler);
        this.handlers.add(salesManagerInfoHandler); }}/ * * *@description: Vehicle evaluation Remarks Event Monitor * Pay attention to order status: * 1, review returned to credit granting -2101, * 2, secondary risk control (final review) rejected -3910, * 3, waiting for pre-loan review (pre-loan materials submitted) -4000 *@Date : 2020/12/29 3:22 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
public class VehicleEvaluateRemarkListener extends AbstractSubmitListener {

    @Resource
    VehicleInfoHandler vehicleInfoHandler;

    public VehicleEvaluateRemarkListener(a) {
        super(SceneEnum.VEHICLE_EVALUATE_REMARK);
    }

    @PostConstruct
    void init(a){
        this.handlers.add(vehicleInfoHandler); }}Copy the code

3.1.9, SubmitEventMulticaster

The class is based on the observer pattern, inform all listeners processing, such as SecondBatchSubmitListener, AdmittanceSubmitListener, etc.

/ * * *@description: Submits the event broadcaster *@Date : 2020/12/2 6:45 PM
 * @Author: Shi Dongdong -Seig Heil */
@Component
@Slf4j
public class SubmitEventMulticaster {
    /**
     * 监听器成员
     */
    protected final List<SubmitEventListen> listeners = new ArrayList<>();

    @Resource
    SecondBatchSubmitListener secondBatchSubmitListener;

    @Resource
    AdmittanceSubmitListener admittanceSubmitListener;

    @Resource
    BeforeLoanSubmitListener beforeLoanSubmitListener;

    @Resource
    MerchantSubmitListener merchantAccountSubmitEvent;

    @Resource
    VehicleEvaluateRemarkListener vehicleEvaluateRemarkListener;

    @PostConstruct
    void init(a){
        listeners.add(secondBatchSubmitListener);
        listeners.add(admittanceSubmitListener);
        listeners.add(beforeLoanSubmitListener);
        listeners.add(merchantAccountSubmitEvent);
        listeners.add(vehicleEvaluateRemarkListener);
    }

    /** * External exposed interface *@paramContext Context */
    public void execute(SubmitEventContext context){
        log.info("[execute]appCode={},ctx={}",context.getAppCode(),JSONObject.toJSONString(context)); listeners.forEach(event -> event.subscribe(context)); }}Copy the code

3.1.10, HistoryDataSynchronizer

Historical data synchronizer

/ * * *@descriptionOrder: historical data (| dealer) synchronizer *@Date : 2020/12/7 11:49 AM
 * @Author: Shi Dongdong -Seig Heil */
@Component
@Slf4j
public class HistoryDataSynchronizer {


    @Resource
    SubmitEventMulticaster submitEventMulticaster;

    @Resource
    OrderInfoService orderInfoService;

    @Resource
    DealerService dealerService;

    @Resource
    ExecutorService synchronizeThreadPoolExecutor;
    /** ** synchronize order *@param request
     */
    public void synchronizeOrders(Request<String> request){
        boolean byScope = CollectionsTools.isNotEmpty(request.getScope());
        List<OrderInfo.SimpleEntity> recordList = byScope ? orderInfoService.queryTargets(request.getScope()) : orderInfoService.queryAll();
        Map<String,Integer> orderMap = recordList.stream()
                .collect(Collectors.toMap(OrderInfo.SimpleEntity::getAppCode,OrderInfo.SimpleEntity::getStatus));
        Set<String> appCodes = orderMap.keySet();
        if(request.getLimit() > 0){
            appCodes = appCodes.stream().limit(request.getLimit()).collect(Collectors.toSet());
        }

        // Order 3 scenarios
        List<SceneEnum> sceneEnumList;
        List<SubmitEventContext> contextList;
        SubmitEventContext context;

        StopWatch stopWatch = new StopWatch();
        stopWatch.start("synchronizeOrders");
        log.info("[synchronizeOrders]count={}",appCodes.size());
        LongAdder adder = new LongAdder();

        // Order traversal executes three scenarios scenario events
        for(String appCode : appCodes){
            Integer status = orderMap.get(appCode);
            sceneEnumList = OrderScene.getScene(status);
            if(! CollectionUtils.isEmpty(sceneEnumList)){ contextList =new ArrayList<>(sceneEnumList.size());
                for (SceneEnum sceneEnum: sceneEnumList) {
                    context = SubmitEventContext.builder()
                            .appCode(appCode)
                            .scene(sceneEnum)
                            .build();
                    contextList.add(context);
                    adder.increment();
                    log.info("[synchronizeOrders]current={},appCode={},context={}",adder.intValue(), appCode, JSON.toJSONString(context));
                    //asyncCall(context);
                }
                asyncCall(contextList);
            }else {
                log.warn("[synchronizeOrders][sceneEnum] warning!!! appCode={},status={}", appCode, status);
            }
        }
        stopWatch.stop();
        log.info("[synchronizeOrders]finished,total={},duration={}",adder.intValue(),stopWatch.getLastTaskTimeMillis() / 1000);
    }

    /** ** **@param request
     */
    public void synchronizeDealers(Request<Long> request){
        boolean byScope = CollectionsTools.isNotEmpty(request.getScope());
        List<Long> ids = byScope ? dealerService.queryTargets(request.getScope()) : dealerService.queryAllIds();
        if(request.getLimit() > 0){
            ids = ids.stream().limit(request.getLimit()).collect(Collectors.toList());
        }
        SubmitEventContext context;
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("[synchronizeDealers]count={}",ids.size());
        LongAdder adder = new LongAdder();
        for(Long merchantId : ids){
            context = SubmitEventContext.builder()
                    .appCode(Objects.toString(merchantId))
                    .merchantId(merchantId)
                    .scene(SceneEnum.MERCHANT_ACCOUNT_SUBMIT)
                    .build();
            adder.increment();
            log.info("[synchronizeDealers]current={},merchantId={},context={}",adder.intValue(),merchantId, JSON.toJSONString(context));
            asyncCall(context);
        }
        stopWatch.stop();
        log.info("[synchronizeDealers]finished,total={},,duration={}",adder.intValue(),stopWatch.getLastTaskTimeMillis() / 1000);
    }

    /** * Write * asynchronously@param context
     */
    void asyncCall(SubmitEventContext context){
        try {
            synchronizeThreadPoolExecutor.execute(() -> submitEventMulticaster.execute(context));
        } catch (Exception e) {
            log.error("[asyncCall]appCode={},context={}",context.getAppCode(),JSON.toJSONString(context)); }}void asyncCall(List<SubmitEventContext> contextList){
        try {
            synchronizeThreadPoolExecutor.execute(() -> {
                for(SubmitEventContext context : contextList) { submitEventMulticaster.execute(context); }}); }catch (Exception e) {
            log.error("[asyncCall]size={},contextList={}",contextList.size(),JSON.toJSONString(contextList)); }}/ * * *@description: Order state and scenario mapping configuration enumeration class *@Date : 2020/12/11 10:20 AM
     * @Author: Shi Dongdong -Seig Heil */
    enum OrderScene{
        /** ** second batch commit */
        SECOND_BATCH_SUBMIT(status -> status.intValue() <= 1200, SceneEnum.SECOND_BATCH_SUBMIT),
        /** * Access submission */
        ADMITTANCE_SUBMIT(status -> status.intValue() > 1200 &&  status.intValue() <= 2100, SceneEnum.SECOND_BATCH_SUBMIT, SceneEnum.ADMITTANCE_SUBMIT),
        /** ** submitted before credit */
        LOAN_BEFORE_SUBMIT(status -> status.intValue() > 2100 &&  status.intValue() < 9999, SceneEnum.SECOND_BATCH_SUBMIT, SceneEnum.ADMITTANCE_SUBMIT, SceneEnum.BEFORE_LOAN_SUBMIT);

        private Predicate<Integer> predicate;
        private List<SceneEnum> sceneEnumList;

        OrderScene(Predicate<Integer> predicate, SceneEnum... scenes){
            this.predicate = predicate;
            this.sceneEnumList = Arrays.stream(scenes).collect(Collectors.toList());
        }
        /** * Get the scene by state *@param status
         * @return* /
        public static List<SceneEnum> getScene(Integer status){
            Optional<OrderScene> optional = Stream.of(OrderScene.values()).filter(each -> each.predicate.test(status)).findFirst();
            return optional.isPresent() ? optional.get().sceneEnumList : null; }}/ * * *@description: Wrapper request parameter *@Date : 2020/12/11 10:19 AM
     * @Author: Shi Dongdong -Seig Heil */
    @Data
    @NoArgsConstructor
    public static class Request<T> {
        /** * Limit number of entries */
        @APIModelProperty (value = "limit number of entries ",dataType =" java.lang.integer ")
        protected int limit;
        /** * specifies the range */
        @APIModelProperty (value = "specified range ",dataType = "java.lang.List
      
       ")
      
        protectedList<T> scope; }}/ * * *@description: Vehicle evaluation business object *@Date : 2020/12/12 9:55 AM
 * @Author: Shi Dongdong -Seig Heil */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CarEvaluationBo {
    /** * car_price_evaluation_apply.id */
    @ ApiModelProperty (value = "primary key" and name = "id", dataType = "Java. Lang. Integer")
    private Integer id;

    /** * car_price_evaluation_apply.app_code */
    @APIModelProperty (value=" order number ",name="app_code",dataType=" java.lang.string ")
    private String appCode;
    /** * car_price_evaluation_apply.vin */
    @ ApiModelProperty (value = "vin code," name = "vin", dataType = "Java. Lang. String")
    private String vin;
    /** * car_price_evaluation_apply.evaluation_remarks */
    @apiModelProperty (value=" evaluation_remarks", name="evaluation_remarks",dataType=" java.lang.string ")
    private String evaluationRemarks;
}
Copy the code

3.1.11, RabbitConsumer

/ * * *@description: RabbitMQ consumer *@Date: 2020/7/10 4:02 PM *@Author: Shi Dongdong -Seig Heil */
@Component
@Slf4j
public class RabbitConsumer {

    final static SceneEnum SCENE_MERCHANT_ACCOUNT_SUBMIT = SceneEnum.MERCHANT_ACCOUNT_SUBMIT;

    static String HOST_ADDRESS;

    static {
        try {
            HOST_ADDRESS = InetAddress.getLocalHost().getHostAddress();
            log.info("[RabbitConsumer],HOST_ADDRESS={}",HOST_ADDRESS);
        } catch (UnknownHostException e) {
            log.error("[RabbitConsumer],init HOST_ADDRESS exception",e);
            HOST_ADDRESS = "127.0.0.0"; }}@Autowired
    ExecutorService commonThreadPoolExecutor;

    @Autowired
    SubmitEventMulticaster submitEventMulticaster;

    @Autowired
    DiamondConfigProxy diamondConfigProxy;

    @Autowired
    RedisService redisService;

    @Autowired
    TraceLogFacade traceLogFacade;

    /** * Subscription order center changes *@param message
     */
    public void subscribeOrderStatus(String message){
        final String LOG_TITLE = "subscribeOrderStatus#orderCenterStatus|ordercenter-key-node-message";
        String appCode = null;
        Integer status = null;
        try {
            BusMessage busMessage = JSON.parseObject(message,new TypeReference<BusMessage>(){});
            log.info("{}, params={}", LOG_TITLE, JSON.toJSONString(busMessage));
            appCode = busMessage.getAppCode();
            status = busMessage.getStatus();
            // 2. Check whether the status is in the processing range
            OrderStatusAttention orderStatusAttention = diamondConfigProxy.orderStatusAttention();
            List<Integer> attentionStatusScope = orderStatusAttention.getScope();
            if(status ! =null && !attentionStatusScope.contains(status)) {
                log.debug(AppCode ={}, status={}", LOG_TITLE, appCode, status);
                return;
            }
            SceneEnum scene = SceneEnum.valueOf(orderStatusAttention.getMapping().get(status));
            SubmitEventContext context = SubmitEventContext.builder()
                    .appCode(appCode)
                    .scene(scene)
                    .build();
            log.info("{} appCode={}, SubmitEventContext={}", LOG_TITLE, appCode, JSON.toJSONString(context));
            //3. Record atlas record
            if(diamondConfigProxy.switchConfig().subscribeOrderStatus){
                submitEventMulticaster.execute(context);
            }
            //4. Record link logs
            syncSaveTraceRecord(appCode,message,traceLog -> {
                traceLog.setUrl("[" + HOST_ADDRESS + "]rabbitConsumer.subscribeOrderStatus");
                traceLog.setResponseBody(JSONObject.toJSONString(context));
            });
        } catch (Exception e) {
            log.error("[subscribeOrderStatus] is unusual, appCode = {}, message = {}", appCode,JSONObject.toJSONString(message),e); }}/** * Subscribe to auto dealer account submission *@param message
     */
    public void subscribeMerchantAccountSubmit(String message){
        final String LOG_TITLE = "subscribeOrderStatus#subscribeMerchantAccountSubmit|ordercenter-key-node-message";
        Long dealerId = null;
        try {
            List<CarDealerInfoDTO> carDealerList = JSON.parseArray(message,CarDealerInfoDTO.class);
            log.info("{}, params={}", LOG_TITLE, JSON.toJSONString(carDealerList));
            if(CollectionsTools.isEmpty(carDealerList)){
                log.warn("{}, params={}", LOG_TITLE, JSON.toJSONString(carDealerList));
                return;
            }
            dealerId = carDealerList.get(0).getMerchantId();
            SubmitEventContext context = SubmitEventContext.builder()
                    .merchantId(dealerId)
                    .carDealers(carDealerList)
                    .scene(SCENE_MERCHANT_ACCOUNT_SUBMIT)
                    .build();
            log.info("{} merchantId={}, SubmitEventContext={}", LOG_TITLE, dealerId, JSON.toJSONString(context));
            //1. Record atlas record
            if(diamondConfigProxy.switchConfig().subscribeMerchantAccountSubmit){
                submitEventMulticaster.execute(context);
            }
            //2. Record link logs
            syncSaveTraceRecord(Objects.toString(dealerId),message,traceLog -> {
                traceLog.setUrl("[" + HOST_ADDRESS + "]rabbitConsumer.subscribeMerchantAccountSubmit");
                traceLog.setResponseBody(JSONObject.toJSONString(context));
            });
        } catch (Exception e) {
            log.error("[subscribeMerchantAccountSubmit] is unusual, dealerId = {}, message = {}", dealerId,message,e); }}/** * Record link logs *@param appCode
     * @param message
     * @param caller
     */
    void syncSaveTraceRecord(String appCode, String message, Consumer<TraceLog> caller){
        TraceLog traceLog = TraceLog.builder()
                .appCode(appCode)
                .target(this.getClass().getPackage().getName() + "." + this.getClass().getSimpleName())
                .requestBody(message)
                .requestTime(TimeTools.createNowTime())
                .responseTime(TimeTools.createNowTime())
                .traceType(TraceTypeEnum.RABBIT_CONSUMER.getIndex()).build();
        caller.accept(traceLog);
        commonThreadPoolExecutor.execute(() -> traceLogFacade.saveRecord(traceLog));
    }


    @Data
    static class BusMessage implements Serializable {
        private Long messageId;
        private String messageCode;
        private String channel;
        private String appCode;
        // The previous state
        private Integer lastStatus;
        // Current status
        private Integer status;
        private String data;
        private Date sendTime;
        // Add the type of credit investigation in the first instance
        privateInteger creditAuthType; }}Copy the code