嵌套模板设计模式优雅解决通用调用第三方服务方案

最近公司正在服务化,因为公司是建筑行业的产业互联网。所以对于工人进出场考勤或者核心链路上都是需要用户进行证明“我是我”这个问题。那么就涉及到调用 实名认证人脸比对人脸质量身份证OCR 等第三方服务。

1、概述

之前我们小组的成员把这个业务逻辑抽取了出来,但是服务方一直没有对接。服务调用自身调用实名相关的接口一直不尽如人意,所以现在准备对接我们的服务。可以对第三方服务支持多种渠道路由方式来提高实名服务的成功率。

由于现在的服务只是从之前的业务逻辑里面提取出来的,虽然满足接口的正常调用但是不满足业务的新需求。我就和小伙伴一起添加新的逻辑以及重构了一下代码来满足业务需求。

标题中提到的是嵌套模板设计模式优雅解决通用调用第三方服务方案,需要排除需要业务方保证幂等这种情况(虽然可以做,但是上面的接口更满足于不需要业务方保证幂等)。基于对于业务方的信任以及接口简单性(尽量少的提供参数),上面的那些第三方接口其实是满足的。但是对于短信这种第三方服务来说,就必须需要业务方保证幂等。同样请求号的短信只能发送一次,是否需要重试交给业务方这样比较合理。所以在短信服务设计的时间,就需要让业务方传递请求业务编码以及一个唯一值。

2、系统分析

下面我们来大体的聊一下业务需求:

2.1 响应上游请求号

如果多渠道实名认证也失败,根据渠道响应的异常码决定是否让用户进入人工审核的流程。并且在人工审核单详情页面会显示渠道失败的信息。请求号会在调用我们接口的时候都会生成,这个请求号会关联我们保存的上游的请求信息和响应信息以及我们请求渠道的请求和响应信息。所以我们需要把这个请求号响应给上游。

2.2 响应上游渠道请求概要信息

之前业务方在请求实名相关的接口调用渠道其实是有进行打点的。业务方在考虑接入实名服务接口的时候,提出这样一个诉求。就是所有的服务都会经过实名服务,那么是不是应该把业务打点这个逻辑下层到实名服务。我基于以下二点说服业务方不会接下这个需求:

第一点:首先实名服务抽取出来,就不应该关心上游的业务逻辑。实名服务的功能是帮助上游屏蔽掉调用第三方渠道。包括对接多个渠道、渠道路由、统一异常响应码以及统一响应等。如果业务方需要各个渠道的成功失败率,我觉得是合理的。但是如果是带有业务含义,我觉得不太合理。

第二点:要解决这个问题就是前端直接调用我的服务,然后由前端告诉业务方结果。这种其实是不可取的,因为前端其实是不可信的(请求可以篡改)。只有在一种情况下,实名这个领域可以对外提供接口,就是实名服务已经是平台级服务了,就是除了对公司提供服务以外还对其它公司提供服务(现阶段还没有达到这个能力)。

业务方后面又提出一个需求,就是返回各个渠道的基本信息。本来我是觉得实名服务就是一个渠道,上游本不关心渠道的信息的。但是考虑到业务方需要打点,这一点我妥协了,给上游提供了以下的渠道信息:

渠道响应概念信息

@Data
public class ChannelRequestInfo {

    /**
     * 调用渠道是否成功 0-失败;1-成功
     */
    private int success;
    /**
     * 渠道码
     */
    private String channelCode;
    /**
     * 调用渠道开始时间
     */
    private long startTimeMs;
    /**
     * 调用渠道结束时间
     */
    private long endTimeMs;

}

注:上游一次请求有可能调用多个渠道,所以返回的是渠道概要信息列表。

3、代码设计

3.1 请求号生成

用户每个请求都会返回请求号,在系统中我们是使用 Feign 进行远程服务调用的,所以通过 Spring MVC 的拦截器(HandlerInterceptor)机制,在请求的时候会预先生成唯一一个请求号,并且保存到 ThreadLocal 当中。

ServiceContextHandlerInterceptor.java

public class ServiceContextHandlerInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        ServiceContext serviceContext = ServiceContext.createEmpty();

        IdManager idManager = ApplicationContextHelper.getBean(IdManager.class);
        serviceContext.setRequestId(idManager.getId());

        ServiceContextHolder.set(serviceContext);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ServiceContextHolder.cleanUp();
    }
}

因为上游请求日志以及请求渠道日志表都需要通过这个请求号关联,所以把它保存到 ThreadLocal 变量当中,整个链路都需要这个参数。

3.2 保存渠道日志以及报警

对于上面提到需要调用的实名认证人脸比对人脸质量身份证OCR 等第三方服务。这些功能都有一些共通点:

  • 保存渠道信息
  • 调用渠道
  • 保存渠道日志
  • 失败报警

基于 Spring 事务模板(TransactionTemplate) 设计思想,我抽取出了 ChannelLogTemplate 这个模板类:

ChannelLogTemplate.java

@Component("channelLogTemplate")
public class ChannelLogTemplate {

    @Resource(name = "channelLogSupport")
    private ChannelLogSupport channelLogSupport;

    public <T> T execute(Object request, ChannelInfo channelInfo, ChannelLogCallback<T> callback) {
        // 添加渠道信息到上下文

        try {
        	// 渠道调用
            T result = callback.doInExecute();

			// 渠道调用成功之后
            callback.processAfterExecuteSuccess();

			// 保存渠道日志
            saveChannelLog(request, channelInfo, null);

			// 响应结果
            return result;
        } catch (BizException e) {
        	// 保存渠道日志
            saveChannelLog(request, channelInfo, e);

			// 钉钉报警

			// 抛出异常前处理逻辑
            callback.processBeforeThrowException(e);

            throw e;
        } catch (ThirdBizException e) {
        	// 保存渠道日志
            saveChannelLog(request, channelInfo, e);

			// 抛出异常前处理逻辑
            callback.processBeforeThrowException(e);

            throw e;
        } catch (Exception e) {
        	// 保存渠道日志
            saveChannelLog(request, channelInfo, e);

			// 钉钉报警

			// 抛出异常前处理逻辑
            callback.processBeforeThrowException(e);

            throw new BizException(ReturnCodeEnum.SYSTEM_ERROR, e.getMessage());
        }
    }

    /**
     * 保存渠道日志
     *
     * @param request
     * @param channelInfo
     * @param e
     */
    private void saveChannelLog(Object request, ChannelInfo channelInfo, Exception e) {
        // todo
    }

}


@FunctionalInterface
public interface ChannelLogCallback<T> {

    /**
     * 任务操作
     * @return
     */
    T doInExecute();

    default void processAfterExecuteSuccess() {
        // 默认不做事
    }

    default void processBeforeThrowException(Exception e) {
        // 默认不做事
    }

}

为了服务出现异常及时做出响应,当调用第三方异常都会触发钉钉报警。而如果是调用第三方是业务异常,比如:说实名认证的时候身份证号与人脸照片不符合的时候,我就会手动抛出 ThirdBizException 异常,这个异常并不会触发钉钉报警。

回调接口 ChannelLogCallback里面有三个方法:

  • doInExecute():调用渠道回调逻辑
  • processAfterExecuteSuccess():成功调用渠道响应时触发,场景是当用户进行银行卡三要素成功之后,我们就会把银行卡号、姓名、身份证号保存到数据库当中。当有同样的请求来的时候后不再走第三方渠道,直接返回成功。
  • processBeforeThrowException():抛出异常前触发,当用于银行卡三要验证失败时,结果会在缓存中缓存一段时间。

3.3 通用响应对象

在上面我们提到我们需要对上游返回二个参数:一个是请求号;另一个是渠道请求概要信息列表。其实需要另外一个参数的,本来我们服务定义的响应对象是:

Resut.java

public class Resut<T> {

	// 响应码
    private String code;

	// 响应信息
    private String msg;

	// 响应对象
    private T data;
}

本来业务方通过 code 就可以返回这次请求是否成功,现在业务方不管调用渠道是否成功都需要把请求渠道概要信息列表返回。通过 code 只能表示调用实名认证服务成功,并不能代码渠道是否成功。而且响应对象(data)里面可能需要包含其它业务信息。比如,身份证OCR 的时候需要返回身份证信息,但是如果请求渠道失败必须要返回请求渠道的信息身份证信息这个时候就是空的。业务方 通过code并不能知道身份证信息是否有值,如果把判断空值这个逻辑交给业务方那么逻辑就太混乱了而且不利于接口对接的简易性。

我觉得应该由服务提供方提供一种判断机制来保证业务参数是否有值。这个时候需要在通用返回对象里面标记是否调用渠道成功,如果成功了实名认证就保证业务参数必然是有值的。而如果调用渠道成功,就应该是拿不到业务参数的。这个时候上面的 Resut 里面data 这个响应对象必须继承下面通用返回对象:

BaseResponseDto.java

@Data
public class BaseResponseDto {

    /**
     * 是否调用渠道成功
     */
    private boolean success;
    /**
     * 请求ID
     */
    private String requestId;

    /**
     * 请求渠道信息
     */
    private List<RequestInfoDto> requestInfos = new ArrayList<>();

    public BaseResponseDto(){
        this.success = true;
    }
    
    /**
     * 添加请求信息
     *
     * @param requestInfoDto
     */
    public void addRequestInfo(RequestInfoDto requestInfoDto) {
        requestInfos.add(requestInfoDto);
    }

}

所有的调用第三方接口都需要返回上面的信息,所以基于之前的渠道日志的模板类我在上面一层又套了一层模板,就是文章标题上面的提到的嵌套模板。

ChannelInfoTemplate.java

@Component("channelInfoTemplate")
public class ChannelInfoTemplate {

    @Resource(name = "channelLogTemplate")
    private ChannelLogTemplate channelLogTemplate;

    public <T> T execute(Object request, BaseResponseDto response, ChannelInfo channelInfo, ChannelLogCallback<T> callback) {
		response.setRequestId(ServiceContextHolder.get().getRequestId());

        RequestInfoDto requestInfo = beforeExecute(channelInfo);

        try {
            T result = channelLogTemplate.execute(request, channelInfo, callback);

            afterExecuteSuccess(requestInfo, response);

            return result;
         catch (Exception e) {
            response.setSuccess(false);
            
            afterExecuteException(requestInfo, response);

            throw e;
        }
    }

    /**
     * 执行操作前
     * @param channelInfo
     * @return
     */
    private RequestInfoDto beforeExecute(ChannelInfo channelInfo) {
        RequestInfoDto requestInfoDto = new RequestInfoDto();
        requestInfoDto.setSuccess(SuccessIntegerEnum.YES.getCode());
        requestInfoDto.setChannelCode(channelInfo.getChannelCode());
        requestInfoDto.setStartTimeMs(System.currentTimeMillis());
        return requestInfoDto;
    }

    /**
     * 执行成功操作后
     * @param requestInfo
     */
    private void afterExecuteSuccess(RequestInfoDto requestInfo, BaseResponseDto response) {
        requestInfo.setEndTimeMs(System.currentTimeMillis());
        response.addRequestInfo(requestInfo);
    }

    /**
     * 执行异常操作后
     * @param requestInfo
     */
    private void afterExecuteException(RequestInfoDto requestInfo, BaseResponseDto response) {
        requestInfo.setSuccess(SuccessIntegerEnum.NO.getCode());
        requestInfo.setEndTimeMs(System.currentTimeMillis());
        response.addRequestInfo(requestInfo);
    }

}

这个时候并没有处理异常(把异常吃掉),因为调用这个接口的上面有可能会对异常进行特殊处理。

<think>好的,用户想让我帮他分析如何撰写高级前端简历,特别是关于项目部分的亮点和难点。首先,我需要理解他的技术栈主要是React,同时用过很多第三方库。他计划写两个中后台项目和两个C端H5项目,需要突出亮点和解决方案。 首先,用户可能已经有一定的经验,但如何将项目描述得吸引人是个挑战。高级前端职位通常更关注解决问题的能力、技术深度和项目影响力。因此,项目描述需要展示技术选型的合理性、复杂问题的解决过程以及实际成果。 接下来,考虑中后台项目和C端项目的不同侧重点。中后台可能涉及复杂的状态管理、性能优化、权限控制等,而C端H5可能更关注用户体验、加载速度、兼容性。需要分别针对这两类项目提炼不同的亮点。 用户提到使用React和第三方库,可能需要具体举例,比如React Hooks、Redux、Ant Design、Webpack优化等。但用户没有具体说明用了哪些库,可能需要建议他列举关键的技术栈,并说明在项目中的应用场景。 难点部分,需要真实的技术挑战,比如中后台的大数据渲染、C端的首屏加载优化。解决方案要具体,比如虚拟滚动、代码分割、懒加载等。同时,要量化成果,比如性能提升百分比,用户体验改善等。 还要注意项目结构,每个项目可能需要包括项目背景、技术栈、亮点、难点及解决方案、成果数据。这样结构清晰,方便招聘方快速捕捉信息。 可能用户没有意识到需要量化结果,或者如何将技术细节转化为成果。比如,优化方案实施后,页面加载时间减少了多少,错误率下降了多少。这些数据能增强说服力。 另外,避免使用过于通用的描述,比如“优化了性能”,而应具体说明采用了什么技术,解决了什么问题。例如,“通过Webpack分包策略和动态加载,将首屏加载时间从3s降至1.2s”。 最后,检查是否符合之前的系统级指令,确保数学公式正确,但这里可能用不上。主要确保回答结构清晰,分点明确,使用中文,技术术语准确。</think>以下是为您量身定制的高级前端简历项目撰写方案(React技术栈),按结构分层解析: --- ### 一、中后台项目撰写模板(2个) #### 项目1:企业级低代码平台 **技术栈**:React18 + TypeScript + Zustand + Ant Design Pro + qiankun微前端 **亮点**: 1. 动态表单引擎:实现$$ \text{JSON Schema} \rightarrow \text{React Component Tree} $$的自动化转换,支持嵌套表单验证 2. 可视化编排:基于React-DnD实现零代码模块组合,开发效率提升40% **难点与解决方案**: - **跨应用状态同步**:采用发布订阅模式 + localStorage事件监听,实现微前端间实时通信 - **性能卡顿**:针对大型表单使用虚拟滚动技术(react-virtualized),渲染耗时从$$ 850\text{ms} \rightarrow 120\text{ms} $$ #### 项目2:医疗数据可视化系统 **技术栈**:React17 + Redux Toolkit + ECharts + Web Workers **亮点**: 1. 百万级数据实时渲染:通过Web Workers进行数据分片处理,主线程FPS稳定在60 2. 智能缓存策略:基于IndexedDB实现离线数据持久化,网络中断时仍可保持2小时正常操作 **难点与解决方案**: - **内存泄漏**:使用React Profiler定位闭包泄漏,采用WeakMap重构事件监听器 - **图表联动延迟**:设计防抖节流组合策略,交互响应时间优化至$$ 200\text{ms} $$以内 --- ### 二、C端H5项目撰写模板(2个) #### 项目1:电商秒杀活动页 **技术栈**:React16 + Next.js + SWR + Taro跨端 **亮点**: 1. 首屏极致优化:通过Webpack分包策略 + 关键CSS内联,LCP指标从$$ 3.2s \rightarrow 1.1s $$ 2. 高并发应对:设计客户端缓存池 + 服务端时序验证双重保障,成功支撑$$ 10w+ $$ QPS **难点与解决方案**: - **白屏问题**:实现React Error Boundary + Sentry监控告警,异常捕获率提升至98% - **动画卡顿**:采用CSS Composite层优化 + requestAnimationFrame节流 #### 项目2:在线教育直播H5 **技术栈**:React18 + WebSocket + WebRTC + FFmpeg.wasm **亮点**: 1. 弱网适配:基于Ack机制设计分级降级策略,在$$ 100\text{kbps} $$带宽下仍可保持音频流畅 2. 跨端兼容:通过UA检测 + 特性渐进增强方案,覆盖98%主流移动浏览器 **难点与解决方案**: - **直播延迟**:实现WebAssembly版JitterBuffer算法,延迟稳定在$$ 800\text{ms} \pm 50\text{ms} $$ - **内存膨胀**:采用Object Pool模式管理信令对象,内存占用减少62% --- ### 三、进阶技巧 1. **技术深度可视化**:在方案描述中嵌入技术指标公式,如$$ \text{性能提升比} = \frac{T_{\text{before}} - T_{\text{after}}}{T_{\text{before}}} \times 100\% $$ 2. **架构图辅助**:用ASCII/纯文本描述关键架构,如: ``` [Client] --WebSocket--> [Gateway]           |                      |           --SSE----------> [Cache Layer] ``` 3. **数据说话**:所有解决方案需附带量化结果(如错误率降低xx%,性能提升xx%) --- ### 四、避坑指南 1. 避免简单罗列API调用,重点展示技术决策过程 2. 难点选择标准:具有技术复利效应的问题(如架构设计、性能优化) 3. 使用专业术语但保持可读性,如将"优化加载速度"改为"实现Tree Shaking + 动态Polyfill方案" 按此框架撰写,既能体现技术深度,又能展现业务价值,符合高级前端工程师的考察维度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值