目录

一、前言

二、责任链简单使用

场景说明

1.前置代码准备

2.基本接口定义

3.业务节点处理代码

活动时效性检验

活动价格管控

活动风控校验

4.业务代码

5.测试与结果展示

测试代码

结果展示

三、责任链处理器使用

场景说明

1.代码前置准备

领域驱动设计产物报告提交内容

全局分析规格说明书

架构映射战略设计方案

领域模型构建产物

领域驱动设计产物报告提交内容校验结果反馈

2.业务处理定义

3.业务节点处理代码

报告完整性分析处理

全局分析规格说明书分析处理

架构映射战略设计方案分析处理

领域模型构建产物分析处理

4.业务代码

5.测试与结果展示

测试代码

测试结果展示

四、责任链变种管道模式使用

场景说明

1.定义管道处理上下文

2.定义管道上下文处理器

3.业务代码准备

定义用户文本数据结构

定义文本清洗后返回结果结构

定义敏感词校验处理结果结构

定义敏感词生效结果结构

定义相关流程处理常量

定义相关流程具体业务处理枚举

4.相关业务代码实现

数据清洗:中文繁体转换为简体

数据清洗:去除相关特殊符号

数据清洗:去除相关emoji信息

数据清洗:文本语言限制

数据清洗:排除隐藏字符

敏感词校验:企业合规管控校验

敏感词校验:正则校验处理

敏感词校验:相关词库处理

敏感词校验:手机号身份证号处理

敏感词生效:合规管控处理

敏感词生效:生效规则处理

敏感词生效:白名单处理

5.敏感词管道路由表整合配置

6.敏感词管道执行器

7.测试

8.其他备注说明


一、前言

责任链在实际开发中的应用还是比较多的,特别是在营销订购系统、审核流转换处理、任务流程处理系统等系统中,其实我们在开发中往往主要应用的主要无非是以下三个场景(起码以我的平时开发的角度来看):

  • 一是无需太关心责任链中各处理流的顺序的简单使用;
  • 二是需要关注处理顺序,按责任链条延续处理,每个处理节点均可对请求进行节点的处理, 或将其传递给链上的下个处理节点;
  • 三是在处理中和纯的责任链模式在链上只会有一个处理器用于处理业务数据存在差异,需要进行管道模式采用多个处理器都会处理业务数据。

针对我说的以上场景,本次主要列举相关的案例场景进行代码举例展示,有问题的可以留言纠正,谢谢!

二、责任链简单使用

场景说明

假设我们需要对一个线上的活动业务上做一些常规的检查,具体包括:时效性检验、价格管控、风控校验等,相关校验之间不需要特别关注先后顺序,且所有链路都需要进行处理。

1.前置代码准备

package org.zyf.javabasic.designpatterns.responsibility.base;

import lombok.Builder;
import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
 * @author yanfengzhang
 * @description 活动信息
 * @date 2022/3/30 22:38
 */
@Data
@Builder
public class ActivityDto {
    /**
     * 活动主键
     */
    private Long id;
    /**
     * 对应活动名称
     */
    private String activityName;
    /**
     * 对应商品信息
     */
    private List<Long> skuIds;
    /**
     * 对应附加商品
     */
    private Long spuId;
    /**
     * 活动类型:1.降价活动;2.限时活动;3.买赠活动;4.首购优惠活动;5.自动续费活动;6.折上优惠活动
     */
    private Integer activityType;
    /**
     * 活动开始时间  "2019-06-26 19:00:00"
     */
    private String startTime;
    /**
     * 活动结束时间  "2019-06-26 23:00:00"
     */
    private String endTime;
    /**
     * 活动价格设定
     */
    private BigDecimal activityPrice;
    /**
     * 活动城市
     */
    private String cityIds;
    /**
     * 活动状态:上线、下线
     */
    private Integer status;
    /**
     * 活动对应的配置信息
     */
    private String activityConfig;
    /**
     * 活动唯一标示
     */
    private Long activityUuid;
    /**
     * 活动创建人
     */
    private String creator;
    /**
     * 活动最新操作人
     */
    private String lastOperator;
    /**
     * 活动适用人群类型:例如会员等机制
     */
    private String userTypes;
}

2.基本接口定义

package org.zyf.javabasic.designpatterns.responsibility.base;

import org.zyf.javabasic.designpatterns.template.check.CheckResponse;

/**
 * @author yanfengzhang
 * @description 活动常规校验
 * @date 2022/3/30 22:43
 */
public interface ActivityCheck {
    /**
     * 活动常规校验
     *
     * @param activityDto 活动信息
     * @return 校验结果
     */
    CheckResponse check(ActivityDto activityDto);
}

3.业务节点处理代码

活动时效性检验

package org.zyf.javabasic.designpatterns.responsibility.base;

import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.template.check.CheckResponse;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author yanfengzhang
 * @description 时效性检验
 * @date 2022/3/30 22:45
 */
@Component
public class ActDurationCheck implements ActivityCheck {

    /**
     * 活动时效性检查
     *
     * @param activityDto 活动信息
     * @return 活动时效性检查结果 true-满足条件
     */
    @Override
    public CheckResponse check(ActivityDto activityDto) {
        /*检查时效性1:如果当前活动为上线状态(1),则该活动时间必须在指定的时间范围内*/
        if (activityDto.getStatus() != 1) {
            return CheckResponse.builder().pass(true).build();
        }

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime curTime = LocalDateTime.now();
        LocalDateTime startTime = LocalDateTime.parse(activityDto.getStartTime(), dtf);
        LocalDateTime endTime = LocalDateTime.parse(activityDto.getEndTime(), dtf);
        if (startTime.isBefore(curTime) && endTime.isAfter(curTime)) {
            return CheckResponse.builder().pass(true).build();
        }

        return CheckResponse.builder().pass(false).errorMsg("[当前线上活动已失效,请检查修正状态!]").build();
    }
}

活动价格管控

package org.zyf.javabasic.designpatterns.responsibility.base;

import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.template.check.CheckResponse;

import java.math.BigDecimal;

/**
 * @author yanfengzhang
 * @description 价格管控
 * @date 2022/3/30 22:51
 */
@Component
public class ActPriceCheck implements ActivityCheck {
    /**
     * 活动价格检查
     * 1.降价活动;2.限时活动;3.买赠活动;4.首购优惠活动;5.自动续费活动;6.折上优惠活动
     *
     * @param activityDto 活动信息
     * @return 活动时效性检查结果 true-满足条件
     */
    @Override
    public CheckResponse check(ActivityDto activityDto) {
        Integer activityType = activityDto.getActivityType();
        BigDecimal activityPrice = activityDto.getActivityPrice();
        if (activityType.equals(1) && !(activityPrice.compareTo(BigDecimal.valueOf(10)) > -1 && activityPrice.compareTo(BigDecimal.valueOf(100)) < 1)) {
            return CheckResponse.builder().pass(false).errorMsg("[降价活动价格必须在10-100之间,请修正价格信息!]").build();
        }
        if (activityType.equals(2) && !(activityPrice.compareTo(BigDecimal.valueOf(10)) > -1 && activityPrice.compareTo(BigDecimal.valueOf(60)) < 1)) {
            return CheckResponse.builder().pass(false).errorMsg("[限时活动价格必须在10-60之间,请修正价格信息!]").build();
        }

        return CheckResponse.builder().pass(true).build();
    }
}

活动风控校验

package org.zyf.javabasic.designpatterns.responsibility.base;

import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.template.check.CheckResponse;

/**
 * @author yanfengzhang
 * @description 风控校验
 * @date 2022/3/30 22:55
 */
@Component
public class ActRiskCheck implements ActivityCheck {
    /**
     * 活动风控检查
     *
     * @param activityDto 活动信息
     * @return 活动时效性检查结果 true-满足条件
     */
    @Override
    public CheckResponse check(ActivityDto activityDto) {
        /*风控校验1:如果当前活动非上线状态(1),则不进行风控管理*/
        if (activityDto.getStatus() != 1) {
            return CheckResponse.builder().pass(true).build();
        }
        /*风控校验2:相关描述不得包含敏感词(以下用于模拟)*/
        String sensitive = "有敏感词";
        if (activityDto.getActivityName().contains(sensitive) || activityDto.getActivityConfig().contains(sensitive)) {
            return CheckResponse.builder().pass(false).errorMsg("[活动设置中存在敏感词,请修正相关配置内容!]").build();
        }

        return CheckResponse.builder().pass(true).build();
    }
}

4.业务代码

package org.zyf.javabasic.designpatterns.responsibility.base;

import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 活动对应业务处理
 * @date 2022/3/30 23:01
 */
@Service
public class ActivityBizService {

    @Autowired
    private List<ActivityCheck> activityCheckList;

    /**
     * 活动常规校验结果
     *
     * @param activityDto 活动信息
     * @return z
     */
    public List<String> check(ActivityDto activityDto) {
        List<String> checkResults = Lists.newArrayList();
        for (ActivityCheck activityCheck : activityCheckList) {
            if (!activityCheck.check(activityDto).isPass()) {
                checkResults.add(activityCheck.check(activityDto).getErrorMsg());
            }
        }
        return checkResults;
    }
}

5.测试与结果展示

测试代码

package org.zyf.javabasic.designpatterns.responsibility.base;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;

import java.math.BigDecimal;
import java.util.List;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/3/30 23:05
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ActivityBizServiceTest {

    @Autowired
    private ActivityBizService activityBizService;

    @Test
    public void testActivityBizService() {
        ActivityDto activityDto = ActivityDto.builder()
                .activityName("张彦峰活动有敏感词")
                .activityType(1)
                .status(1)
                .activityConfig("张彦峰活动配置中有敏感词")
                .activityPrice(BigDecimal.valueOf(120))
                .startTime("2022-03-30 12:00:00")
                .endTime("2022-03-30 16:00:00").build();
        log.info("=======对活动常规性校验处理结果开始=======");
        List<String> checkRes = activityBizService.check(activityDto);
        if (CollectionUtils.isEmpty(checkRes)) {
            log.info("校验处理结果完成:该活动符合要求!");
            return;
        }
        log.info("校验处理结果完成,该活动不符合要求的主要原因如下:");
        checkRes.forEach(res -> {
            log.info("{}", res);
        });
    }
}

结果展示

2022-04-04 17:35:22,272 [main] INFO  o.z.j.d.r.b.ActivityBizServiceTest [ActivityBizServiceTest.java : 38] - =======对活动常规性校验处理结果开始=======
2022-04-04 17:35:22,283 [main] INFO  o.z.j.d.r.b.ActivityBizServiceTest [ActivityBizServiceTest.java : 44] - 校验处理结果完成,该活动不符合要求的主要原因如下:
2022-04-04 17:35:22,283 [main] INFO  o.z.j.d.r.b.ActivityBizServiceTest [ActivityBizServiceTest.java : 46] - [当前线上活动已失效,请检查修正状态!]
2022-04-04 17:35:22,283 [main] INFO  o.z.j.d.r.b.ActivityBizServiceTest [ActivityBizServiceTest.java : 46] - [降价活动价格必须在10-100之间,请修正价格信息!]
2022-04-04 17:35:22,283 [main] INFO  o.z.j.d.r.b.ActivityBizServiceTest [ActivityBizServiceTest.java : 46] - [活动设置中存在敏感词,请修正相关配置内容!]

三、责任链处理器使用

场景说明

假设我们要求研发整体按DDD领域驱动设计方式设计新提交的系统方案,那么在进行DDD领域驱动设计过程中需要按要求指定提交领域驱动设计产物报告,其覆盖了我们对应DDD的基本要求,对应方案写完后需先进行提交,系统先自动检查相关资料是否符合规范,符合后才进行通知相关人员进行评审,对于领域驱动设计产物报告的评估按需求需要进行以下评估:

  • 报告完整性分析处理:在全局分析阶段、架构映射阶段、领域建模阶段都需要按指定要求输出相关的产物报告内容,如有缺失则中断后续处理并告知,不进行评审组织安排;
  • 全局分析规格说明书分析处理:全局分析阶段需要产出对应的全局分析规格说明书,系统需要对该说明书先进行相关的评定,如不符合规范则中断后续处理并告知理由,不进行评审组织安排;
  • 架构映射战略设计方案分析处理:在架构映射阶段需要给出架构映射战略设计方案,系统需要对该方案先进行相关的评定,如不符合规范则中断后续处理并告知理由,不进行评审组织安排;
  • 领域模型构建产物分析处理:在领域建模阶段输出领域分析模型、领域设计模型、领域实现模型等,系统需要对相关模型先进行相关的评定,如不符合规范则中断后续处理并告知理由,不进行评审组织安排;

1.代码前置准备

前置性的准备话,我们先写一些以上相关需要的基本数据内容,具体可能包含以下内容:

领域驱动设计产物报告提交内容

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.Builder;
import lombok.Data;

/**
 * @author yanfengzhang
 * @description 领域驱动设计产物报告提交内容
 * @date 2022/4/1  22:56
 */
@Data
@Builder
public class DDDProductReport {
    /**
     * 全局分析规格说明书
     */
    private GlobalAnalysisSpec globalAnalysisSpec;
    /**
     * 架构映射战略设计方案
     */
    private ArchitectureMappingScheme architectureMappingScheme;
    /**
     * 领域模型构建产物
     */
    private DomainModelBuildProduct domainModelBuildProduct;
}

全局分析规格说明书

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.Builder;
import lombok.Data;

/**
 * @author yanfengzhang
 * @description 全局分析规格说明书
 * @date 2022/4/1  23:01
 */
@Data
@Builder
public class GlobalAnalysisSpec {
    /**
     * 价值需求
     * 分析:利益相关者 + 系统愿景 + 系统范围(当前状态、未来状态、业务目标)
     */
    private String valueNeeds;
    /**
     * 业务需求
     * 业务描述与子领域规划分析(核心域、支撑域、通用域)
     */
    private String businessNeeds;
    /**
     * 业务流程
     * 核心业务流程 + 主要业务流程
     */
    private String businessProcess;
    /**
     * 子领域分析
     * 子领域==业务场景+业务服务(编号、名称、描述、触发事件、基本流程、替代流程、验收标准)
     */
    private String subFieldAnalysis;
}

架构映射战略设计方案

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.Builder;
import lombok.Data;

/**
 * @author yanfengzhang
 * @description 架构映射战略设计方案
 * @date 2022/4/1  23:07
 */
@Data
@Builder
public class ArchitectureMappingScheme {
    /**
     * 系统上下文
     */
    private String systemContext;
    /**
     * 业务架构 : 业务组件 + 业务架构视图
     */
    private String businessrchitecture;
    /**
     * 应用架构 : 业务组件 + 应用架构视图
     */
    private String applicationArchitecture;
    /**
     * 子领域架构 : 核心域、支撑域、通用域
     */
    private String subFieldAnalysis;
}

领域模型构建产物

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.Builder;
import lombok.Data;

/**
 * @author yanfengzhang
 * @description 领域模型构建产物
 * @date 2022/4/1  23:11
 */
@Data
@Builder
public class DomainModelBuildProduct {
    /**
     * 领域分析模型:业务服务约束 + 领域模型概念图
     */
    private String domainAnalysisModel;
    /**
     * 领域设计模型:以聚合为核心的静态设计类图 + 由角色构造型组成的动态序列图 + 序列图脚本
     */
    private String domainDesignModel;
    /**
     * 领域实现模型:实现业务功能的产品代码(示例) + 验证业务功能的测试代码(示例)
     */
    private String domainImplementationModel;
}

领域驱动设计产物报告提交内容校验结果反馈

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.Builder;
import lombok.Data;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 领域驱动设计产物报告提交内容校验结果反馈
 * @date 2022/4/1  23:15
 */
@Data
@Builder
public class DDDReportValidateRes {
    /**
     * 报告是否符合产出 true-符合要求;false-不符合规范
     */
    private boolean legal;
    /**
     * 如果报告不符合要求,给出详细原因
     */
    private List<String> detailReasons;
}

2.业务处理定义

领域驱动设计报告自动检验处理,其实就是规定责任链中各节点上主要实现的基本接口,该处我们主要给出每个节点需要处理的基本方法validateDDDHandler,同时定义建造者处理进行责任链连接,具体代码如下:

package org.zyf.javabasic.designpatterns.responsibility.chain;

/**
 * @author yanfengzhang
 * @description 领域驱动设计报告自动检验处理
 * @date 2022/4/1  23:19
 */
public abstract class DDDAnalysisHandler {

    protected DDDAnalysisHandler next = null;

    /**
     * 校验DDD过程中输出产物是否符合要求
     *
     * @param dddProductReport     领域驱动设计产物报告提交内容
     * @param dddReportValidateRes 领域驱动设计产物报告提交内容校验结果反馈
     */
    public abstract void validateDDDHandler(DDDProductReport dddProductReport, DDDReportValidateRes dddReportValidateRes);

    /**
     * 建造者处理进行责任链连接
     */
    public static class Builder {
        private DDDAnalysisHandler header = null;
        private DDDAnalysisHandler tail = null;

        public Builder add(DDDAnalysisHandler dddAnalysisHandler) {
            if (this.header == null) {
                this.header = this.tail = dddAnalysisHandler;
            } else {
                tail.next = dddAnalysisHandler;
                tail = dddAnalysisHandler;
            }
            return this;
        }

        public DDDAnalysisHandler build() {
            return this.header;
        }
    }
}

3.业务节点处理代码

报告完整性分析处理

package org.zyf.javabasic.designpatterns.responsibility.chain;

import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;

import java.util.List;
import java.util.Objects;

/**
 * @author yanfengzhang
 * @description 报告完整性分析处理
 * @date 2022/4/2  23:05
 */
public class ReportIntegrityValidate extends DDDAnalysisHandler {

    /**
     * 报告完整性分析
     *
     * @param dddProductReport 领域驱动设计产物报告提交内容
     * @return 将对应分析输出到 领域驱动设计产物报告提交内容校验结果反馈 中
     */
    @Override
    public void validateDDDHandler(DDDProductReport dddProductReport, DDDReportValidateRes dddReportValidateRes) {
        List<String> detailReasons = Lists.newArrayList();
        if (Objects.isNull(dddProductReport)) {
            detailReasons.add("在进行组织评审前请按要求提交DDD领域驱动设计产物报告!");
            dddReportValidateRes.setLegal(false);
            dddReportValidateRes.setDetailReasons(detailReasons);
        } else {
            /*1.校验报告相关完整性分析*/
            validateIntegrity(dddProductReport.getGlobalAnalysisSpec(), "DDD领域驱动设计产物报告中全局分析规格说明书未补充!", detailReasons);
            validateIntegrity(dddProductReport.getArchitectureMappingScheme(), "DDD领域驱动设计产物报告中架构映射战略设计方案未补充!", detailReasons);
            validateIntegrity(dddProductReport.getDomainModelBuildProduct(), "DDD领域驱动设计产物报告中领域模型构建产物未补充!", detailReasons);

            /*2.输出校验结果*/
            if (CollectionUtils.isNotEmpty(detailReasons)) {
                dddReportValidateRes.setLegal(false);
                dddReportValidateRes.setDetailReasons(detailReasons);
            }
        }


        if (next != null) {
            next.validateDDDHandler(dddProductReport, dddReportValidateRes);
        }
    }

    /**
     * 校验报告相关完整性分析
     *
     * @param dddProduct   DDD产物报告细项
     * @param detailReason 分析原因
     */
    private void validateIntegrity(Object dddProduct, String detailReason, List<String> detailReasons) {
        if (Objects.isNull(dddProduct)) {
            detailReasons.add(detailReason);
        }
    }
}

全局分析规格说明书分析处理

package org.zyf.javabasic.designpatterns.responsibility.chain;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 全局分析规格说明书分析处理
 * @date 2022/4/2  23:13
 */
public class GlobalAnalysisSpecValidate extends DDDAnalysisHandler {
    /**
     * 全局分析规格说明书分析
     *
     * @param dddProductReport 领域驱动设计产物报告提交内容
     * @return 将对应分析输出到 领域驱动设计产物报告提交内容校验结果反馈 中
     */
    @Override
    public void validateDDDHandler(DDDProductReport dddProductReport, DDDReportValidateRes dddReportValidateRes) {
        /*1.如果上个节点的处理结果未通过则本流程不进行处理*/
        if (!dddReportValidateRes.isLegal()) {
            return;
        }

        /*2.校验报告相关完整性分析*/
        GlobalAnalysisSpec globalAnalysisSpec = dddProductReport.getGlobalAnalysisSpec();
        validateGlobalSpec(globalAnalysisSpec.getValueNeeds(), "全局分析规格说明书:价值需求分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateGlobalSpec(globalAnalysisSpec.getBusinessNeeds(), "全局分析规格说明书:业务需求分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateGlobalSpec(globalAnalysisSpec.getBusinessProcess(), "全局分析规格说明书:业务流程涵盖内容存在问题,请进行流程梳理!", dddReportValidateRes.getDetailReasons());
        validateGlobalSpec(globalAnalysisSpec.getSubFieldAnalysis(), "全局分析规格说明书:子领域分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        
        /*3.输出校验结果*/
        if (CollectionUtils.isNotEmpty(dddReportValidateRes.getDetailReasons())) {
            dddReportValidateRes.setLegal(false);
        }
        if (next != null) {
            next.validateDDDHandler(dddProductReport, dddReportValidateRes);
        }
    }

    /**
     * 校验全局分析规格说明书分析
     *
     * @param analysisSpec 全局分析规格说明书内容
     * @param detailReason 分析原因
     */
    private void validateGlobalSpec(String analysisSpec, String detailReason, List<String> detailReasons) {
        if (StringUtils.isBlank(analysisSpec)) {
            detailReasons.add(detailReason);
        }
    }
}

架构映射战略设计方案分析处理

package org.zyf.javabasic.designpatterns.responsibility.chain;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 架构映射战略设计方案分析处理
 * @date 2022/4/2  23:21
 */
public class ArchitectureMappingValidate extends DDDAnalysisHandler {
    /**
     * 架构映射战略设计方案分析
     *
     * @param dddProductReport 领域驱动设计产物报告提交内容
     * @return 将对应分析输出到 领域驱动设计产物报告提交内容校验结果反馈 中
     */
    @Override
    public void validateDDDHandler(DDDProductReport dddProductReport, DDDReportValidateRes dddReportValidateRes) {
        /*1.如果上个节点的处理结果未通过则本流程不进行处理*/
        if (!dddReportValidateRes.isLegal()) {
            if (next != null) {
                next.validateDDDHandler(null, dddReportValidateRes);
            }
            return;
        }

        /*2.校验报告相关完整性分析*/
        ArchitectureMappingScheme architectureMappingScheme = dddProductReport.getArchitectureMappingScheme();
        validateArchitectureMapping(architectureMappingScheme.getSystemContext(), "架构映射战略设计方案:系统上下文分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateArchitectureMapping(architectureMappingScheme.getBusinessrchitecture(), "架构映射战略设计方案:业务架构分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateArchitectureMapping(architectureMappingScheme.getApplicationArchitecture(), "架构映射战略设计方案:应用架构分析不符合要求,请按规定进行填写!!", dddReportValidateRes.getDetailReasons());
        validateArchitectureMapping(architectureMappingScheme.getSubFieldAnalysis(), "架构映射战略设计方案:子领域架构分析不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        
        /*3.输出校验结果*/
        if (CollectionUtils.isNotEmpty(dddReportValidateRes.getDetailReasons())) {
            dddReportValidateRes.setLegal(false);
        }

        if (next != null) {
            next.validateDDDHandler(dddProductReport, dddReportValidateRes);
        }
    }

    /**
     * 校验架构映射
     *
     * @param analysisSpec 架构映射内容
     * @param detailReason 分析原因
     */
    private void validateArchitectureMapping(String analysisSpec, String detailReason, List<String> detailReasons) {
        if (StringUtils.isBlank(analysisSpec)) {
            detailReasons.add(detailReason);
        }
    }
}

领域模型构建产物分析处理

package org.zyf.javabasic.designpatterns.responsibility.chain;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 领域模型构建产物分析处理
 * @date 2022/4/2  23:32
 */
public class DomainModelBuildValidate extends DDDAnalysisHandler {
    /**
     * 领域模型构建产物分析
     *
     * @param dddProductReport 领域驱动设计产物报告提交内容
     * @return 将对应分析输出到 领域驱动设计产物报告提交内容校验结果反馈 中
     */
    @Override
    public void validateDDDHandler(DDDProductReport dddProductReport, DDDReportValidateRes dddReportValidateRes) {
        /*1.如果上个节点的处理结果未通过则本流程不进行处理*/
        if (!dddReportValidateRes.isLegal()) {
            if (next != null) {
                next.validateDDDHandler(null, dddReportValidateRes);
            }
            return;
        }

        /*2.校验报告相关完整性分析*/
        DomainModelBuildProduct domainModelBuildProduct = dddProductReport.getDomainModelBuildProduct();
        validateDomainModelBuild(domainModelBuildProduct.getDomainAnalysisModel(), "领域模型构建产物分析:领域分析模型不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateDomainModelBuild(domainModelBuildProduct.getDomainDesignModel(), "领域模型构建产物分析:领域设计模型不符合要求,请按规定进行填写!", dddReportValidateRes.getDetailReasons());
        validateDomainModelBuild(domainModelBuildProduct.getDomainImplementationModel(), "领域模型构建产物分析:领域实现模型不符合要求,请按规定进行填写!!", dddReportValidateRes.getDetailReasons());

        /*3.输出校验结果*/
        if (CollectionUtils.isNotEmpty(dddReportValidateRes.getDetailReasons())) {
            dddReportValidateRes.setLegal(false);
        }

        if (next != null) {
            next.validateDDDHandler(dddProductReport, dddReportValidateRes);
        }
    }

    /**
     * 校验领域模型
     *
     * @param model        领域模型
     * @param detailReason 分析原因
     */
    private void validateDomainModelBuild(String model, String detailReason, List<String> detailReasons) {
        if (StringUtils.isBlank(model)) {
            detailReasons.add(detailReason);
        }
    }
}

4.业务代码

实际业务代码中需要去规定相关处理的先后次序,并进行相关的流程处理,本处只给出样例,具体如下:

package org.zyf.javabasic.designpatterns.responsibility.chain;

import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;

/**
 * @author yanfengzhang
 * @description 领域驱动设计报告交互服务
 * @date 2022/4/2  23:43
 */
@Service
public class DDDBizService {

    /**
     * 组织评审
     */
    public void organizationalReview(DDDProductReport dddProductReport) {
        /*1.构建链路形成实际检验链路*/
        DDDAnalysisHandler.Builder builder = new DDDAnalysisHandler.Builder();
        builder.add(new ReportIntegrityValidate()).add(new GlobalAnalysisSpecValidate())
                .add(new ArchitectureMappingValidate()).add(new DomainModelBuildValidate());
        
        System.out.println("===准备进行组织评审,对应领域驱动设计产物报告已提交,正在进行机审处理分析!===");
        /*2.先进行基本报告的分析,机审通过后在进行相关人员通知*/
        DDDReportValidateRes dddReportValidateRes = DDDReportValidateRes.builder().legal(true).detailReasons(Lists.newArrayList()).build();
        builder.build().validateDDDHandler(dddProductReport, dddReportValidateRes);
        if (!dddReportValidateRes.isLegal()) {
            System.out.println("===领域驱动设计产物报告已提交,但机审未通过,具体原因如下:===");
            dddReportValidateRes.getDetailReasons().forEach(System.out::println);
            System.out.println("===组织评审发起失败!请按机审结果进行修正相关内容重新发起!===");
            return;
        }

        /*3.通知相关人员评审*/
        System.out.println("===机审处理分析已通过,正在通知相关人员进行确认评审时间===");
        System.out.println("===相关人员进行确认评审时间已确定,已发送具体评审时间与地点!===");
    }
}

5.测试与结果展示

测试代码

package org.zyf.javabasic.designpatterns.responsibility.chain;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/4/2  23:52
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DDDBizServiceTest {
    @Autowired
    private DDDBizService dddBizService;

    @Test
    public void testDDDBizService() {
        DDDProductReport dddProductReport = DDDProductReport.builder()
                .globalAnalysisSpec(GlobalAnalysisSpec.builder().valueNeeds("利益相关者 + 系统愿景 + 系统范围(当前状态、未来状态、业务目标)")
                        .businessNeeds("业务描述与子领域规划分析(核心域、支撑域、通用域)")
                        .businessProcess("核心业务流程 + 主要业务流程")
                        .subFieldAnalysis("业务场景+业务服务(编号、名称、描述、触发事件、基本流程、替代流程、验收标准)").build())
                .architectureMappingScheme(ArchitectureMappingScheme.builder()
                        .systemContext("系统上下文").applicationArchitecture("业务组件 + 应用架构视图").businessrchitecture("业务组件 + 业务架构视图")
                        .subFieldAnalysis("核心域、支撑域、通用域").build())
                .domainModelBuildProduct(DomainModelBuildProduct.builder().build()).build();

        dddBizService.organizationalReview(dddProductReport);
    }
}

测试结果展示

===准备进行组织评审,对应领域驱动设计产物报告已提交,正在进行机审处理分析!===
===领域驱动设计产物报告已提交,但机审未通过,具体原因如下:===
领域模型构建产物分析:领域分析模型不符合要求,请按规定进行填写!
领域模型构建产物分析:领域设计模型不符合要求,请按规定进行填写!
领域模型构建产物分析:领域实现模型不符合要求,请按规定进行填写!!
===组织评审发起失败!请按机审结果进行修正相关内容重新发起!===

四、责任链变种管道模式使用

场景说明

假设我们有一个敏感词处理系统,我们需要对用户提交的相关内容进行敏感词校验,具体流程如下:

责任链模式javademo 责任链模式应用_责任链模式javademo

 业务变动上后期还会在基本处理上加入或删除一些流程,或调整相关的顺序,具体可能如下:

责任链模式javademo 责任链模式应用_责任链模式_02

 可以看到以上的场景是一个多链条的处理,不在像前面的单链条模式,在处理以上结构时,我们的策略往往都是责任链的变种处理:管道模式处理。管道模式的处理大致上就正好如同以上的处理流程图一样,开始写代码吧!

1.定义管道处理上下文

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

/**
 * @author yanfengzhang
 * @description 管道的上下文基本结构
 * @date 2022/4/2  20:43
 */

@Getter
@Setter
public class PipelineContext {
    /**
     * 模板名称
     */
    private String name;
    /**
     * 处理开始时间
     */
    private LocalDateTime startTime;
    /**
     * 处理结束时间
     */
    private LocalDateTime endTime;

    /**
     * 获取数据名称
     */
    public String getName() {
        return this.getClass().getSimpleName();
    }
}

2.定义管道上下文处理器

package org.zyf.javabasic.designpatterns.responsibility.pipeline;

import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.PipelineContext;

/**
 * @author yanfengzhang
 * @description 管道中的上下文处理器
 * T:管道中的上下文 R:管道中的上下文处理结果
 * @date 2022/4/2  20:48
 */

public interface ContextHandler<T extends PipelineContext, R> {
    /**
     * @param context 处理时的上下文数据
     * @return dealRes R 处理结果
     */
    R handle(T context);
}

3.业务代码准备

定义用户文本数据结构

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model;

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author yanfengzhang
 * @description 用户输入的文本信息上下文
 * @date 2022/4/2  21:05
 */
@EqualsAndHashCode(callSuper = true)
@Builder
@Data
public class ContentInfoContext extends PipelineContext {
    /**
     * 用户输入文本原稿(清洗中会不断被更新内容)
     */
    private String content;
    /**
     * 经过清洗后的文本(任意清洗中都进行变更)
     */
    private String cleanContent;
    /**
     * 用户文本属性
     */
    private ContentAttr contentAttr;

    @Override
    public String getName() {
        return "数据清洗(用户文本)构建上下文";
    }
}

定义文本清洗后返回结果结构

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model;

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author yanfengzhang
 * @description 用户文本清洗结果文本信息上下文
 * @date 2022/12/14  17:18
 */
@EqualsAndHashCode(callSuper = true)
@Builder
@Data
public class ContentCleanResContext extends PipelineContext {
    /**
     * 清洗完成 true-清洗完成
     * 清洗阶段数据节点流转信号
     */
    private boolean isCleanDone;
    /**
     * 用户输入文本原稿(原始文本不变)
     */
    private String content;
    /**
     * 用户清洗后的文本内容
     */
    private String cleanContent;
    /**
     * 用户文本属性
     */
    private ContentAttr contentAttr;
    /**
     * 数据节点流转终止原因,只有isCleanDone为false生效
     */
    private String reason;

    @Override
    public String getName() {
        return "数据清洗结果构建上下文";
    }
}

定义敏感词校验处理结果结构

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model;

import lombok.Builder;
import lombok.Data;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 敏感词命中情况基本信息
 * @date 2022/4/4  22:56
 */
@Builder
@Data
public class SensitveHitContext extends PipelineContext {
    /**
     * 是否存在敏感词命中
     */
    private Boolean hasHit;
    /**
     * 用户输入文本原稿
     */
    private String content;
    /**
     * 用户清洗后的文本内容(清洗内容后的结果)
     */
    private String cleanContent;
    /**
     * 用户文本属性
     */
    private ContentAttr contentAttr;
    /**
     * 命中的敏感词
     */
    private List<SensitiveWord> hitWords;

    @Override
    public String getName() {
        return "模型实例(敏感词命中)构建上下文";
    }
}

定义敏感词生效结果结构

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model;

import lombok.Builder;
import lombok.Data;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 按配置条件实际生效的敏感词
 * @date 2022/4/4  22:18
 */
@Builder
@Data
public class SensitveEffectiveContext extends PipelineContext {
    /**
     * 是否命中敏感词,最终生效的
     */
    private Boolean isHit;
    /**
     * 用户输入文本原稿
     */
    private String content;
    /**
     * 用户清洗后的文本内容(清洗内容后的结果)
     */
    private String cleanContent;
    /**
     * 命中的敏感词
     */
    private List<SensitiveWord> hitWords;
    /**
     * 命中的敏感词被加白
     */
    private List<SensitiveWord> whitedWords;
    /**
     * 命中的敏感词被合规放行
     */
    private List<SensitiveWord> complianceIgnoreWords;
    /**
     * 命中的敏感词中被对应规则放行
     */
    private List<SensitiveWord> ruleIgnoreWords;

    @Override
    public String getName() {
        return "模型实例(敏感词生效)构建上下文";
    }
}

定义相关流程处理常量

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/23  18:18
 */
public class SensitiveCons {

    /**
     * 数据清洗
     */
    public static class Clean {
        /**
         * 中文繁体转换为简体
         */
        public static final int TRADITIONAL_TO_SIMPLE = 1001;
        /**
         * 去除相关特殊符号
         */
        public static final int REMOVE_SPECIAL_SYMBOLS = 1002;
        /**
         * 去除相关emoji信息
         */
        public static final int REMOVE_EMOJI = 1003;
        /**
         * 文本语言限制
         */
        public static final int LANGUAGE_LIMIT = 1004;
        /**
         * 排除隐藏字符
         */
        public static final int EXCULDE_HIDDEN = 1005;
    }

    /**
     * 敏感词校验
     */
    public static class Validate {
        /**
         * 企业合规管控校验
         */
        public static final int COMPLIANCE = 2001;
        /**
         * 敏感词分析处理:根据相关业务配置进行相关词库校验匹配
         */
        public static final int THESAURUS = 2002;
        /**
         * 正则校验处理
         */
        public static final int REGULAR = 2003;
        /**
         * 手机号身份证号处理
         */
        public static final int PRIVACY = 2004;
    }

    /**
     * 敏感词校验
     */
    public static class Effect {
        /**
         * 合规管控处理
         */
        public static final int COMPLIANCE_CONTROL = 3001;
        /**
         * 生效规则处理
         */
        public static final int RULE = 3002;
        /**
         * 白名单处理
         */
        public static final int WHITE = 3003;
    }
}

定义相关流程具体业务处理枚举

数据清洗

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/23  18:05
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SensitiveClean {
    /**
     * 清洗编码
     */
    int cleanCode();
}

敏感词校验

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/23  18:06
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SensitiveValidate {
    /**
     * 生效编码
     */
    int validateCode();
}

敏感词生效

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/23  18:06
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SensitiveEffect {
    /**
     * 生效编码
     */
    int effectCode();
}

4.相关业务代码实现

数据清洗相关业务代码

相关能力说明:将用户文本进行一定的清洗,每次清洗结果将保留到下一次清洗当中继续执行下一步的清洗工作,链条外部整体把控是否继续责任链的传递。

数据清洗:中文繁体转换为简体

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean;

import com.github.houbb.opencc4j.util.ZhConverterUtil;
import com.github.houbb.opencc4j.util.ZhTwConverterUtil;
import com.luhuiguo.chinese.ChineseUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;


/**
 * @author yanfengzhang
 * @description 数据清洗:中文繁体转换为简体
 * @date 2022/4/5  22:32
 */
@Component
@SensitiveClean(cleanCode = SensitiveCons.Clean.TRADITIONAL_TO_SIMPLE)
public class TraditionalSimplifiedConversion implements ContextHandler<ContentInfoContext, ContentCleanResContext> {

    /**
     * 对用户内容进行处理:中文繁体转换为简体
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词词库校验)
     */
    @Override
    public ContentCleanResContext handle(ContentInfoContext context) {
        try {
            /*其他链路中清洗后的词*/
            String traditionalChinese = context.getCleanContent();
            /*中国大陆普通词库进行第一次转换*/
            String commonConverter = ChineseUtils.toSimplified(traditionalChinese);
            /*中国港澳词库进行第二次转换*/
            String macauAndHKConverter = ZhConverterUtil.toSimple(commonConverter);
            /*中国台湾词库进行第三次转换*/
            String finalContent = ZhTwConverterUtil.toSimple(macauAndHKConverter);

            /*将本次清洗数据载入待继续清洗实体中*/
            context.setCleanContent(finalContent);
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(true)
                    .content(context.getContent())
                    .cleanContent(finalContent)
                    .contentAttr(context.getContentAttr())
                    .build();
        } catch (Exception e) {
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(false)
                    .content(context.getContent())
                    /*记录下中间态数据*/
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .reason("数据清洗异常:中文繁体转换失败")
                    .build();
        }
    }
}

数据清洗:去除相关特殊符号

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.base.BCConvert;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author yanfengzhang
 * @description 数据清洗:去除相关特殊符号
 * @date 2022/4/5  14:25
 */
@Component
@SensitiveClean(cleanCode = SensitiveCons.Clean.REMOVE_SPECIAL_SYMBOLS)
public class RemoveSpecialSymbols implements ContextHandler<ContentInfoContext, ContentCleanResContext> {

    private LoadingCache<String, Set<Integer>> specialSymbolsCache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            /*构建缓存*/
            .build(new CacheLoader<String, Set<Integer>>() {
                /*初始化加载数据的缓存信息*/
                @Override
                public Set<Integer> load(String specialSymbols) throws Exception {
                    return getSpecialSymbols();
                }
            });

    /**
     * 对用户内容进行处理:将用户输入中含有的特殊符号进行排除
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词词库校验)
     */
    @Override
    public ContentCleanResContext handle(ContentInfoContext context) {
        try {
            String SPECIAL_SYMBOLS = "specialSymbols";
            Set<Integer> specialSymbols = specialSymbolsCache.get(SPECIAL_SYMBOLS);
            StringBuilder cleanContent = new StringBuilder();
            /*其他链路中清洗后的词*/
            char[] valueChars = context.getCleanContent().toCharArray();
            for (char valueChar : valueChars) {
                int temp = BCConvert.charConvert(valueChar);
                if (specialSymbols.contains(temp)) {
                    /*过滤特殊字符*/
                    continue;
                }
                cleanContent.append(valueChar);
            }

            /*将本次清洗数据载入待继续清洗实体中*/
            context.setCleanContent(cleanContent.toString());
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(true)
                    .content(context.getContent())
                    .cleanContent(cleanContent.toString())
                    .contentAttr(context.getContentAttr())
                    .build();
        } catch (Exception e) {
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(false)
                    .content(context.getContent())
                    /*记录下中间态数据*/
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .reason("数据清洗异常:排除特殊符号失败")
                    .build();
        }
    }

    /**
     * 本处举例,实际应该放置到指定的配置页面来实时生效
     *
     * @return 相关特殊符号集合
     */
    private static Set<Integer> getSpecialSymbols() {
        List<String> specialSymbolsRes = Lists.newArrayList();
        String speciSymbols = "'͏@¥^…&()()、。 ;:|【】[]{}-—_%*$#!/\\<>《》,,.:“”\"』『•‘’'??+=!!" +
                "°❤❥웃유☮☏☢☠✔☑♚▲♪✈✞÷↑↓◆◇⊙■□△▽¿─│♥❣♂♀☿✉☣☤✘☒♛▼♫⌘☪≈←→◈◎☉★☆⊿※¡━┃♡ღツ☼☁❅✎©®™Σ✪✯☭➳卐√↖↗●◐Θ◤◥︻" +
                "〖〗┄┆℃℉°✿ϟ☃☂✄¢€£∞✫★½✡×↙↘○◑⊕◣◢︼【】┅┇☽☾✚〓▂▃▄▅▆▇█▉▊▋▌▍▎▏↔↕☽☾の•▸◂▴▾┈┊①②③④⑤⑥⑦⑧⑨⑩" +
                "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍿▓♨♛❖☪✙┉┋☹☺☻تヅツッシÜϡﭢ™℠℗©®♥❤❥❣❦❧♡۵웃유ღ♂♀☿☼☀☁☂☄☾☽❄☃☈⊙☉℃℉❅✺ϟ☇♤♧♡♢♠♣♥♦☜☞☚☛☟✽✾✿❁❃❋❀⚘☑✓✔" +
                "√☐☒✗✘ㄨ✕✖✖⋆✢✣✤✥❋✦✧✩✰✪✫✬✭✮✯❂✡★✱✲✳✴✵✶✷✸✹✺✻✼❄❅❆❇❈❉❊†☨✞✝☥☦☓☩☯☧☬☸✡♁✙♆。,、':∶;?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;!" +
                "´?!~—ˉ|‖"〃`@﹫¡¿﹏﹋﹌︴々﹟#﹩$﹠&﹪%*﹡﹢﹦﹤‐ ̄¯―﹨ˆ˜﹍﹎+=<__-\\ˇ~﹉﹊()〈〉‹›﹛﹜『』〖〗[]《》〔〕" +
                "{}「」【】︵︷︿︹︽_﹁﹃︻︶︸﹀︺︾ˉ﹂﹄︼☩☨☦✞✛✜✝✙✠✚†‡◉○◌◍◎●◐◑◒◓◔◕◖◗❂☢⊗⊙◘◙◍⅟½⅓⅕⅙⅛⅔⅖⅚⅜¾⅗⅝⅞⅘≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓" +
                "≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩⊰⊱⋛⋚∫∬∭∮∯∰∱∲∳%℅‰‱㉿囍♔♕♖♗♘♙♚♛♜♝♞♟ℂℍℕℙℚℝℤℬℰℯℱℊℋℎℐℒℓℳℴ℘ℛℭ℮ℌℑℜℨ♪♫♩♬♭♮♯°ø☮☪✡☭✯卐✐" +
                "✎✏✑✒✉✁✂✃✄✆✉☎☏➟➡➢➣➤➥➦➧➨➚➘➙➛➜➝➞➸➲➳⏎➴➵➶➷➸➹➺➻➼➽←↑→↓↔↕↖↗↘↙↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨➫➬➩➪➭➮➯➱↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹↺↻↼↽↾↿⇀⇁⇂⇃⇄⇅⇆" +
                "⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇚⇛⇜⇝⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾ┌┍┎┏┐┑┒┓" +
                "└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬◤◥◄►▶◀◣◢▲▼◥▸◂▴▾" +
                "△▽▷◁⊿▻◅▵▿▹◃❏❐❑❒▀▁▂▃▄▅▆▇▉▊▋█▌▍▎▏▐░▒▓▔▕■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯" +
                "㋀㋁㋂㋃㋄㋅㋆㋇㋈㋉㋊㋋㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰㍘" +
                "☰☲☱☴☵☶☳☷☯♂♀✲☀☼☾☽◐◑☺☻☎☏✿❀№↑↓←→√×÷★℃℉°◆◇⊙■□△▽¿½☯✡㍿卍卐♂♀✚〓㎡♪♫♩♬囍Φ♀♂‖$@*&#※卍卐Ψ♫♬♭♩♪♯♮⌒¶∮‖€£¥$" +
                "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⓪⓿❶❷❸❹❺❻❼❽❾❿⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇" +
                "⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ" +
                "﹢﹣×÷±+-*/^=≌∽≦≧≒﹤﹥≈≡≠≤≥≮≯∷∶∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙√∛∜∟⊿㏒㏑%‰⅟½⅓⅕⅙⅐⅛⅑⅒⅔¾⅖⅗⅘⅚⅜⅝⅞≂≃≄≅≆≇≉≊≋≍≎≏≐≑≓≔≕≖≗≘≙≚≛≜≝≞≟≢≣≨≩⊰⊱⋛⋚" +
                "∫∮∬∭∯∰∱∲∳℅øπ∀∁∂∃∄∅∆∇∉∊∋∌∍∎∐−∓∔∕∖∗∘∙∡∢∣∤∦∸∹∺∻∼∾∿≀≁≪≫≬≭≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊚⊛⊜⊝⊞⊟⊠⊡" +
                "⊢⊣⊤⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿" +
                "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮↁↂↃↅↆↇↈ↉↊↋■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥" +
                "◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◿◻◼◽◾⏢⏥⌓⌔⌖⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₒₓₔₕₖₗₘₙₚₛₜ。,、':∶;?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;" +
                "!´?!~—ˉ|‖"〃`@﹫¡¿﹏﹋﹌︴々﹟#﹩$﹠&﹪%*﹡﹢﹦﹤‐ ̄¯―﹨ˆ˜﹍﹎+=<__-\\ˇ~﹉﹊()〈〉‹›﹛﹜『』〖〗[]《》〔〕{}「」【】" +
                "︵︷︿︹︽_﹁﹃︻︶︸﹀︺︾ˉ﹂﹄︼❝❞‐‑‒–―‖‗‘’‚‛“”„‟†‡•‣․‥…‧‰‱′″‴‵‶‷‸※‼‽‾‿⁀⁁⁂⁃⁄⁇⁈⁉⁊⁋⁌⁍⁎⁏" +
                "⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞°′″$¥〒¢£%@℃℉﹩﹪‰﹫㎡㎥³㎜㎟㎣㎝㎠㎤㍷㍸㍹㎞㎢㎦㏎㎚㎛㏕㎍㎎㎏㏄º○¤%$º¹²³" +
                "㍺㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏋㏌㏍㏎㏏㏐㏑㏒㏓㏔㏕㏖㏗㏘㏙㏚㏛㏜" +
                "㏝㏞㏟㍱㍲㍳㍴㍵㍶€£Ұ₴$₰¢₤¥₳₲₪₵元₣₱฿¤₡₮₭₩ރ円₢₥₫₦ł﷼₠₧₯₨čर₹ƒ₸¢↑↓←→↖↗↘↙↔↕" +
                "➻➼➽➸➳➺➻➴➵➶➷➹▶►▷◁◀◄«»➩➪➫➬➭➮➯➱⏎➲➾➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨↚↛↜↝↞↟↠↠↡↢↣↤↤↥↦↧↨⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇖⇗⇘⇙⇜↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹☇☈↼↽↾↿⇀⇁⇂⇃" +
                "⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪↺↻⇚⇛✐✎✏✑✒✉✁✂✃✄✆✉☎☏☑✓✔√☐☒✗✘ㄨ✕✖✖☢☠☣✈★☆✡囍㍿☯☰☲☱☴☵☶☳☷☜☞☚☛☟♤♧♡♢♠♣♥♦☀☁☂❄☃♨웃유❖☽☾☪✿♂♀✪✯☭➳" +
                "卍卐√×■◆●○◐◑✙☺☻❀⚘♔♕♖♗♘♙♚♛♜♝♞♟♧♡♂♀♠♣♥❤☜☞☎☏⊙◎☺☻☼▧▨♨◐◑↔↕▪▒◊◦▣▤▥▦▩◘◈◇♬♪♩♭♪の★☆→あぃ£Ю〓§♤♥▶¤✲❈✿✲❈➹☀☂☁【】" +
                "┱┲❣✚✪✣✤✥✦❉❥❦❧❃❂❁❀✄☪☣☢☠☭ღ▶▷◀◁☀☁☂☃☄★☆☇☈⊙☊☋☌☍ⓛⓞⓥⓔ╬『』∴☀♫♬♩♭♪☆∷﹌の★◎▶☺☻►◄▧▨♨◐◑↔↕↘▀▄█▌◦☼♪の☆→♧ぃ£❤▒▬♦◊◦♠♣▣۰•❤•۰" +
                "►◄▧▨♨◐◑↔↕▪▫☼♦⊙●○①⊕◎Θ⊙¤㊣★☆♀◆◇◣◢◥▲▼△▽⊿◤◥✐✡✓✔✕✖♂♀♥♡☜☞☎☏⊙◎☺☻►◄▧▨♨◐◑↔↕♥♡▪▫☼♦▀▄█▌▐░▒▬♦◊◘◙◦☼♠♣▣▤▥▦▩◘◙◈" +
                "♫♬♪♩♭♪✄☪☣☢☠♯♩♪♫♬♭♮☎☏☪ºº₪¤큐«»™♂✿♥◕‿-。。◕‿◕。āáǎàōóǒòēéěèīíǐìūúǔùǖǘǚǜüêɑńňɡ" +
                "ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ々〆のぁ〡〢〣〤〥〦〧〨〩─━│┃╌╍╎╏┄┅┆┇┈┉┊┋" +
                "┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╪╫╬═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╳╔╗╝╚╬═╓╩┠┨┯┷┏┓┗┛┳" +
                "⊥﹃﹄┌╮╭╯╰♚♛♝♞♜♟♔♕♗♘♖♟";
        if (StringUtils.isNotBlank(speciSymbols)) {
            for (int index = 0; index < speciSymbols.length(); index++) {
                specialSymbolsRes.add(String.valueOf(speciSymbols.charAt(index)));
            }
        }

        Set<Integer> specialSymbolsSet = new HashSet<>();
        if (!CollectionUtils.isEmpty(specialSymbolsRes)) {
            char[] chs;
            for (String curr : specialSymbolsRes) {
                chs = curr.toCharArray();
                for (char c : chs) {
                    specialSymbolsSet.add(BCConvert.charConvert(c));
                }
            }
        }
        return specialSymbolsSet;
    }

    public static void main(String[] args) {
        String content = "你₴$₰¢₤¥₳₲₪₵元₣₱฿¤₡₮₭₩ރ円₢好❆❇❈,张┶┷┸┹┺┻┼┽┾彦㎵㎶㎷㎸㎹㎺峰⓳⓴⓵⓶⓷!";
        char[] valueChars = content.toCharArray();
        Set<Integer> specialSymbols = getSpecialSymbols();
        StringBuilder cleanContent = new StringBuilder();
        for (char valueChar : valueChars) {
            int temp = BCConvert.charConvert(valueChar);
            if (specialSymbols.contains(temp)) {
                /*过滤特殊字符*/
                continue;
            }
            cleanContent.append(valueChar);
        }
        System.out.println(cleanContent.toString());
    }
}

数据清洗:去除相关emoji信息

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.base.BCConvert;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author yanfengzhang
 * @description 数据清洗:去除相关emoji信息
 * @date 2022/4/5  15:36
 */
@Component
@SensitiveClean(cleanCode = SensitiveCons.Clean.REMOVE_EMOJI)
public class RemoveEmoji implements ContextHandler<ContentInfoContext, ContentCleanResContext> {

    private LoadingCache<String, Set<Integer>> emojiCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(10, TimeUnit.MINUTES)
            /*构建缓存*/
            .build(new CacheLoader<String, Set<Integer>>() {
                /*初始化加载数据的缓存信息*/
                @Override
                public Set<Integer> load(String specialSymbols) throws Exception {
                    return getEmojis();
                }
            });

    /**
     * 对用户内容进行处理:去除相关emoji信息
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词词库校验)
     */
    @Override
    public ContentCleanResContext handle(ContentInfoContext context) {
        try {
            String EMOJI = "emoji";
            Set<Integer> emojis = emojiCache.get(EMOJI);
            StringBuilder cleanContent = new StringBuilder();
            /*其他链路中清洗后的词*/
            char[] valueChars = context.getCleanContent().toCharArray();
            for (char valueChar : valueChars) {
                int temp = BCConvert.charConvert(valueChar);
                if (emojis.contains(temp)) {
                    /*过滤特殊字符*/
                    continue;
                }
                cleanContent.append(valueChar);
            }

            /*将本次清洗数据载入待继续清洗实体中*/
            context.setCleanContent(cleanContent.toString());
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(true)
                    .content(context.getContent())
                    .cleanContent(cleanContent.toString())
                    .contentAttr(context.getContentAttr())
                    .build();
        } catch (Exception e) {
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(false)
                    .content(context.getContent())
                    /*记录下中间态数据*/
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .reason("数据清洗异常:去除相关emoji信息失败")
                    .build();
        }
    }

    /**
     * 本处举例,实际应该放置到指定的配置页面来实时生效
     *
     * @return 相关Emoji集合
     */
    private static Set<Integer> getEmojis() {
        String emojiStr = "\uD83D\uDC36,\uD83D\uDC15,\uD83D\uDC59,\uD83D\uDEAD,\uD83D\uDEAC,\uD83D\uDC8A,\uD83C\uDF47,\uD83C\uDF48," +
                "\uD83C\uDF49,\uD83C\uDF4A,\uD83C\uDF4B,\uD83C\uDF4C,\uD83C\uDF4D,\uD83C\uDF4E,\uD83C\uDF4F,\uD83C\uDF50," +
                "\uD83C\uDF51,\uD83C\uDF52,\uD83C\uDF53,\uD83E\uDD5D,\uD83C\uDF45,\uD83E\uDD65,\uD83E\uDD51,\uD83C\uDF46," +
                "\uD83E\uDD54,\uD83E\uDD55,\uD83C\uDF3D,\uD83C\uDF36️,\uD83E\uDD52,\uD83E\uDD66,\uD83C\uDF44,\uD83E\uDD5C," +
                "\uD83C\uDF30,\uD83C\uDF5E,\uD83E\uDD50,\uD83E\uDD56,\uD83E\uDD68,\uD83E\uDD5E,\uD83E\uDDC0,\uD83C\uDF56," +
                "\uD83C\uDF57,\uD83E\uDD69,\uD83E\uDD53,\uD83C\uDF54,\uD83C\uDF5F,\uD83C\uDF55,\uD83C\uDF2D,\uD83E\uDD6A," +
                "\uD83C\uDF2E,\uD83C\uDF2F,\uD83E\uDD59,\uD83E\uDD5A,\uD83C\uDF73,\uD83E\uDD58,\uD83C\uDF72,\uD83E\uDD63," +
                "\uD83E\uDD57,\uD83C\uDF7F,\uD83E\uDD6B,\uD83C\uDF71,\uD83C\uDF58,\uD83C\uDF59,\uD83C\uDF5A,\uD83C\uDF5B," +
                "\uD83C\uDF5C,\uD83C\uDF5D,\uD83C\uDF60,\uD83C\uDF62,\uD83C\uDF63,\uD83C\uDF64,\uD83C\uDF65,\uD83C\uDF61," +
                "\uD83E\uDD5F,\uD83E\uDD60,\uD83E\uDD61,\uD83C\uDF66,\uD83C\uDF67,\uD83C\uDF68,\uD83C\uDF69,\uD83C\uDF6A," +
                "\uD83C\uDF82,\uD83C\uDF70,\uD83E\uDDC1,\uD83E\uDD67,\uD83C\uDF6B,\uD83C\uDF6C,\uD83C\uDF6D,\uD83C\uDF6E," +
                "\uD83C\uDF6F,\uD83C\uDF7C,\uD83E\uDD5B,☕,\uD83E\uDED6,\uD83C\uDF75,\uD83C\uDF76,\uD83C\uDF7E,\uD83C\uDF77," +
                "\uD83C\uDF78,\uD83C\uDF79,\uD83C\uDF7A,\uD83C\uDF7B,\uD83E\uDD42,\uD83E\uDD43,\uD83E\uDD64,\uD83E\uDD62," +
                "\uD83C\uDF7D️,\uD83C\uDF74,\uD83E\uDD44,\uD83D\uDEA3,\uD83D\uDDFE,\uD83C\uDFD4️,⛰️,\uD83C\uDF0B,\uD83D\uDDFB," +
                "\uD83C\uDFD5️,\uD83C\uDFD6️,\uD83C\uDFDC️,\uD83C\uDFDD️,\uD83C\uDFDE️,\uD83C\uDFDF️,\uD83C\uDFDB️,\uD83C\uDFD7️,⛩️," +
                "\uD83D\uDD4B,⛲,⛺,\uD83C\uDF01,\uD83C\uDF03,\uD83C\uDFD9️,\uD83C\uDF04,\uD83C\uDF05,\uD83C\uDF06,\uD83C\uDF07," +
                "\uD83C\uDF09,\uD83C\uDFA0,\uD83C\uDFA1,\uD83C\uDFA2,\uD83D\uDE82,\uD83D\uDE83,\uD83D\uDE84,\uD83D\uDE85," +
                "\uD83D\uDE86,\uD83D\uDE87,\uD83D\uDE88,\uD83D\uDE89,\uD83D\uDE8A,\uD83D\uDE9D,\uD83D\uDE9E,\uD83D\uDE8B," +
                "\uD83D\uDE8C,\uD83D\uDE8D,\uD83D\uDE8E,\uD83D\uDE90,\uD83D\uDE91,\uD83D\uDE92,\uD83D\uDE93,\uD83D\uDE94," +
                "\uD83D\uDE95,\uD83D\uDE96,\uD83D\uDE97,\uD83D\uDE98,\uD83D\uDE99,\uD83D\uDE9A,\uD83D\uDE9B,\uD83D\uDE9C," +
                "\uD83C\uDFCE️,\uD83C\uDFCD️,\uD83D\uDEF5,\uD83D\uDEB2,\uD83D\uDEF4,\uD83D\uDE8F,\uD83D\uDEE3️,\uD83D\uDEE4️,⛽," +
                "\uD83D\uDEA8,\uD83D\uDEA5,\uD83D\uDEA6,\uD83D\uDEA7,⚓,⛵,\uD83D\uDEA4,\uD83D\uDEF3️,⛴️,\uD83D\uDEE5️," +
                "\uD83D\uDEA2,✈️,\uD83D\uDEE9️,\uD83D\uDEEB,\uD83D\uDEEC,⛱️,\uD83C\uDF86,\uD83C\uDF87,\uD83C\uDF91,\uD83D\uDCB4," +
                "\uD83D\uDCB5,\uD83D\uDCB6,\uD83D\uDCB7,\uD83D\uDDFF,\uD83D\uDEC2,\uD83D\uDEC3,\uD83D\uDEC4,\uD83D\uDEC5," +
                "\uD83D\uDE48,\uD83D\uDE49,\uD83D\uDE4A,\uD83D\uDCA5,\uD83D\uDCAB,\uD83D\uDCA6,\uD83D\uDCA8,\uD83D\uDC35," +
                "\uD83D\uDC12,\uD83E\uDD8D,\uD83D\uDC36,\uD83D\uDC15,\uD83D\uDC29,\uD83D\uDC3A,\uD83E\uDD8A,\uD83D\uDC31," +
                "\uD83D\uDC08,\uD83D\uDC08\u200D⬛,\uD83E\uDD81,\uD83D\uDC2F,\uD83D\uDC05,\uD83D\uDC06,\uD83D\uDC34,\uD83D\uDC0E," +
                "\uD83E\uDD84,\uD83E\uDD93,\uD83E\uDD8C,\uD83E\uDDAC,\uD83D\uDC2E,\uD83D\uDC02,\uD83D\uDC03,\uD83D\uDC04," +
                "\uD83D\uDC37,\uD83D\uDC16,\uD83D\uDC17,\uD83D\uDC3D,\uD83D\uDC0F,\uD83D\uDC11,\uD83D\uDC10,\uD83D\uDC2A," +
                "\uD83D\uDC2B,\uD83E\uDD99,\uD83E\uDD92,\uD83D\uDC18,\uD83E\uDD8F,\uD83D\uDC2D,\uD83D\uDC01,\uD83D\uDC00," +
                "\uD83D\uDC39,\uD83D\uDC30,\uD83D\uDC07,\uD83D\uDC3F️,\uD83E\uDDAB,\uD83E\uDD94,\uD83E\uDD87,\uD83D\uDC3B," +
                "\uD83D\uDC3B\u200D❄️,\uD83D\uDC28,\uD83D\uDC3C,\uD83D\uDC3E,\uD83E\uDD83,\uD83D\uDC14,\uD83D\uDC13," +
                "\uD83D\uDC23,\uD83D\uDC24,\uD83D\uDC25,\uD83D\uDC26,\uD83D\uDC27,\uD83D\uDD4A️,\uD83E\uDD85,\uD83E\uDD86," +
                "\uD83E\uDD89,\uD83D\uDC38,\uD83D\uDC0A,\uD83D\uDC22,\uD83E\uDD8E,\uD83D\uDC0D,\uD83D\uDC32,\uD83D\uDC09," +
                "\uD83E\uDD95,\uD83E\uDD96,\uD83D\uDC33,\uD83D\uDC0B,\uD83D\uDC2C,\uD83D\uDC1F,\uD83D\uDC20,\uD83D\uDC21," +
                "\uD83E\uDD88,\uD83D\uDC19,\uD83D\uDC1A,\uD83D\uDC0C,\uD83E\uDD8B,\uD83D\uDC1B,\uD83D\uDC1C,\uD83D\uDC1D,☘️," +
                "\uD83C\uDF40,\uD83C\uDF41,\uD83C\uDF42,\uD83C\uDF43,\uD83C\uDF44,\uD83C\uDF30,\uD83E\uDD80,\uD83E\uDD9E," +
                "\uD83E\uDD90,\uD83E\uDD91,\uD83C\uDF0D,\uD83C\uDF0E,\uD83C\uDF0F,\uD83C\uDF10,\uD83C\uDF11,\uD83C\uDF12," +
                "\uD83C\uDF13,\uD83C\uDF14,\uD83C\uDF15,\uD83C\uDF16,\uD83C\uDF17,\uD83C\uDF18,\uD83C\uDF19,\uD83C\uDF1A," +
                "\uD83C\uDF1B,\uD83C\uDF1C,☀️,\uD83C\uDF1D,\uD83C\uDF1E,⭐,\uD83C\uDF1F,\uD83C\uDF20,☁️,⛅,⛈️,\uD83C\uDF24️," +
                "\uD83C\uDF25️,\uD83C\uDF26️,\uD83C\uDF27️,\uD83C\uDF28️,\uD83C\uDF29️,\uD83C\uDF2A️,\uD83C\uDF2B️,\uD83C\uDF2C️," +
                "\uD83C\uDF08,☂️,☔,⚡,❄️,☃️,⛄,☄️,\uD83D\uDD25,\uD83D\uDCA7,\uD83C\uDF0A,\uD83C\uDF84,✨,\uD83C\uDF8B," +
                "\uD83C\uDF8D,\uD83D\uDE00,\uD83D\uDE03,\uD83D\uDE04,\uD83D\uDE01,\uD83D\uDE06,\uD83D\uDE05,\uD83E\uDD23," +
                "\uD83D\uDE02,\uD83D\uDE42,\uD83D\uDE43,\uD83D\uDE09,\uD83D\uDE0A,\uD83D\uDE07,\uD83E\uDD70,\uD83D\uDE0D," +
                "\uD83E\uDD29,\uD83D\uDE18,\uD83D\uDE17,☺️,\uD83D\uDE1A,\uD83D\uDE19,\uD83E\uDD72,\uD83D\uDE0B,\uD83D\uDE1B," +
                "\uD83D\uDE1C,\uD83E\uDD2A,\uD83D\uDE1D,\uD83E\uDD11,\uD83E\uDD17,\uD83E\uDD2D,\uD83E\uDD2B,\uD83E\uDD14," +
                "\uD83E\uDD10,\uD83E\uDD28,\uD83D\uDE10,\uD83D\uDE11,\uD83D\uDE36,\uD83D\uDE0F,\uD83D\uDE12,\uD83D\uDE44," +
                "\uD83D\uDE2C,\uD83E\uDD25,\uD83D\uDE0C,\uD83D\uDE14,\uD83D\uDE2A,\uD83E\uDD24,\uD83D\uDE34,\uD83D\uDE37," +
                "\uD83E\uDD12,\uD83E\uDD15,\uD83E\uDD22,\uD83E\uDD2E,\uD83E\uDD27,\uD83D\uDE35,\uD83E\uDD2F,\uD83E\uDD20," +
                "\uD83D\uDE0E,\uD83E\uDD13,\uD83E\uDDD0,\uD83D\uDE15,\uD83D\uDE1F,\uD83D\uDE41,☹️,\uD83D\uDE2E,\uD83D\uDE2F," +
                "\uD83D\uDE32,\uD83D\uDE33,\uD83E\uDD7A,\uD83D\uDE26,\uD83D\uDE27,\uD83D\uDE28,\uD83D\uDE30,\uD83D\uDE25," +
                "\uD83D\uDE22,\uD83D\uDE2D,\uD83D\uDE31,\uD83D\uDE16,\uD83D\uDE23,\uD83D\uDE1E,\uD83D\uDE13,\uD83D\uDE29," +
                "\uD83D\uDE2B,\uD83E\uDD71,\uD83D\uDE24,\uD83D\uDE21,\uD83D\uDE20,\uD83E\uDD2C,\uD83D\uDE08,\uD83D\uDC7F," +
                "\uD83D\uDC80,☠️,\uD83D\uDCA9,\uD83E\uDD21,\uD83D\uDC79,\uD83D\uDC7A,\uD83D\uDC7B,\uD83D\uDC7D,\uD83D\uDC7E," +
                "\uD83E\uDD16,\uD83D\uDE3A,\uD83D\uDE38,\uD83D\uDE39,\uD83D\uDE3B,\uD83D\uDE3C,\uD83D\uDE3D,\uD83D\uDE40," +
                "\uD83D\uDE3F,\uD83D\uDE3E,\uD83D\uDC8B,\uD83D\uDC4B,\uD83E\uDD1A,\uD83D\uDD90️,✋,\uD83D\uDD96,\uD83D\uDC4C," +
                "✌️,\uD83E\uDD1E,\uD83E\uDD1F,\uD83E\uDD18,\uD83E\uDD19,\uD83D\uDC48,\uD83D\uDC49,\uD83D\uDC46,\uD83D\uDD95," +
                "\uD83D\uDC47,☝️,\uD83D\uDC4D,\uD83D\uDC4E,✊,\uD83D\uDC4A,\uD83E\uDD1B,\uD83E\uDD1C,\uD83D\uDC4F,\uD83D\uDE4C," +
                "\uD83D\uDC50,\uD83E\uDD32,\uD83E\uDD1D,\uD83D\uDE4F,✍️,\uD83D\uDC85,\uD83E\uDD33,\uD83D\uDCAA,\uD83D\uDC42," +
                "\uD83E\uDDBB,\uD83D\uDC43,\uD83E\uDDE0,\uD83D\uDC40,\uD83D\uDC41️,\uD83D\uDC45,\uD83D\uDC44,\uD83D\uDC76," +
                "\uD83E\uDDD2,\uD83D\uDC66,\uD83D\uDC67,\uD83E\uDDD1,\uD83D\uDC71,\uD83D\uDC68,\uD83E\uDDD4,\uD83D\uDC69," +
                "\uD83D\uDC71\u200D♀️,\uD83D\uDC71\u200D♂️,\uD83E\uDDD3,\uD83D\uDC74,\uD83D\uDC75,\uD83D\uDE4D,\uD83D\uDE4D\u200D♂️," +
                "\uD83D\uDE4D\u200D♀️,\uD83D\uDE4E,\uD83D\uDE4E\u200D♂️,\uD83D\uDE4E\u200D♀️,\uD83D\uDE45,\uD83D\uDE45\u200D♂️," +
                "\uD83D\uDE45\u200D♀️,\uD83D\uDE46,\uD83D\uDE46\u200D♂️,\uD83D\uDE46\u200D♀️,\uD83D\uDC81,\uD83D\uDC81\u200D♂️," +
                "\uD83D\uDC81\u200D♀️,\uD83D\uDE4B,\uD83D\uDE4B\u200D♂️,\uD83D\uDE4B\u200D♀️,\uD83D\uDE47,\uD83D\uDE47\u200D♂️," +
                "\uD83D\uDE47\u200D♀️,\uD83E\uDD26,\uD83E\uDD26\u200D♂️,\uD83E\uDD26\u200D♀️,\uD83E\uDD37,\uD83E\uDD37\u200D♂️," +
                "\uD83E\uDD37\u200D♀️,\uD83E\uDDD1\u200D⚕️,\uD83D\uDC68\u200D⚕️,\uD83D\uDC69\u200D⚕️,\uD83E\uDDD1\u200D\uD83C\uDF93," +
                "\uD83D\uDC68\u200D\uD83C\uDF93,\uD83D\uDC69\u200D\uD83C\uDF93,\uD83E\uDDD1\u200D\uD83C\uDFEB," +
                "\uD83D\uDC68\u200D\uD83C\uDFEB,\uD83D\uDC69\u200D\uD83C\uDFEB,\uD83E\uDDD1\u200D⚖️,\uD83D\uDC68\u200D⚖️," +
                "\uD83D\uDC69\u200D⚖️,\uD83E\uDDD1\u200D\uD83C\uDF3E,\uD83D\uDC68\u200D\uD83C\uDF3E,\uD83D\uDC69,\uD83C\uDF3E," +
                "\uD83E\uDDD1\u200D\uD83C\uDF73,\uD83D\uDC68\u200D\uD83C\uDF73,\uD83D\uDC69," +
                "\uD83D\uDC68\u200D\uD83D\uDC66\u200D\uD83D\uDC66,\uD83D\uDC68\u200D\uD83D\uDC67," +
                "\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC66,\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC67," +
                "\uD83D\uDC69\u200D\uD83D\uDC66,\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66,\uD83D\uDC69\u200D\uD83D\uDC67," +
                "\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66,\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67,\uD83D\uDDE3️," +
                "\uD83D\uDC64,\uD83D\uDC65,⛑️,\uD83D\uDC84,\uD83D\uDC8D,\uD83D\uDCBC,\uD83E\uDE78,\uD83D\uDC8C,\uD83D\uDD73️,\uD83D\uDCA3," +
                "\uD83D\uDEC0,\uD83D\uDECC,\uD83D\uDD2A,\uD83C\uDFFA,\uD83D\uDDFA️,\uD83D\uDC88,\uD83D\uDEE2️,\uD83D\uDECE️," +
                "⌛,⏳,⌚,⏰,⏱️,⏲️,\uD83D\uDD70️,\uD83C\uDF21️,⛱️,⌨️,\uD83D\uDDB1️,\uD83D\uDDB2️,\uD83D\uDCBD,\uD83D\uDCBE,\uD83D\uDCBF," +
                "\uD83D\uDCC0,\uD83C\uDFA5,\uD83C\uDF9E️,\uD83D\uDCFD️,\uD83D\uDCFA,\uD83D\uDCF7,\uD83D\uDCF8,\uD83D\uDCF9,\uD83D\uDCFC," +
                "\uD83D\uDD0D,\uD83D\uDD0E,\uD83D\uDD6F️,\uD83D\uDCA1,\uD83D\uDD26,\uD83C\uDFEE,\uD83D\uDCD4,\uD83D\uDCD5,\uD83D\uDCD6," +
                "\uD83D\uDCD7,\uD83D\uDCD8,\uD83D\uDCD9,\uD83D\uDCDA,\uD83D\uDCD3,\uD83D\uDCD2,\uD83D\uDCC3,\uD83D\uDCDC,\uD83D\uDCC4," +
                "\uD83D\uDCF0,\uD83D\uDDDE️,\uD83D\uDCD1,\uD83D\uDD16,\uD83C\uDFF7️,\uD83D\uDCB0,\uD83D\uDCB4,\uD83D\uDCB5,\uD83D\uDCB6," +
                "\uD83D\uDCB7,\uD83D\uDCB8,\uD83D\uDCB3,✉️,\uD83D\uDCE7,\uD83D\uDCE8,\uD83D\uDCE9,\uD83D\uDCE4,\uD83D\uDCE5,\uD83D\uDCE6," +
                "\uD83D\uDCEB,\uD83D\uDCEA,\uD83D\uDCEC,\uD83D\uDCED,\uD83D\uDCEE,\uD83D\uDDF3️,✏️,✒️,\uD83D\uDD8B️,\uD83D\uDD8A️," +
                "\uD83D\uDD8C️,\uD83D\uDD8D️,\uD83D\uDCDD,\uD83D\uDCC1,\uD83D\uDCC2,\uD83D\uDDC2️,\uD83D\uDCC5,\uD83D\uDCC6,\uD83D\uDDD2️," +
                "\uD83D\uDDD3️,\uD83D\uDCC7,\uD83D\uDCC8,\uD83D\uDCC9,\uD83D\uDCCA,\uD83D\uDCCB,\uD83D\uDCCC,\uD83D\uDCCD,\uD83D\uDCCE," +
                "\uD83D\uDD87️,\uD83D\uDCCF,\uD83D\uDCD0,✂️,\uD83D\uDDC3️,\uD83D\uDDC4️,\uD83D\uDDD1️,\uD83D\uDD12,\uD83D\uDD13,\uD83D\uDD0F," +
                "\uD83D\uDD10,\uD83D\uDD11,\uD83D\uDDDD️,\uD83D\uDD28,⛏️,⚒️,\uD83D\uDEE0️,\uD83D\uDDE1️,⚔️,\uD83D\uDD2B,\uD83D\uDEE1️," +
                "\uD83D\uDD27,\uD83D\uDD29,⚙️,\uD83D\uDDDC️,⚖️,\uD83E\uDDAF,\uD83D\uDD17,⛓️,⚗️,⚰️,⚱️,\uD83D\uDDFF,\uD83D\uDEB0," +
                "\uD83D\uDD74️,\uD83E\uDDD7,\uD83E\uDDD7\u200D♂️,\uD83E\uDDD7\u200D♀️,\uD83E\uDD3A,\uD83C\uDFC7,⛷️,\uD83C\uDFC2," +
                "\uD83C\uDFCC️,\uD83C\uDFCC️\u200D♂️,\uD83C\uDFCC️\u200D♀️,\uD83C\uDFC4,\uD83C\uDFC4\u200D♂️,\uD83C\uDFC4\u200D♀️," +
                "\uD83D\uDEA3,\uD83D\uDEA3\u200D♂️,\uD83D\uDEA3\u200D♀️,\uD83C\uDFCA,\uD83C\uDFCA\u200D♂️,\uD83C\uDFCA\u200D♀️,⛹️" +
                ",⛹️\u200D♂️,⛹️\u200D♀️,\uD83C\uDFCB️,\uD83C\uDFCB️\u200D♂️,\uD83C\uDFCB️\u200D♀️,\uD83D\uDEB4,\uD83D\uDEB4\u200D♂️," +
                "\uD83D\uDEB4\u200D♀️,\uD83D\uDEB5,\uD83D\uDEB5\u200D♂️,\uD83D\uDEB5\u200D♀️,\uD83E\uDD38,\uD83E\uDD38\u200D♂️," +
                "\uD83E\uDD38\u200D♀️,\uD83E\uDD3C,\uD83E\uDD3C\u200D♂️,\uD83E\uDD3C\u200D♀️,\uD83E\uDD3D,\uD83E\uDD3D\u200D♂️," +
                "\uD83E\uDD3D\u200D♀️,\uD83E\uDD3E,\uD83E\uDD3E\u200D♂️,\uD83E\uDD3E\u200D♀️,\uD83E\uDD39,\uD83E\uDD39\u200D♂️," +
                "\uD83E\uDD39\u200D♀️,\uD83E\uDDD8,\uD83E\uDDD8\u200D♂️,\uD83E\uDDD8\u200D♀️,\uD83C\uDFAA,\uD83D\uDEF6,\uD83C\uDF97️," +
                "\uD83C\uDF9F️,\uD83C\uDFAB,\uD83C\uDF96️,\uD83C\uDFC6,\uD83C\uDFC5,\uD83E\uDD47,\uD83E\uDD48,\uD83E\uDD49,⚽,⚾," +
                "\uD83C\uDFC0,\uD83C\uDFD0,\uD83C\uDFC8,\uD83C\uDFC9,\uD83C\uDFBE,\uD83C\uDFB3,\uD83C\uDFCF,\uD83C\uDFD1,\uD83C\uDFD2," +
                "\uD83C\uDFD3,\uD83C\uDFF8,\uD83E\uDD4A,\uD83E\uDD4B,\uD83E\uDD45,⛳,⛸️,\uD83C\uDFA3,\uD83C\uDFBD,\uD83C\uDFBF," +
                "\uD83D\uDEF7,\uD83E\uDD4C,\uD83C\uDFAF,\uD83C\uDFB1,\uD83C\uDFAE,\uD83C\uDFB0,\uD83C\uDFB2,♟️,\uD83C\uDFAD,\uD83C\uDFA8," +
                "\uD83C\uDFBC,\uD83C\uDFA4,\uD83C\uDFA7,\uD83C\uDFB7,\uD83C\uDFB8,\uD83C\uDFB9,\uD83C\uDFBA,\uD83C\uDFBB,\uD83E\uDD41," +
                "\uD83E\uDE98,\uD83C\uDFAC,\uD83C\uDFF9,\uD83D\uDC98,\uD83D\uDC9D,\uD83D\uDC96,\uD83D\uDC97,\uD83D\uDC93,\uD83D\uDC9E," +
                "\uD83D\uDC95,\uD83D\uDC9F,❣️,\uD83D\uDC94,❤️,\uD83E\uDDE1,\uD83D\uDC9B,\uD83D\uDC9A,\uD83D\uDC99,\uD83D\uDC9C," +
                "\uD83D\uDDA4,\uD83D\uDCAF,\uD83D\uDCA2,\uD83D\uDCAC,\uD83D\uDC41️\u200D\uD83D\uDDE8️,\uD83D\uDDE8️,\uD83D\uDDEF️," +
                "\uD83D\uDCAD,\uD83D\uDCA4,\uD83D\uDCAE,♨️,\uD83D\uDC88,\uD83D\uDED1,\uD83D\uDD5B,\uD83D\uDD67,\uD83D\uDD50,\uD83D\uDD5C," +
                "\uD83D\uDD51,\uD83D\uDD5D,\uD83D\uDD52,\uD83D\uDD5E,\uD83D\uDD53,\uD83D\uDD5F,\uD83D\uDD54,\uD83D\uDD60,\uD83D\uDD55," +
                "\uD83D\uDD61,\uD83D\uDD56,\uD83D\uDD62,\uD83D\uDD57,\uD83D\uDD63,\uD83D\uDD58,\uD83D\uDD64,\uD83D\uDD59,\uD83D\uDD65," +
                "\uD83D\uDD5A,\uD83D\uDD66,\uD83C\uDF00,♠️,♥️,♦️,♣️,\uD83C\uDCCF,\uD83C\uDC04,\uD83C\uDFB4,\uD83D\uDD07,\uD83D\uDD08," +
                "\uD83D\uDD09,\uD83D\uDD0A,\uD83D\uDCE2,\uD83D\uDCE3,\uD83D\uDCEF,\uD83D\uDD14,\uD83D\uDD15,\uD83C\uDFB5,\uD83C\uDFB6," +
                "\uD83D\uDCB9,\uD83C\uDFE7,\uD83D\uDEAE,\uD83D\uDEB0,♿,\uD83D\uDEB9,\uD83D\uDEBA,\uD83D\uDEBB,\uD83D\uDEBC,\uD83D\uDEBE," +
                "⚠️,\uD83D\uDEB8,⛔,\uD83D\uDEAB,\uD83D\uDEB3,\uD83D\uDEAD,\uD83D\uDEAF,\uD83D\uDEB1,\uD83D\uDEB7,\uD83D\uDCF5," +
                "\uD83D\uDD1E,☢️,☣️,⬆️,↗️,➡️,↘️,⬇️,↙️,⬅️,↖️,↕️,↔️,↩️,↪️,⤴️,⤵️,\uD83D\uDD03,\uD83D\uDD04,\uD83D\uDD19,\uD83D\uDD1A," +
                "\uD83D\uDD1B,\uD83D\uDD1C,\uD83D\uDD1D,\uD83D\uDED0,⚛️,\uD83D\uDD49️,✡️,☸️,☯️,✝️,☦️,☪️,☮️,\uD83D\uDD4E,\uD83D\uDD2F" +
                ",♈,♉,♊,♋,♌,♍,♎,♏,♐,♑,♒,♓,⛎,\uD83D\uDD00,\uD83D\uDD01,\uD83D\uDD02,▶️,⏩,⏭️,⏯️,◀️,⏪,⏮️,\uD83D\uDD3C,⏫," +
                "\uD83D\uDD3D,⏬,⏸️,⏹️,⏺️,⏏️,\uD83C\uDFA6,\uD83D\uDD05,\uD83D\uDD06,\uD83D\uDCF6,\uD83D\uDCF3,\uD83D\uDCF4,♀️,♂️,✖️,➕" +
                ",➖,➗,♾️,‼️,⁉️,❓,❔,❕,❗,〰️,\uD83D\uDCB1,\uD83D\uDCB2,⚕️,♻️,⚜️,\uD83D\uDD31,\uD83D\uDCDB,\uD83D\uDD30,⭕,✅,☑️,✔️" +
                ",❌,❎,➰,➿,,〽️,✳️,✴️,❇️,©️,®️,™️,#️⃣,*️⃣,0️⃣,1️⃣,2️⃣,3️⃣,4️⃣,5️⃣,6️⃣,7️⃣,8️⃣,9️⃣,\uD83D\uDD1F,\uD83D\uDD20,\uD83D\uDD21," +
                "\uD83D\uDD22,\uD83D\uDD23,\uD83D\uDD24,\uD83C\uDD70️,\uD83C\uDD8E,\uD83C\uDD71️,\uD83C\uDD91,\uD83C\uDD92,\uD83C\uDD93," +
                "ℹ️,\uD83C\uDD94,Ⓜ️,\uD83C\uDD95,\uD83C\uDD96,\uD83C\uDD7E️,\uD83C\uDD97,\uD83C\uDD7F️,\uD83C\uDD98,\uD83C\uDD99," +
                "\uD83C\uDD9A,\uD83C\uDE01,\uD83C\uDE02️,\uD83C\uDE37️,\uD83C\uDE36,\uD83C\uDE2F,\uD83C\uDE50,\uD83C\uDE39,\uD83C\uDE1A," +
                "\uD83C\uDE32,\uD83C\uDE51,\uD83C\uDE38,\uD83C\uDE34,\uD83C\uDE33,㊗️,㊙️,\uD83C\uDE3A,\uD83C\uDE35,\uD83D\uDD34," +
                "\uD83D\uDD35,⚫,⚪,⬛,⬜,◼️,◻️,◾,◽,▪️,▫️,\uD83D\uDD36,\uD83D\uDD37,\uD83D\uDD38,\uD83D\uDD39,\uD83D\uDD3A," +
                "\uD83D\uDD3B,\uD83D\uDCA0,\uD83D\uDD18,\uD83D\uDD33,\uD83D\uDD32,\uD83C\uDFC1,\uD83D\uDEA9,\uD83C\uDF8C,\uD83C\uDFF4," +
                "\uD83C\uDFF3️,\uD83C\uDFF3️\u200D\uD83C\uDF08,\uD83C\uDFF3️\u200D⚧️,\uD83C\uDFF4\u200D☠️,\uD83C\uDFF4\u200D☠️," +
                "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F," +
                "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F," +
                "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F," +
                "\uD83C\uDFF4\uDB40\uDC75\uDB40\uDC73\uDB40\uDC74\uDB40\uDC78\uDB40\uDC7F\uDB40\uDC75\uDB40\uDC73\uDB40\uDC74\uDB40\uDC78\uDB40\uDC7F";
        if (StringUtils.isBlank(emojiStr)) {
            return Sets.newHashSet();
        }

        Set<String> emojis = Sets.newHashSet();
        for (String emoji : emojiStr.split(",")) {
            emojis.add(emoji);
        }
        if (CollectionUtils.isEmpty(emojis)) {
            return Sets.newHashSet();
        }

        char[] chs;
        Set<Integer> emojiRes = Sets.newHashSet();
        for (String curr : emojis) {
            chs = curr.toCharArray();
            for (char c : chs) {
                emojiRes.add(BCConvert.charConvert(c));
            }
        }

        return emojiRes;
    }

    public static void main(String[] args) {
        Set<Integer> emojis = getEmojis();
        String content = "你♋♌♍♎好✴️,张⏳⌚⏰彦⚗️⚰️峰✌⛳!☕";
        char[] valueChars = content.toCharArray();
        StringBuilder cleanContent = new StringBuilder();
        for (char valueChar : valueChars) {
            int temp = BCConvert.charConvert(valueChar);
            if (emojis.contains(temp)) {
                /*过滤特殊字符*/
                continue;
            }
            cleanContent.append(valueChar);
        }
        System.out.println(cleanContent.toString());
    }
}

数据清洗:文本语言限制

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean;

import com.kanlon.entity.DetectMode;
import com.kanlon.language.LanguageDistinguish;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;

/**
 * @author yanfengzhang
 * @description 文本语言限制
 * @date 2022/4/17  22:22
 */
@Component
@SensitiveClean(cleanCode = SensitiveCons.Clean.LANGUAGE_LIMIT)
public class LanguageDistinguishLimit implements ContextHandler<ContentInfoContext, ContentCleanResContext> {

    /**
     * 文本语言限制,避免用户各种语言进行非法宣传,对文本进行语言限制
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词词库校验)
     */
    @Override
    public ContentCleanResContext handle(ContentInfoContext context) {
        try {
            char[] valueChars = context.getCleanContent().toCharArray();
            /*1.对文本语言进行识别,并判断语言类型种类*/
            /*2.查看对应语言类型是否符合系统处理的要求,如果不符合抛出异常,链路结束处理*/

            /*将本次清洗数据载入待继续清洗实体中*/
            context.setCleanContent(context.getCleanContent());
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(true)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .build();
        } catch (Exception e) {
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(false)
                    .content(context.getContent())
                    /*记录下中间态数据*/
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .reason("数据清洗异常:文本语言已超出系统限制要求")
                    .build();
        }
    }

    public static void main(String[] args) {
        System.out.println(LanguageDistinguish.getLanguageByString("com.cybozu.labs.langdetect.Detector.getProbabilities", DetectMode.PRECISION));
        System.out.println(LanguageDistinguish.getLanguageByString("尽管每种应用都会有所不同,但是本质上都是相似的,需要比较单独个体的相似性。", DetectMode.PRECISION));
        System.out.println(LanguageDistinguish.getLanguageByString("com.cybozu.labs.langdetect.Detector.getProbabilities尽管每种应用都会有所不同,但是本质上都是相似的,需要比较单独个体的相似性。", DetectMode.PRECISION));
        System.out.println(LanguageDistinguish.getLanguageByString("BTS (방탄소년단) 'Save ME' Official MV\n", DetectMode.PRECISION));

    }
}

数据清洗:排除隐藏字符

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.base.BCConvert;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author yanfengzhang
 * @description 排除隐藏字符
 * @date 2022/4/17  22:20
 */
@Component
@SensitiveClean(cleanCode = SensitiveCons.Clean.EXCULDE_HIDDEN)
public class ExcludeHiddenCharacters implements ContextHandler<ContentInfoContext, ContentCleanResContext> {

    private LoadingCache<String, Set<Integer>> hiddenCharactersCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(10, TimeUnit.MINUTES)
            /*构建缓存*/
            .build(new CacheLoader<String, Set<Integer>>() {
                /*初始化加载数据的缓存信息*/
                @Override
                public Set<Integer> load(String specialSymbols) throws Exception {
                    return getHiddenCharacters();
                }
            });

    /**
     * 排除隐藏字符,避免用户采用一些特殊的隐藏字符来逃避敏感词命中
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词词库校验)
     */
    @Override
    public ContentCleanResContext handle(ContentInfoContext context) {
        try {
            String HIDDEREN = "hidden";

            Set<Integer> emojis = hiddenCharactersCache.get(HIDDEREN);
            /*其他链路中清洗后的词*/
            char[] valueChars = context.getCleanContent().toCharArray();
            StringBuilder cleanContent = new StringBuilder();
            for (char valueChar : valueChars) {
                int temp = BCConvert.charConvert(valueChar);
                if (emojis.contains(temp)) {
                    /*过滤隐藏字符*/
                    continue;
                }
                cleanContent.append(valueChar);
            }

            /*将本次清洗数据载入待继续清洗实体中*/
            context.setCleanContent(cleanContent.toString());
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(true)
                    .content(context.getContent())
                    .cleanContent(cleanContent.toString())
                    .contentAttr(context.getContentAttr())
                    .build();
        } catch (Exception e) {
            /*设置处理结果*/
            return ContentCleanResContext.builder()
                    .isCleanDone(false)
                    .content(context.getContent())
                    /*记录下中间态数据*/
                    .cleanContent(context.getCleanContent())
                    .contentAttr(context.getContentAttr())
                    .reason("数据清洗异常:排除隐藏字符失败")
                    .build();
        }
    }

    /**
     * 本处举例,实际应该放置到指定的配置页面来实时生效
     *
     * @return 相关特殊符号集合
     */
    private static Set<Integer> getHiddenCharacters() {
        List<String> specialSymbolsRes = Lists.newArrayList();
        String speciSymbols = "\u2069\u202A\u202B\u202C\u202D\u202E\u2060\u2061\u2062\u2063\u2064\uE7C7\uE7C8" +
                "͏\u2069\u2060\u2061\u2062\u2063\u2064\u2067\u206C\u206B";
        if (StringUtils.isNotBlank(speciSymbols)) {
            for (int index = 0; index < speciSymbols.length(); index++) {
                specialSymbolsRes.add(String.valueOf(speciSymbols.charAt(index)));
            }
        }

        Set<Integer> specialSymbolsSet = new HashSet<>();
        if (!CollectionUtils.isEmpty(specialSymbolsRes)) {
            char[] chs;
            for (String curr : specialSymbolsRes) {
                chs = curr.toCharArray();
                for (char c : chs) {
                    specialSymbolsSet.add(BCConvert.charConvert(c));
                }
            }
        }
        return specialSymbolsSet;
    }

    public static void main(String[] args) {
        String content = "你\u2069\u202A好\u2062\u2063\u2064\uE7C7\uE7C8,张\u2067\u206C彦\u2061\u2062\u2063峰!";
        char[] valueChars = content.toCharArray();
        Set<Integer> specialSymbols = getHiddenCharacters();
        StringBuilder cleanContent = new StringBuilder();
        for (char valueChar : valueChars) {
            int temp = BCConvert.charConvert(valueChar);
            if (specialSymbols.contains(temp)) {
                /*过滤特殊字符*/
                continue;
            }
            cleanContent.append(valueChar);
        }
        System.out.println(cleanContent.toString());
    }
}

敏感词校验相关业务代码

相关能力说明:将清洗之后的用户文本和相关涉及到的相关词库进行比对,并保存整合对应命中的敏感词内容和对应的归属等。

敏感词校验:企业合规管控校验

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.validate;

import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveValidate;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.BizType;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentAttr;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 敏感词校验:企业合规管控校验
 * @date 2022/4/5  18:15
 */
@Component
@SensitiveValidate(validateCode = SensitiveCons.Validate.COMPLIANCE)
public class SensitiveComplianceValidator implements ContextHandler<ContentCleanResContext, SensitveHitContext> {

    /**
     * 敏感词分析处理:根据相关企业业务配置进行处理
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词生效处理)
     */
    @Override
    public SensitveHitContext handle(ContentCleanResContext context) {
        /*如果命中敏感词,则显示命中,且终止链路传递*/
        return SensitveHitContext.builder()
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .contentAttr(context.getContentAttr())
                .hitWords(corporateCompliance(context)).build();
    }

    /**
     * 企业合规管控词库   实际应与企业对应词库进行匹配
     * <p>
     * 模拟几个合规词
     * 1 假定政治人物类词汇:张彦峰
     * 2 假定侵权类词汇:肯德基
     *
     * @param content 文本内容
     * @return 校验结果
     */
    private List<SensitiveWord> corporateCompliance(ContentCleanResContext content) {
        /*该词库只对某些业务进行校验,此处应设置成配置选项*/
        List<BizType> bizTypes = Lists.newArrayList(BizType.E_COMMERCE, BizType.LOGISTICS, BizType.ENTERTAINMENT, BizType.TAKEAWAY, BizType.FALASH_SALE, BizType.MREDICINE);
        ContentAttr contentAttr = content.getContentAttr();
        if (!bizTypes.contains(BizType.getEnumById(contentAttr.getBizType()))) {
            return Lists.newArrayList();
        }
        List<SensitiveWord> sensitiveWords = Lists.newArrayList();
        if (content.getCleanContent().contains("张彦峰")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("张彦峰")
                    .sensitiveId(23L)
                    .kind(4).build());
        }
        if (content.getCleanContent().contains("肯德基")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("肯德基")
                    .sensitiveId(24L)
                    .kind(4).build());
        }
        return sensitiveWords;
    }
}

敏感词校验:正则校验处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.validate;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.base.Base64;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveValidate;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.RegularTypeEnum;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.WordRegular;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author yanfengzhang
 * @description 敏感词校验:正则校验处理
 * @date 2022/4/5  22:09
 */
@Component
@Log4j2
@SensitiveValidate(validateCode = SensitiveCons.Validate.REGULAR)
public class SensitiveRegularValidator implements ContextHandler<ContentCleanResContext, SensitveHitContext> {

    private LoadingCache<String, Map<WordRegular, Pattern[]>> wordRegularCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(10, TimeUnit.MINUTES)
            /*构建缓存*/
            .build(new CacheLoader<String, Map<WordRegular, Pattern[]>>() {
                /*初始化加载数据的缓存信息*/
                @Override
                public Map<WordRegular, Pattern[]> load(String wordRegularInfo) throws Exception {
                    return getwordRegularCache();
                }
            });

    /**
     * 敏感词分析处理:根据相关业务配置进行相关正则校验处理
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词生效处理)
     */
    @Override
    public SensitveHitContext handle(ContentCleanResContext context) {
        return SensitveHitContext.builder()
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .contentAttr(context.getContentAttr())
                .hitWords(getSensitiveRegularValidator(context.getCleanContent())).build();
    }

    private Map<WordRegular, Pattern[]> getwordRegularCache() {
        /*从指定词正则库中拉取配置,在此处放本地缓存或redis,这里只进行模拟*/
        List<WordRegular> words = Lists.newArrayList();
        words.add(WordRegular.builder().id(11L).type(RegularTypeEnum.AND_REGULAR.getCode()).words(constructWords()).build());

        Map<WordRegular, Pattern[]> wordRegularPattern = Maps.newHashMap();
        String[] wordstr;
        Pattern p1;
        Pattern p2;
        for (WordRegular wordRegular : words) {
            switch (RegularTypeEnum.getByCode(wordRegular.getType())) {
                case SIAMPLE_REGULAR:
                    Pattern p = Pattern.compile(Base64.decodeToString(wordRegular.getWords()), Pattern.CASE_INSENSITIVE);
                    wordRegularPattern.put(wordRegular, new Pattern[]{p});
                    break;
                case AND_REGULAR:
                case N_AND_REGULAR:
                    wordstr = wordRegular.getWords().split(",");
                    if (2 != wordstr.length) {
                        continue;
                    }
                    p1 = Pattern.compile(Base64.decodeToString(wordstr[0]), Pattern.CASE_INSENSITIVE);
                    p2 = Pattern.compile(Base64.decodeToString(wordstr[1]), Pattern.CASE_INSENSITIVE);
                    wordRegularPattern.put(wordRegular, new Pattern[]{p1, p2});
                    break;
                default:
                    break;
            }
        }
        return wordRegularPattern;
    }

    private List<SensitiveWord> getSensitiveRegularValidator(String content) {
        List<SensitiveWord> result = Lists.newArrayList();
        if (StringUtils.isEmpty(content)) {
            return result;
        }
        final String CHAR_PATTERN = "[\\s\\d\\pP+~$`^=|<>~`$^+=|<>¥× ]";
        content = content.replaceAll(CHAR_PATTERN, "");
        boolean illegal = false;
        Matcher matcher1 = null;
        Matcher matcher2 = null;

        Map<WordRegular, Pattern[]> getwordRegularCache = null;
        try {
            getwordRegularCache = wordRegularCache.get("wordRegularInfo");
        } catch (ExecutionException e) {
            getwordRegularCache = Maps.newHashMap();
        }
        for (Map.Entry<WordRegular, Pattern[]> e : getwordRegularCache.entrySet()) {
            switch (Objects.requireNonNull(RegularTypeEnum.getByCode(e.getKey().getType()))) {
                case SIAMPLE_REGULAR:
                    Matcher matcher = e.getValue()[0].matcher(content);
                    illegal = matcher.find();
                    /*已经命中正则的前提下,需要同时满足业务身份的要求*/
                    if (illegal) {
                        result.add(SensitiveWord.builder().sensitive(matcher.group()).sensitiveId(e.getKey().getId()).kind(5).build());
                    }
                    break;
                case AND_REGULAR:
                    matcher1 = e.getValue()[0].matcher(content);
                    matcher2 = e.getValue()[1].matcher(content);
                    illegal = matcher1.find() && matcher2.find();
                    if (illegal) {
                        result.add(SensitiveWord.builder().sensitive(matcher1.group()).sensitiveId(e.getKey().getId()).kind(5).build());
                        result.add(SensitiveWord.builder().sensitive(matcher2.group()).sensitiveId(e.getKey().getId()).kind(5).build());
                    }
                    break;
                case N_AND_REGULAR:
                    matcher1 = e.getValue()[0].matcher(content);
                    matcher2 = e.getValue()[1].matcher(content);
                    illegal = matcher1.find() && !matcher2.find();
                    if (illegal) {
                        result.add(SensitiveWord.builder().sensitive(matcher1.group()).sensitiveId(e.getKey().getId()).kind(5).build());
                    }
                    break;
                default:
                    break;
            }
        }
        return result;
    }

    private String constructWords() {
        String[] wordArr = new String[]{"酒精", "南京"};
        return StringUtils.join(constructWordsEncode(wordArr), ",");
    }

    private List<String> constructWordsEncode(String[] wordArray) {
        /*其他存在存多词组合的内容,进行编码转存*/
        List<String> wordsList = Lists.newArrayList();
        for (String item : wordArray) {
            try {
                wordsList.add(new String(new org.apache.commons.codec.binary.Base64().encode(item.getBytes())));
            } catch (Exception e) {
                wordsList.add(item);
            }
        }
        return wordsList;
    }
}

敏感词校验:相关词库处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.validate;

import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveValidate;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

import java.util.Arrays;
import java.util.List;

/**
 * @author yanfengzhang
 * @description 敏感词校验:相关词库处理
 * @date 2022/4/5  22:31
 */
@Component
@SensitiveValidate(validateCode = SensitiveCons.Validate.THESAURUS)
public class SensitiveThesaurusValidator implements ContextHandler<ContentCleanResContext, SensitveHitContext> {

    /**
     * 敏感词分析处理:根据相关业务配置进行相关词库校验匹配
     *
     * @param context 处理时的上下文数据
     * @return 处理结果(代进入敏感词生效处理)
     */
    @Override
    public SensitveHitContext handle(ContentCleanResContext context) {
        Integer bizType = context.getContentAttr().getBizType();
        /*根据业务方接入来源获取对应的业务方词库校验要求*/
        List<Integer> validatorModes = getBizSensitiveModes(bizType);
        if (CollectionUtils.isEmpty(validatorModes)) {
            /*没有配置则直接默认走企业词库校验*/
            validatorModes.add(1);
        }

        List<SensitiveWord> hitWords = Lists.newArrayList();
        for (Integer validatorMode : validatorModes) {
            if (validatorMode.equals(1)) {
                /*企业词库校验*/
                hitWords.addAll(companySensitive(context.getContent()));
            }
            if (validatorMode.equals(2)) {
                /*企业词库校验*/
                hitWords.addAll(departmentSensitive(context.getContent()));
            }
            if (validatorMode.equals(3)) {
                /*企业词库校验*/
                hitWords.addAll(otherSensitive(context.getContent()));
            }
        }

        return SensitveHitContext.builder()
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .contentAttr(context.getContentAttr())
                .hitWords(hitWords).build();
    }

    /**
     * 模拟假设配置的都是所有词库都跑一遍
     *
     * @return 业务方词库配置
     */
    private List<Integer> getBizSensitiveModes(Integer bizType) {
        /**
         * 1-企业词库校验;2-部门词库校验;3-其他词库校验
         */
        return Arrays.asList(1, 2, 3);
    }

    /**
     * 企业敏感词库(编号1)匹配,实际为词库校验,此处只做模拟
     *
     * @param content 用户内容
     * @return 匹配结果
     */
    private List<SensitiveWord> companySensitive(String content) {
        List<SensitiveWord> sensitiveWords = Lists.newArrayList();
        if (content.contains("外卖")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("外卖")
                    .sensitiveId(1L)
                    .kind(1).build());
        }
        if (content.contains("美团")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("美团")
                    .sensitiveId(86L)
                    .kind(1).build());
        }
        return sensitiveWords;
    }

    /**
     * 部门敏感词库(编号2)匹配,实际为不同部门的词库校验,此处只做模拟
     *
     * @param content 用户内容
     * @return 匹配结果
     */
    private List<SensitiveWord> departmentSensitive(String content) {
        List<SensitiveWord> sensitiveWords = Lists.newArrayList();
        if (content.contains("腾讯")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("腾讯")
                    .sensitiveId(23L)
                    .kind(2).build());
        }
        if (content.contains("阿里")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("阿里")
                    .sensitiveId(36L)
                    .kind(2).build());
        }
        return sensitiveWords;
    }

    /**
     * 其他敏感词库(编号3)匹配,实际为不同第三方词库校验,此处只做模拟
     *
     * @param content 用户内容
     * @return 匹配结果
     */
    private List<SensitiveWord> otherSensitive(String content) {
        List<SensitiveWord> sensitiveWords = Lists.newArrayList();
        if (content.contains("禁药")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("禁药")
                    .sensitiveId(28L)
                    .kind(3).build());
        }
        if (content.contains("毒品")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("毒品")
                    .sensitiveId(376L)
                    .kind(3).build());
        }
        return sensitiveWords;
    }
}

敏感词校验:手机号身份证号处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.validate;

import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveValidate;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

import java.util.List;

/**
 * @author yanfengzhang
 * @description 敏感词校验:手机号身份证号处理
 * @date 2022/4/5  22:19
 */
@Component
@SensitiveValidate(validateCode = SensitiveCons.Validate.PRIVACY)
public class SensitivePrivacyValidator implements ContextHandler<ContentCleanResContext, SensitveHitContext> {

    /**
     * 敏感词分析处理:手机号身份证号处理
     *
     * @param context 处理时的上下文数据
     * @return
     */
    @Override
    public SensitveHitContext handle(ContentCleanResContext context) {
        return SensitveHitContext.builder()
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .contentAttr(context.getContentAttr())
                .hitWords(getPrivacy(context.getCleanContent())).build();
    }

    private List<SensitiveWord> getPrivacy(String content) {
        /*模拟*/
        List<SensitiveWord> sensitiveWords = Lists.newArrayList();
        if (content.contains("18252066688")) {
            sensitiveWords.add(SensitiveWord.builder()
                    .sensitive("18252066688")
                    .sensitiveId(453L)
                    .kind(18).build());
        }
        return sensitiveWords;
    }
}

敏感词生效相关业务代码

相关能力说明:词库命中的相关敏感词可能生效的业务范围较大,在生效处理中需要筛选和确定是否真实生效。

敏感词生效:合规管控处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.effect;

import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveEffect;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveEffectiveContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

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

/**
 * @author yanfengzhang
 * @description 敏感词生效:合规管控处理
 * @date 2022/4/17  22:17
 */
@Component
@SensitiveEffect(effectCode = SensitiveCons.Effect.COMPLIANCE_CONTROL)
public class ComplianceControlProcess implements ContextHandler<SensitveHitContext, SensitveEffectiveContext> {

    /**
     * 实际合规管控处理是否可以放行
     *
     * @param context 处理时的上下文数据
     * @return 最终处理结果
     */
    @Override
    public SensitveEffectiveContext handle(SensitveHitContext context) {
        List<SensitiveWord> hitWords = context.getHitWords();
        /*如果未命中任何敏感词则直接返回*/
        if (CollectionUtils.isEmpty(hitWords)) {
            return SensitveEffectiveContext.builder()
                    /*没有任何词生效*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList()).build();
        }

        /*此处只为模拟,根据当前命中的敏感词信息查询是否存在对应的合规管控处理策略,如果存在对放行的敏感词进行标志*/
        List<SensitiveWord> complianceControlSensitiveWord = getComplianceControlSensitiveWord(context.getHitWords());
        if (CollectionUtils.isEmpty(complianceControlSensitiveWord)) {
            /*没有需要放行的词,则当前词就是命中的,可直接返回*/
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(true)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(hitWords)
                    .complianceIgnoreWords(Lists.newArrayList()).build();
        }

        /*整合合规管控中放行的词*/
        List<Long> complianceIgnoreWordIds = complianceControlSensitiveWord.stream().map(SensitiveWord::getSensitiveId).collect(Collectors.toList());
        /*去除放行词后命中的词*/
        List<SensitiveWord> finalHitWords = hitWords.stream().filter(sensitiveWord -> !complianceIgnoreWordIds.contains(sensitiveWord.getSensitiveId())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(finalHitWords)) {
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList())
                    .complianceIgnoreWords(complianceControlSensitiveWord).build();
        }

        /*此时链路可能还会记继续,保持对应内容的传递*/
        context.setHitWords(finalHitWords);
        /*返回当前命中的内容*/
        return SensitveEffectiveContext.builder()
                /*已经有生效的词直接返回*/
                .isHit(true)
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .hitWords(finalHitWords)
                .complianceIgnoreWords(complianceControlSensitiveWord).build();
    }

    private List<SensitiveWord> getComplianceControlSensitiveWord(List<SensitiveWord> hitWords) {
        if (CollectionUtils.isEmpty(hitWords)) {
            return Lists.newArrayList();
        }
        /*合规处某些词管控中有新的策略,如果是改词的话直接放行*/
        List<SensitiveWord> complianceControlSensitiveWord = Arrays.asList(SensitiveWord.builder().sensitive("外卖").build(),
                SensitiveWord.builder().sensitive("闪购").build());
        List<String> complianceControlSensitiveWordInfo = complianceControlSensitiveWord.stream().map(SensitiveWord::getSensitive).collect(Collectors.toList());
        return hitWords.stream().filter(sensitiveWord -> complianceControlSensitiveWordInfo.contains(sensitiveWord.getSensitive())).collect(Collectors.toList());
    }
}

敏感词生效:生效规则处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.effect;

import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveEffect;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentAttr;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveEffectiveContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

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

/**
 * @author yanfengzhang
 * @description 敏感词生效:生效规则处理
 * @date 2022/4/17  22:14
 */
@Component
@SensitiveEffect(effectCode = SensitiveCons.Effect.RULE)
public class EffectiveRuleProcess implements ContextHandler<SensitveHitContext, SensitveEffectiveContext> {

    /**
     * 实际生效规则处理是否可以放行
     *
     * @param context 处理时的上下文数据
     * @return
     */
    @Override
    public SensitveEffectiveContext handle(SensitveHitContext context) {
        List<SensitiveWord> hitWords = context.getHitWords();
        /*如果未命中任何敏感词则直接返回*/
        if (CollectionUtils.isEmpty(hitWords)) {
            return SensitveEffectiveContext.builder()
                    /*没有任何词生效*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList()).build();
        }

        /*此处只为模拟,根据当前命中的敏感词信息查询是否存在因规则而舍弃的,如果存在对放行的敏感词进行标志*/
        List<SensitiveWord> ignoreSensitiveWord = getIgnoreSensitiveWord(context.getHitWords(), context.getContentAttr());
        if (CollectionUtils.isEmpty(ignoreSensitiveWord)) {
            /*没有需要放行的词,则当前词就是命中的,可直接返回*/
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(true)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(hitWords)
                    .ruleIgnoreWords(Lists.newArrayList()).build();
        }

        /*整合敏感词配置规则放行的词*/
        List<Long> ignoreSensitiveWordIds = ignoreSensitiveWord.stream().map(SensitiveWord::getSensitiveId).collect(Collectors.toList());
        /*去除放行词后命中的词*/
        List<SensitiveWord> finalHitWords = hitWords.stream().filter(sensitiveWord -> !ignoreSensitiveWordIds.contains(sensitiveWord.getSensitiveId())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(finalHitWords)) {
            /*此时链路可能还会记继续,保持对应内容的传递*/
            context.setHitWords(Lists.newArrayList());
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList())
                    .ruleIgnoreWords(ignoreSensitiveWord).build();
        }

        /*此时链路可能还会记继续,保持对应内容的传递*/
        context.setHitWords(finalHitWords);
        /*返回当前命中的内容*/
        return SensitveEffectiveContext.builder()
                /*已经有生效的词直接返回*/
                .isHit(true)
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .hitWords(finalHitWords).ruleIgnoreWords(ignoreSensitiveWord).build();
    }

    private List<SensitiveWord> getIgnoreSensitiveWord(List<SensitiveWord> hitWords, ContentAttr contentAttr) {
        if (CollectionUtils.isEmpty(hitWords)) {
            return Lists.newArrayList();
        }

        return hitWords.stream().filter(sensitiveWord -> ignoreByrule(sensitiveWord, contentAttr)).collect(Collectors.toList());

    }

    private boolean ignoreByrule(SensitiveWord sensitiveWord, ContentAttr contentAttr) {
        /*1.根据敏感词配置内容获取对应敏感词实际生效配置,此处做模拟*/
        if (contentAttr.getCityCode().equals(110010)) {
            /*改词只对该地区生效,不忽略*/
            return false;
        }
        return true;
    }
}

敏感词生效:白名单处理

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.effect;

import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.ContextHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.constants.SensitiveCons;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveEffect;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveEffectiveContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;

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

/**
 * @author yanfengzhang
 * @description 敏感词生效:白名单处理
 * @date 2022/4/17  22:20
 */
@Component
@SensitiveEffect(effectCode = SensitiveCons.Effect.WHITE)
public class WhitelistProcess implements ContextHandler<SensitveHitContext, SensitveEffectiveContext> {

    /**
     * 实际白名单处理是否可以放行
     *
     * @param context 处理时的上下文数据
     * @return 最终处理结果
     */
    @Override
    public SensitveEffectiveContext handle(SensitveHitContext context) {
        List<SensitiveWord> hitWords = context.getHitWords();
        /*如果未命中任何敏感词则直接返回*/
        if (CollectionUtils.isEmpty(hitWords)) {
            return SensitveEffectiveContext.builder()
                    /*没有任何词生效*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList()).build();
        }

        /*此处只为模拟,根据当前命中的敏感词信息查询是否存在加白的词,如果存在对放行的敏感词进行标志*/
        List<SensitiveWord> ignoreSensitiveWord = getIgnoreSensitiveByWhite(context.getHitWords());
        if (CollectionUtils.isEmpty(ignoreSensitiveWord)) {
            /*没有需要放行的词,则当前词就是命中的,可直接返回*/
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(true)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(hitWords)
                    .whitedWords(Lists.newArrayList()).build();
        }

        /*整合敏感词配置规则放行的词*/
        List<Long> ignoreSensitiveWordIds = ignoreSensitiveWord.stream().map(SensitiveWord::getSensitiveId).collect(Collectors.toList());
        /*去除放行词后命中的词*/
        List<SensitiveWord> finalHitWords = hitWords.stream().filter(sensitiveWord -> !ignoreSensitiveWordIds.contains(sensitiveWord.getSensitiveId())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(finalHitWords)) {
            return SensitveEffectiveContext.builder()
                    /*已经有生效的词直接返回*/
                    .isHit(false)
                    .content(context.getContent())
                    .cleanContent(context.getCleanContent())
                    .hitWords(Lists.newArrayList())
                    .whitedWords(ignoreSensitiveWord).build();
        }

        /*此时链路可能还会记继续,保持对应内容的传递*/
        context.setHitWords(finalHitWords);
        /*返回当前命中的内容*/
        return SensitveEffectiveContext.builder()
                /*已经有生效的词直接返回*/
                .isHit(true)
                .content(context.getContent())
                .cleanContent(context.getCleanContent())
                .hitWords(finalHitWords)
                .whitedWords(ignoreSensitiveWord).build();
    }

    private List<SensitiveWord> getIgnoreSensitiveByWhite(List<SensitiveWord> hitWords) {
        if (CollectionUtils.isEmpty(hitWords)) {
            return Lists.newArrayList();
        }

        return hitWords.stream().filter(sensitiveWord -> ignoreByWhite(sensitiveWord)).collect(Collectors.toList());

    }

    private boolean ignoreByWhite(SensitiveWord sensitiveWord) {
        /*1.根据敏感词配置内容获取对应敏感词实际白名单生效配置,此处做模拟*/
        if (sensitiveWord.getSensitiveId() == 36) {
            return true;
        }
        return false;
    }
}

5.敏感词管道路由表整合配置

package org.zyf.javabasic.designpatterns.responsibility.pipeline;

import com.google.common.collect.Maps;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.SensitivePipelineExecutor;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveClean;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveEffect;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.enums.SensitiveValidate;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Set;

/**
 * @author yanfengzhang
 * @description 管道路由表整合配置
 * @date 2022/4/17  22:24
 */
public class PipelineRouteConfig {
    private static Map<Integer, Class> contentCleanProcessor = Maps.newHashMap();
    private static Map<Integer, Class> sensitiveValidateProcessor = Maps.newHashMap();
    private static Map<Integer, Class> sensitiveEffectProcessor = Maps.newHashMap();
    private static final Logger LOGGER = LoggerFactory.getLogger(SensitivePipelineExecutor.class);

    static {
        /*1.数据清洗能力集合*/
        Reflections contentCleanRef = new Reflections("org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.dataclean");
        Set<Class<?>> contentCleanClassSet = contentCleanRef.getTypesAnnotatedWith(SensitiveClean.class);
        for (Class<?> cl : contentCleanClassSet) {
            Annotation[] annotations = cl.getAnnotations();
            for (Annotation a : annotations) {
                if (a instanceof SensitiveClean) {
                    SensitiveClean sensitiveClean = (SensitiveClean) a;
                    contentCleanProcessor.put(sensitiveClean.cleanCode(), cl);
                }
            }
        }
        /*2.敏感词校验能力集合*/
        Reflections sensitiveValidateRef = new Reflections("org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.validate");
        Set<Class<?>> sensitiveValidateClassSet = sensitiveValidateRef.getTypesAnnotatedWith(SensitiveValidate.class);
        for (Class<?> cl : sensitiveValidateClassSet) {
            Annotation[] annotations = cl.getAnnotations();
            for (Annotation a : annotations) {
                if (a instanceof SensitiveValidate) {
                    SensitiveValidate sensitiveValidate = (SensitiveValidate) a;
                    sensitiveValidateProcessor.put(sensitiveValidate.validateCode(), cl);
                }
            }
        }
        /*3.敏感词生效能力集合*/
        Reflections sensitiveEffectRef = new Reflections("org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.effect");
        Set<Class<?>> sensitiveEffectClassSet = sensitiveEffectRef.getTypesAnnotatedWith(SensitiveEffect.class);
        for (Class<?> cl : sensitiveEffectClassSet) {
            Annotation[] annotations = cl.getAnnotations();
            for (Annotation a : annotations) {
                if (a instanceof SensitiveEffect) {
                    SensitiveEffect sensitiveEffect = (SensitiveEffect) a;
                    sensitiveEffectProcessor.put(sensitiveEffect.effectCode(), cl);
                }
            }
        }
    }

    public static ContextHandler getInstance(int code) {
        if (contentCleanProcessor.containsKey(code)) {
            try {
                Constructor constructor = contentCleanProcessor.get(code).getConstructor();
                return (ContextHandler) constructor.newInstance();
            } catch (Exception ex) {
                LOGGER.error("ContextHandler contentCleanProcessor getInstance error, exception:", ex);
            }
        }
        if (sensitiveValidateProcessor.containsKey(code)) {
            try {
                Constructor constructor = sensitiveValidateProcessor.get(code).getConstructor();
                return (ContextHandler) constructor.newInstance();
            } catch (Exception ex) {
                LOGGER.error("ContextHandler sensitiveValidateProcessor getInstance error, exception:", ex);
            }
        }
        if (sensitiveEffectProcessor.containsKey(code)) {
            try {
                Constructor constructor = sensitiveEffectProcessor.get(code).getConstructor();
                return (ContextHandler) constructor.newInstance();
            } catch (Exception ex) {
                LOGGER.error("ContextHandler sensitiveEffectProcessor getInstance error, exception:", ex);
            }
        }
        return null;
    }

    public static Map<Integer, Class> getContentCleanProcessor() {
        return contentCleanProcessor;
    }

    public static Map<Integer, Class> getSensitiveValidateProcessor() {
        return sensitiveValidateProcessor;
    }

    public static Map<Integer, Class> getSensitiveEffectProcessor() {
        return sensitiveEffectProcessor;
    }
}

6.敏感词管道执行器

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination;

import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.PipelineRouteConfig;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentCleanResContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitiveWord;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveEffectiveContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.SensitveHitContext;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.common.CommonHeadHandler;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.common.CommonTailHandler;

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

/**
 * @author yanfengzhang
 * @description 敏感词管道执行器
 * @date 2022/12/21  16:49
 */
@Component
@Log4j2
public class SensitivePipelineExecutor {
    @Autowired
    private CommonHeadHandler commonHeadHandler;
    @Autowired
    private CommonTailHandler commonTailHandler;

    public String getSensitiveDealRes(ContentInfoContext contentInfoContext) {
        StringBuilder sensitiveDealRes = new StringBuilder();
        sensitiveDealRes.append("用户文本【").append(contentInfoContext.getContent()).append("】");
        /*1.检查请求内容的合法性*/
        /*2.获取对应请求的文本清洗结果*/
        ContentCleanResContext contentCleanResContext = getContentCleanRes(contentInfoContext);
        if (!contentCleanResContext.isCleanDone()) {
            sensitiveDealRes.append("内部清洗处理异常:").append("具体原因为【").append(contentCleanResContext.getReason()).append("】");
            return sensitiveDealRes.toString();
        }

        /*3.整合相关的校验能力*/
        SensitveHitContext sensitveHitContext = getSensitveHitRes(contentCleanResContext);
        if (!sensitveHitContext.getHasHit()) {
            sensitiveDealRes.append("无任何敏感词命中");
            return sensitiveDealRes.toString();
        }
        /*4.整合最终的命中结果*/
        SensitveEffectiveContext sensitveEffectiveContext = getSensitveHitRes(sensitveHitContext);
        if (sensitveEffectiveContext.getIsHit()) {
            sensitiveDealRes.append("命中相关敏感词:").append("【").append(sensitveEffectiveContext.getHitWords().stream().map(SensitiveWord::getSensitive).collect(Collectors.toList())).append("】");
            return sensitiveDealRes.toString();
        }

        sensitiveDealRes.append("无任何敏感词命中");
        return sensitiveDealRes.toString();
    }

    /**
     * 根据用户文本获取对应数据清洗结果内容
     *
     * @param contentInfoContext 用户文本内容
     * @return 数据清洗结果
     */
    public ContentCleanResContext getContentCleanRes(ContentInfoContext contentInfoContext) {
        /*【通用头处理器】处理*/
        commonHeadHandler.handle(contentInfoContext);
        ContentCleanResContext contentCleanResContext = null;
        for (Integer cleanCode : PipelineRouteConfig.getContentCleanProcessor().keySet()) {
            contentCleanResContext = (ContentCleanResContext) PipelineRouteConfig.getInstance(cleanCode).handle(contentInfoContext);
            if (Objects.isNull(contentCleanResContext)) {
                continue;
            }
            if (!contentCleanResContext.isCleanDone()) {
                /*【通用尾处理器】处理*/
                commonTailHandler.handle(contentInfoContext);
                return contentCleanResContext;
            }
        }
        /*【通用尾处理器】处理*/
        commonTailHandler.handle(contentInfoContext);
        return contentCleanResContext;
    }

    /**
     * 根据用户数据清洗结果获取相关词库命中情况
     *
     * @param contentCleanResContext 数据清洗结果
     * @return 词库命中情况
     */
    public SensitveHitContext getSensitveHitRes(ContentCleanResContext contentCleanResContext) {
        /*【通用头处理器】处理*/
        commonHeadHandler.handle(contentCleanResContext);
        SensitveHitContext sensitveHitContext = null;
        List<SensitiveWord> hitWords = Lists.newArrayList();
        for (Integer validateCode : PipelineRouteConfig.getSensitiveValidateProcessor().keySet()) {
            sensitveHitContext = (SensitveHitContext) PipelineRouteConfig.getInstance(validateCode).handle(contentCleanResContext);
            if (Objects.isNull(sensitveHitContext)) {
                continue;
            }
            hitWords.addAll(sensitveHitContext.getHitWords());
        }
        /*【通用尾处理器】处理*/
        commonTailHandler.handle(contentCleanResContext);

        if (Objects.isNull(sensitveHitContext)) {
            return SensitveHitContext.builder().hasHit(false).build();
        }

        /*根据统计词库信息决定最后的词库结果*/
        if (CollectionUtils.isNotEmpty(hitWords)) {
            sensitveHitContext.setHasHit(true);
            sensitveHitContext.setHitWords(hitWords);
        }
        return sensitveHitContext;
    }

    /**
     * 获取最终返回的命中结果
     *
     * @param sensitveHitContext 词库命中结果
     * @return 经过其他分析后最终命中结果
     */
    public SensitveEffectiveContext getSensitveHitRes(SensitveHitContext sensitveHitContext) {
        /*【通用头处理器】处理*/
        commonHeadHandler.handle(sensitveHitContext);

        SensitveEffectiveContext sensitveEffectiveContext = null;
        List<SensitiveWord> whitedWords = Lists.newArrayList();
        List<SensitiveWord> complianceIgnoreWords = Lists.newArrayList();
        List<SensitiveWord> ruleIgnoreWords = Lists.newArrayList();
        for (Integer effectCode : PipelineRouteConfig.getSensitiveEffectProcessor().keySet()) {
            sensitveEffectiveContext = (SensitveEffectiveContext) PipelineRouteConfig.getInstance(effectCode).handle(sensitveHitContext);
            if (Objects.isNull(sensitveHitContext)) {
                continue;
            }
            if (CollectionUtils.isNotEmpty(sensitveEffectiveContext.getComplianceIgnoreWords())) {
                complianceIgnoreWords.addAll(sensitveEffectiveContext.getComplianceIgnoreWords());
            }
            if (CollectionUtils.isNotEmpty(sensitveEffectiveContext.getRuleIgnoreWords())) {
                ruleIgnoreWords.addAll(sensitveEffectiveContext.getRuleIgnoreWords());
            }
            if (CollectionUtils.isNotEmpty(sensitveEffectiveContext.getWhitedWords())) {
                whitedWords.addAll(sensitveEffectiveContext.getWhitedWords());
            }
            /*如果已经没有命中的词了则直接返回即可*/
            if (!sensitveEffectiveContext.getIsHit()) {
                sensitveEffectiveContext.setWhitedWords(whitedWords);
                sensitveEffectiveContext.setComplianceIgnoreWords(complianceIgnoreWords);
                sensitveEffectiveContext.setRuleIgnoreWords(ruleIgnoreWords);
                return sensitveEffectiveContext;
            }
        }
        /*【通用尾处理器】处理*/
        commonTailHandler.handle(sensitveHitContext);

        sensitveEffectiveContext.setWhitedWords(whitedWords);
        sensitveEffectiveContext.setComplianceIgnoreWords(complianceIgnoreWords);
        sensitveEffectiveContext.setRuleIgnoreWords(ruleIgnoreWords);
        return sensitveEffectiveContext;
    }

}

7.测试

package org.zyf.javabasic.designpatterns.responsibility.pipeline.combination;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.zyf.javabasic.ZYFApplication;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.BizType;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentAttr;
import org.zyf.javabasic.designpatterns.responsibility.pipeline.combination.model.ContentInfoContext;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/21  18:12
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SensitivePipelineExecutorTest {

    @Autowired
    private SensitivePipelineExecutor sensitivePipelineExecutor;

    @Test
    public void test() {
        sensitivePipelineExecutor.getContentCleanRes(
                ContentInfoContext.builder()
                        .content("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                        .cleanContent("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                        .contentAttr(ContentAttr.builder().build()).build());
    }

    /**
     * =====敏感词词库模拟设置=====
     * 企业合规管控校验 4  23 张彦峰
     * 4  24 肯德基
     * =======相关词库处理=======
     * 企业敏感词库(编号1)          1  1  外卖
     * 1  86  美团
     * 部门敏感词库(编号2)匹配       2  23  腾讯
     * 2  36  阿里
     * 其他敏感词库(编号3)          3  28  禁药
     * 3  376  毒品
     * =======正则校验词库处理=======
     * 与正则                       5  11 "酒精", "南京"
     * ======手机号身份证号处理======
     * 模拟电话                     18 453 18252066688
     * <p>
     * 生效模拟
     * 合规管控处理     外卖 + 闪购
     * 生效规则处理     只对改地区生效110010
     * 白名单处理       比方针对敏感词36加白
     */
    @Test
    public void testSensitivePipelineExecutor() {
        log.info(sensitivePipelineExecutor.getSensitiveDealRes(ContentInfoContext.builder()
                .content("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                .cleanContent("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                .contentAttr(ContentAttr.builder().bizType(BizType.E_COMMERCE.getType()).cityCode(110010).build()).build()));
        log.info(sensitivePipelineExecutor.getSensitiveDealRes(ContentInfoContext.builder()
                .content("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                .cleanContent("中國张彦峰㎵㎶㎷㎸㎹㎺外卖⏳⌚⏰")
                .contentAttr(ContentAttr.builder().bizType(BizType.E_COMMERCE.getType()).cityCode(115510).build()).build()));
    }
}

8.其他备注说明

实际也可以针对通用的业务责任链进行整合,整合思路和以上对应相类似,定义整合业务枚举

package org.zyf.javabasic.designpatterns.responsibility.pipeline.distribution.enums;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yanfengzhang
 * @description
 * @date 2022/12/26  19:46
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CommonBiz {
    /**
     * 业务描述
     */
    int bizDesc();

    /**
     * 业务编码,具体业务链流程功能
     */
    int bizCode();
}

接着定义相关的通用路由表及对应通用的人执行器即可,这里不在进行展开了,因为实际开发中这样应用的还是较少的。