• 实现风控中台案例分析


    目录

    一、搭建风控平台

    1、业务需求

    2、业务流程设计

    3、伪代码展示

    二、平台演化成中台过程

    1、中台体系问题

    2、中台架构问题

     3、中台技术问题


    今天我们实现风控中台案例分析,首先这个风控中台不是凭空出现的,必须依托核心的业务,比如我们现有的电商平台,需要进行风险控制,因此做一个风控平台是当务之急,而后随着业务的扩张,并且其他的业务组产品也使用我们的这个风控平台,慢慢的我们的风控平台就演化成了中台的模样。

    一、搭建风控平台

    1、业务需求

            比如当前,我们的电商项目业务功能算是比较成体系了,但是对于风险控制方面,还没有太多考虑。现在有个需求,要在注册、下单、交易等各个环节来增加对用户手机号码安全性的检查。你会怎么做?
           最开始的做法肯定是在各个业务环节里加代码,去检查手机号码是否安全。然后有些逻辑很相似,就会抽象成一个微服务。当微服务的逻辑逐渐复杂,使用场景开始增多,就会独立出一个平台。这个平台要求有丰富的业务能力,并且能够给多个外部系统调用。现在要你来设计这样一个平台,你会怎样设计?

    2、业务流程设计

    1)服务端

             分为三个统一的业务接口,一个swagger的测试接口。 另外两个是需要加密的客户端接口,一个是同步服务调用接口,另一个是异步服务通知接口。三个接口的业务接入逻辑略有不同,但是后面的业务实现逻辑是一样的。业务实现通过serviceCode来区分。每个serviceCode标识一种业务能力,匹配不同的业务报文。
            提供同步服务调用和异步服务通知两种接入方案。

    2)客户端

             先接入,再使用。需要使用接入后分配的相关信息,才能调用具体的服务。

    3)机构设计


           服务端拥有完整的业务流程,单独记录每次服务的日志。对外提供基于HTTP的通用业务服务,对内通过Dubbo调用内部的服务能力。

    4)代码实现关键点

      A、  请求报文加解密
      B、  重复请求处理
      C、  日志文件处理

     大家可以想象一下,我们这个风控平台是不是已经具备了往中台发展的潜质?以后完全可以通过ServiceCode,扩展出更多更丰富的业务功能,给电商项目提供更多的支持,甚至给电商以外的其他系统也能提供支持。但是,即便他的能力很强,也只能称之为平台,离中台还有一定的距离。

    3、伪代码展示

    1)控制层

    同步接口

    1. /**
    2. * 两个异步接口统一的处理逻辑:
    3. * 1、接口接收请求,进行参数验证。
    4. * 2、同步返回参数验证的结果。
    5. * 3、参数验证通过后,进入业务处理逻辑。 根据transId查询历史请求,已经请求过的,直接返回上次的结果。
    6. * 没有请求过的, 进行业务逻辑处理,异步推送业务结果到sysId对应的服务接收地址。
    7. */
    8. @ApiOperation(value = "GenSI Restful接口", notes = "GenSI Restful调试接口")
    9. @RequestMapping(value = "/gsInterface", method = RequestMethod.POST)
    10. public Object gsRestInterface(@RequestBody String requestMessage, HttpServletRequest request) {
    11. logger.info("GsRestInterface: request=> " + requestMessage);
    12. JSONObject rspBody = new JSONObject();
    13. JSONObject jRequestMessage = JSONObject.parseObject(requestMessage);
    14. RequestMsgHeader reqHeader = jRequestMessage.getObject("header", RequestMsgHeader.class);
    15. // 通一使用sysId标识时再启用
    16. // header为必须传的参数 包含transId,sysId,serviceCode。
    17. if (null == jRequestMessage || null == reqHeader) {
    18. logger.info("GsRestInterface: error => 接口数据错误");
    19. // 返回错误信息
    20. rspBody.put("transId", "");
    21. rspBody.put("result", "1");
    22. rspBody.put("desc", "接口数据错误");
    23. return rspBody;
    24. }
    25. // 启动mysql连接重连测试
    26. gsrequestService.connectCheck();
    27. // 统一鉴权预留。
    28. // 根据接口内容将body提取成接口业务需要的对象
    29. String serviceCode = reqHeader.getServiceCode();
    30. String reqTransId = reqHeader.getTransId();
    31. String sysId = reqHeader.getSysId();
    32. GsResponse gsRes = null;// 返回给外围系统的结果
    33. JSONObject jRequestBody = jRequestMessage.getJSONObject("body");
    34. // 检查系统标识是否存在 通一使用sysId标识时再启用
    35. if (!ConfigUtils.checkSysId(sysId)) {
    36. logger.info("GsRestInterface: error =>无效的系统标识sysId:" + sysId);
    37. // 返回错误信息
    38. gsRes = new GsResponse(serviceCode, reqTransId, "2", "无效的系统标识"+sysId);
    39. rspBody = gsRes.toJsonFormat();
    40. return rspBody;
    41. }
    42. //以ServiceCode和transId为标识,有历史记录就返回历史记录
    43. if(StringUtils.isEmpty(reqTransId)|| StringUtils.isEmpty(sysId)|| StringUtils.isEmpty(serviceCode)){
    44. logger.info("GsRestInterface: error => 缺少必要的参数");
    45. gsRes = new GsResponse(serviceCode, reqTransId, "3", "缺少必要的参数");
    46. rspBody = gsRes.toJsonFormat();
    47. return rspBody;
    48. }else{
    49. //追加该请求的日志文件
    50. gsLogConfig.addTransLogAppender(logger, reqTransId);
    51. logger.info("GsRestInterface: request=> " + requestMessage);
    52. //参数处理正常即同步返回请求接收成功的响应,业务结果异步进行推送
    53. logger.info("GsRestInterface: status => 参数处理正常");
    54. gsRes = new GsResponse(serviceCode, reqTransId, "0", "请求接收成功");
    55. rspBody = gsRes.toJsonFormat();
    56. String hisRsponse = gsrequestService.getHisResp(reqTransId, serviceCode);// 查询历史记录
    57. if (StringUtils.isNotEmpty(hisRsponse)) {
    58. // 有历史记录,直接给业务系统推送历史记录。
    59. logger.info("crawlerInterface: info => 该请求已正常请求过,将返回历史结果;transId:"+reqTransId+";serviceCode:"+serviceCode+" ");
    60. // 异步发送给业务系统
    61. ServiceUtils.sendAsyn(sysId, hisRsponse);
    62. return rspBody;
    63. }
    64. }
    65. // 具体每个接口的核心业务逻辑
    66. busiService.doProcess(sysId,reqTransId, serviceCode, jRequestBody);
    67. logger.info("crawlerInterface: 返回请求响应 => " + rspBody.toJSONString());
    68. gsLogConfig.removeTransLogAppender(logger);
    69. return rspBody;
    70. }

    异步接口

    1. @ApiOperation(value = "GenSI异步业务接口", notes = "增加接口鉴权相关机制。需配合提供的客户端程序进行使用。")
    2. @RequestMapping(value = "/gsInterfaceAsync", method = RequestMethod.POST)
    3. public Object gsDecrypedInterfaceAsync(HttpServletRequest request) {
    4. logger.info("gsInterfaceAsync: received Request => " + request.getParameter("ftRequestInfo"));
    5. JSONObject rspBody = new JSONObject();
    6. GsResponse fydRes = null;
    7. JSONObject ftRequestInfo = JSON.parseObject(request.getParameter("ftRequestInfo"));
    8. // 报文解密
    9. JSONObject jRequestMessage = decrypService.decrypData(ftRequestInfo, rspBody);
    10. logger.info("gsInterfaceAsync: decryped Request=> " + jRequestMessage);
    11. // 解密失败 返回错误信息
    12. if (null == jRequestMessage) {
    13. return rspBody;
    14. }
    15. RequestMsgHeaderV2 reqHeader = jRequestMessage.getObject("header", RequestMsgHeaderV2.class);
    16. //简单报文参数检查
    17. if(null == reqHeader || "".equals(reqHeader.getServiceCode()) || "".equals(reqHeader.getTransId())){
    18. logger.info("gsInterfaceAsync: error =>报文头参数缺失");
    19. // 返回错误信息
    20. rspBody.put("transId", "");
    21. rspBody.put("result", "1");
    22. rspBody.put("desc", "报文头参数缺失");
    23. return rspBody;
    24. }
    25. // 检查系统标识是否存在 通一使用sysId标识时再启用
    26. String sysId = reqHeader.getSysId();
    27. if (!ConfigUtils.checkSysId(sysId)) {
    28. logger.info("gsInterfaceAsync: error =>无效的系统标识sysId:" + sysId);
    29. // 返回错误信息
    30. rspBody.put("transId", "");
    31. rspBody.put("result", "1");
    32. rspBody.put("desc", "无效的系统标识:" + sysId);
    33. return rspBody;
    34. }
    35. JSONObject jRequestBody = jRequestMessage.getJSONObject("body");
    36. String transId = reqHeader.getTransId();
    37. String serviceCode = reqHeader.getServiceCode();
    38. //有历史请求记录直接返回
    39. if(StringUtils.isEmpty(transId)|| StringUtils.isEmpty(sysId)|| StringUtils.isEmpty(serviceCode)){
    40. logger.info("gsInterfaceAsync: error => 参数处理错误");
    41. fydRes = new GsResponse(serviceCode, transId, "1", "参数处理错误");
    42. rspBody = fydRes.toJsonFormat();
    43. return rspBody;
    44. }else{
    45. gsLogConfig.addTransLogAppender(logger, transId);
    46. logger.info("gsInterfaceAsync: request=> " + jRequestMessage);
    47. logger.info("gsInterfaceAsync: error => 参数处理正常");
    48. fydRes = new GsResponse(serviceCode, transId, "0", "请求接收成功");
    49. rspBody = fydRes.toJsonFormat();
    50. String hisRsponse = gsrequestService.getHisResp(transId, serviceCode);// 查询历史记录
    51. if (StringUtils.isNotEmpty(hisRsponse)) {
    52. // 有历史记录,直接推送历史记录。
    53. logger.info("gsInterfaceAsync: info => transId:"+transId+";serviceCode:"+serviceCode+" 已正常请求过,返回历史结果");
    54. // 异步发送结果
    55. ServiceUtils.sendAsyn(sysId, hisRsponse);
    56. return rspBody;
    57. }
    58. }
    59. //核心业务接口
    60. busiService.doProcess(sysId,transId, serviceCode, jRequestBody);
    61. gsLogConfig.removeTransLogAppender(logger);
    62. return rspBody;
    63. }

    2)核心业务层

    1. public void doProcess(String sysId,String transId, String serviceCode, Map<String, Object> requestMsg) {
    2. logger.info("start doProcess:sysId => "+sysId+";serviceCode => "+serviceCode+";requestMsg => "+requestMsg);
    3. // =================================手机号码标注业务==========================================
    4. if (RequestMsg.MOBILE_MARK_SERVICE.equals(serviceCode)) {
    5. logger.info("process entry => 进入手机号码标注接口对接");
    6. // 转换body为业务对象
    7. PhoneTagInfo requestInfo = new PhoneTagInfo();
    8. requestInfo.setTransId(transId);
    9. requestInfo.setMobile(requestMsg.get("mobile").toString());
    10. // 启动数据回调线程
    11. taskExecutor.execute(new PhoneTagCallbackV2(sysId,requestInfo,gsRequestService,mobileTagService));
    12. // 返回正确接收请求的响应信息
    13. logger.info("process info : 手机号码标注异步推送线程正常启动");
    14. // =================================手机号码归属省==========================================
    15. }else if(RequestMsg.MOBILE_AREA_SERVICE.equals(serviceCode)){
    16. logger.info("process entry => 进入手机号码标注接口对接");
    17. // 转换body为业务对象
    18. PhoneTagInfo requestInfo = new PhoneTagInfo();
    19. requestInfo.setTransId(transId);
    20. requestInfo.setMobile(requestMsg.get("mobile").toString());
    21. // 启动数据回调线程
    22. taskExecutor.execute(new MobileAreaCallbackV2(sysId,requestInfo,gsRequestService,mobileAresService));
    23. // 返回正确接收请求的响应信息
    24. logger.info("process info : 手机号码标注异步推送线程正常启动");
    25. }else {
    26. logger.info("doProcess : entry => 没有匹配的接口,请确认serviceCode是否正确。");
    27. }
    28. }

     核心业务接口就是如上伪代码。

    二、平台演化成中台过程

     中台是一种企业战略,而不是一项具体的技术。中台战略涉及到的不光是技术重组,还包括公司的组织架构、人员流动机制、资源投入等各个方面的协调。所以如果你站在高层视角去看过中台,那么理解中台需要发挥你的想象力。这里通过几个问题的思考,带大家站在架构师的角度,从大到小形成一个可实施的中台建设方案的设想。

    1、中台体系问题

    1)问题一: 你可能觉得我们这个风控中台业务太小了,那一个企业级的风控中台是怎么发展出来的?

         A、  从小了说,以我们的这个电商项目的风控部分需求为例。只是我们这个项目业务体量还没有上来,所以你可能还没有太多的感觉。

        一方面风控需求的面会越来越广。
    首先,目前我们的这个中台,还只有两个业务,业务体量确实比较少。但是当以后业务多了之后,可以通过ServiceCode快速扩展新的业务能力。然后,目前中台对接的业务系统还只有电商这一个系统。随着业务体量的加大,用户、营销、交易都可能发展成为独立的业务系统,这个时候风控中台的体量就大了,还需要针对客户端进行接口权限控制、调用计费控制、数据权限控制等等。
    接下来,针对后台数据,目前还只是简单的采用搜索引擎的数据,但是业务体量大了之后,必须要形成自己的数据体系,获取数据以及处理数据的能力都需要大幅度提升。例如对于用户,有大量追逐小利的羊毛党,只要稍不留神,一个营销活动就可能造成平台大量亏损。还要防止商家与用户勾结,进行线上洗钱等非法活动(虚构产品,交易不发货)。另外还有大量的国家监管体系也需要接入。这些行业内的运营经验,需要有一个能够集中进行整理以及沉淀的地方。而对于这些行业经验,全部由自身业务推动,积累得就太慢了。所以,这些经验和能力,单独进行沉淀,就逐渐形成了风控中台。这些功能都考虑进去,是不是有点做中台的感觉了?
            另一方面各个业务的风控能力也是不一样。
    例如我们的这个电商项目,客户端应用的接入,不是一个简单的注册就能解决的,还需要有线上线下各种综合的审批制度。而如果上线运营后,单靠自己一家的数据,各自为战,是很难做到有效控制的。例如羊毛党,可以在平台之间迅速转移,像蝗虫一样导出收割福利。这也需要风控中台与行业内形成统一联防机制。然后针对营销环节,想要提高营销活动的用户转化率,也是需要风控中台提供一些数据支撑的。而这些事情,都不是仅仅依靠技术就能够解决的,还需要有大量的人力、物力等资源的投入才能完成的。要将这些资源整合到一起,是不是有点做中台的感觉了?

           有一句很经典的话:中台,是业务演进出来的,而不是架构出来的。实际上,阿里早在09年就开始建设共享事业部,当时阿里还只有淘宝和天猫两个产品。但是从"厚平台,薄应用"的平台化战略走到中台战略,阿里积累了近十年的建设经验。

     
     B、从大了说,当中台体系足够完善,就可以用成熟业务场景反哺相对不太成熟的业务场景。
    例如阿里的无人超市、无人酒店、盒马生鲜等新业务,开展起来就非常快。因为风控、支付、用户、商户这些环节都已经有了现成的能力,线上部分只需要开发一个简单的APP,将这些能力组合到一起,就可以开展业务。

    2)问题二: 有了风控中台的概念后,你觉得我们电商项目还有哪些地方可以抽取成中台?

     从技术体系上,风控中台肯定不是单独搭建的,还需要有用户中台、商户中台、收银中台、交易中台、营销中台等等其他中台一起发展。

    3)问题三: 现在人人都在喊中台,是不是所有企业都要用中台?

    虽然现在看来,中台确实是非常好,但是业界尤其是阿里、京东这些中台战略领先实施的大厂,也有不少的声音开始反对中台了。为什么呢?这是因为中台的理想虽然好,但是落地实践的问题却非常多。所以如果你所在的公司准备要开始推行中台化了,有些问题就需要提前考虑好了。
    1、中台的价值很模糊。
    中台并不能直接产生价值,他需要依托于业务部门才能体现出他的价值。而一个企业的业务往往是不平衡的,有赚钱的核心主业务,也有不赚钱的一些边缘小业务。这个时候就很容易陷入一种中台抱大业务大腿,而小业务抱中台的大腿的困局。中台往往优先按照大业务的要求进行功能开发,而小业务的要求就比较难得到相应。这就很容易造成新业务的同质化,从而影响到新业务的竞争能力。其实这从阿里也能看出点端倪,阿里的无人售货机、无人超市、无人酒店这一类的业务,其实就线上能力这一块来说,都是很相似的。如果阿里不是不断的通过拓展线下环境,那线上的很多产品就都沦为差不多的东西了,大家用起了也会丧失新鲜感。

             阿里的体量尚且如此,一般的企业就更需要慎重了。


    2、中台并不是总能提炼出共性需求
    中台的核心是提炼业务的共性,进行沉淀、提炼,形成中台业务能力。但是在具体实施时,哪些功能是通用的,哪些功能是个性化的,很难形成一个标准。这个度很难把握,中台做得太多了,就成了业务系统偷懒的利器,反正什么功能都提给中台就行了。中台做得太少了,又很难得到业务部门尤其是小业务的反馈,评估不出真正通用的功能。中台很容易变成大业务的后台,而对小业务,一句"你的功能不通用",就全部给打回去了。


    3、中台存在更多的变化
    中台的业务能力要实时发生变化,这很容易造成具体业务掉队。例如一个中台针对十个业务开发了一个底层业务能力,这时,其中一个大业务要对这个业务能力进行更新。中台如果接受了,就很可能会要求其他九个业务也要调整自己的业务流程以适应新的中台功能。那这对这九个业务来说,就是无意义的工作。所以现在越来越多的人提出中台并不是银弹,并不是所有企业都需要中台。

    2、中台架构问题

     我们这个中台还太过简单,你可能会感觉困惑,这就是一个中台了吗? 这是因为我们这里只考虑了数据的使用,而忽略了数据收集与处理的问题。一个典型的企业级风控系统的整体架构设计图。

     3、中台技术问题

     在我们这个风控中台体系中如何快速开发一个新的业务?有没有发现要开发一个新的功能,要改的地方还是挺多的,开发过程也挺麻烦的。并且,所有的ServiceCode都在BusiService中通过if-else语句来扩展,可以想象要开发十个二十个业务后,整个代码会混乱成一个什么样的程度。要如何保证系统优雅、稳定的演进?那么DDD思想就是现在业界非常认可的一种思路。

    到此、实现风控中台案例分析完毕,下篇我们分析DDD领域驱动设计的思想,敬请期待。

  • 相关阅读:
    查看svn当前版本信息
    Matlab数值计算(多项式插值)
    操作系统考试速成01
    基于YOLOv8模型和CrowdHuman数据集的行人检测系统(PyTorch+Pyside6+YOLOv8模型)
    C语言 指针与数组
    ch17、面向对象编程-扩展与复用
    JDBC版本简介
    ArrayList集合特点为什么是增删慢、查询快?
    【iOS开发】- Block传值的基础学习
    C++:重定义:符号重定义:变量重定义
  • 原文地址:https://blog.csdn.net/nandao158/article/details/126261663