feign请求签名统一处理

背景

在实际项目中经常有需要对接第三方开放平台的接口,这些接口通畅需要token、签名等,本文使用feign的扩展解决这一类型问题

聚水潭出库单demo

接口

/**
 * 聚水潭出库单查询
 * @author authorZhao
 * @since 2022-11-17
 */
@FeignClient(value = "JuShuiTanOdersOutFeign",
        url = "${com.git.client.jst.url:https://openapi.jushuitan.com}/open",
        //url = "http://127.0.0.1:9199/api/im/test",
        configuration = MyConfiguration.class,
        contextId = "JuShuiTanOdersOutFeign")
public interface JuShuiTanOdersOutFeign {

    /**
     * <a href="https://openweb.jushuitan.com/dev-doc?docType=8&docId=34">聚水潭出库单查询</a>
     * 参数为什么写object和map目的就是为了写出不可维护的代码
     * @param object
     * @return
     */
    @PostMapping(value = "/orders/out/simple/query",consumes = "application/x-www-form-urlencoded;charset=UTF-8")
    AjaxResult<JstOrderOutDTO> query(Object object);

    @PostMapping(value = "/other/inout/query",consumes = "application/x-www-form-urlencoded;charset=UTF-8")
    AjaxResult<Map> otherInoutQuery(Map map);

}

自定义配置

@Slf4j
public class MyConfiguration {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private JstConfig jstConfig;

    @Bean
    @Scope("prototype")
    public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        return new MyFormEncoder(new SpringEncoder(messageConverters),jstConfig,redisTemplate);
    }

    public static class MyFormEncoder extends FormEncoder{
        private final JstConfig jstConfig;
        private final RedisTemplate redisTemplate;
        public MyFormEncoder(Encoder delegate, JstConfig jstConfig,RedisTemplate redisTemplate) {
            super(delegate);
            this.jstConfig = jstConfig;
            this.redisTemplate = redisTemplate;
        }

        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
            Object o = redisTemplate.opsForValue().get(RedisPreEnum.JST_TOKEN.getType());
            String accessToken = Optional.ofNullable(o).map(Object::toString).map(i->JSON.parseObject(i, JstTokenDTO.class))
                    .map(JstTokenDTO::getAccess_token).orElse(StringUtils.EMPTY);
            String jsonString = Optional.ofNullable(JSON.toJSONString(object)).orElse("{}");
            Map<String,String> map = new HashMap<>();
            map.put("timestamp",String.valueOf((int)(System.currentTimeMillis()/1000)));
            map.put("version","2");
            map.put("charset","utf-8");
            map.put("access_token",accessToken);
            map.put("app_key",jstConfig.getAppKey());
            map.put("biz",jsonString);
            SignUtil.sign(jstConfig.getAppSecret(), map);
            log.info("jst param={}",JSON.toJSONString(map));
            super.encode(map,Encoder.MAP_STRING_WILDCARD,template);
        }
    }
}

解析

上述注意事项

1.聚水潭的请求接口content-type不是常见的json而是form表单,这需要注意等下结合这个需要做自定义处理

使用@RequestBody最终会被解析为json类型的

如果在feign里面使用Map<String,?>这种类型的参数在encode时候会被当做表单处理,本文这可能是Object也可能是Map<?,?>所以需要自定义解码

2.为什么token或者签名不在拦截器里面处理

拦截器在encode之后,到那时在处理数据会被处理多次没有必要

3.自定义解码集成springEncode最终将类型固定为Encoder.MAP_STRING_WILDCARD 这样子会以表单形式提交

U9案例

配置

/**
 * @author authorZhao
 * @summary fegin 客户端的自定义配置
 */
@Slf4j
public class U9FeignJsonConfiguration {
    @Autowired
    private ThirdBasicComponent thirdBasicComponent;


    @Bean
    public RequestInterceptor headerToken() {
        return template -> {
            String token = thirdBasicComponent.u9Token();
            if(StringUtils.isNotBlank(token)){
                template.header("token",token);
            }else {
                log.warn("u9 token获取失败");
            }
        };
    }

    @Bean
    @Scope("prototype")
    public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        return new U9FeignJsonConfiguration.MyFormEncoder(new SpringEncoder(messageConverters));
    }

    public static class MyFormEncoder extends FormEncoder {
        public MyFormEncoder(Encoder delegate) {
            super(delegate);
        }

        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
            template.body(JSON.toJSONString(object));
            //super.encode(object,bodyType,template);
//            String typeName = bodyType.getTypeName();
//
        }
    }


}

u9的接口就直接忽略了,直接看配置,解释问题

注意事项:

1.U9只需要token,这时候在拦截器里面处理最方便

2.为什么U9的编码也需要自定义处理

u9的请求参数是json形式,但是U9的系统是c#编写的,里面解析json如果有的值为null会解析报错(把这个key去除没问题),我们改不了别人的接口,只能改自己的json序列化方式,为例避免全局影响,直接使用fastjson2处理

其他事项

1.每次请求获取token使用redis这个可以优化增加多级缓存

2.如何刷新token

  • 聚水潭新旧token有五分钟共存时间,基本问题不大定时刷新
  • u9的每次请求判断token是否国过期,快过期的时候刷新,这时候如果并发量很大会有问题,本文主要是数据同步,数据量没那么大随便操作

本文原创,转载请申明

### 微服务中 Feign 的远程调用使用教程、原理、配置与示例 #### 什么是 FeignFeign 是一种声明式的 Web Service 客户端工具,它使得编写 HTTP API 调用变得更加简单。通过 Feign,开发人员可以像本地方法一样调用远程的服务接口[^1]。 --- #### Feign 的工作原理 Feign 实现了基于接口的动态代理机制,当程序运行时会根据定义好的接口生成具体的实现类并完成远程服务的调用过程。其底层依赖于 Ribbon 和 Hystrix 来提供负载均衡和服务熔断的功能[^2]。 --- #### 开启 Feign 功能的核心注解 为了启用 Feign 的功能,在 Spring Boot 应用中的启动类上需要添加 `@EnableFeignClients` 注解。该注解的作用是指定哪些包下的接口会被扫描为 Feign Client 接口,并自动生成对应的代理对象[^4]。 ```java @SpringBootApplication @EnableFeignClients(basePackages = "com.example.feign.clients") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` --- #### 定义远程服务调用接口 通过 `@FeignClient` 注解标注的接口用来描述如何访问目标服务的方法签名以及 URL 映射关系。下面是一个简单的例子: ```java @FeignClient(name = "producer-service", url = "http://localhost:8081") public interface ProducerServiceClient { @GetMapping("/api/data/{id}") String getDataById(@PathVariable("id") Long id); @PostMapping("/api/data/save") ResponseEntity<String> saveData(@RequestBody DataRequest request); } ``` 上述代码片段展示了如何利用 Feign 对指定路径发起 GET 请求和 POST 请求[^3]。 --- #### 配置 OpenFeign 日志增强 如果希望查看更详细的调试信息,则可以通过设置日志级别来观察每次请求的具体情况。例如修改 application.yml 文件如下所示: ```yaml logging: level: com.example.feign.clients.ProducerServiceClient: DEBUG ``` 同时还需要确保项目引入了 spring-cloud-starter-openfeign 依赖项以便支持高级特性如日志记录等功能。 --- #### 解决常见问题——跨服务认证失败 在一个分布式系统里实施统一身份验证之后可能会遇到一个问题:当前用户的身份令牌无法自动传递给下游服务造成未授权错误。针对这种情况有两种解决方案可供选择: - **手动复制 Header** :可以在 Consumer 端显式地把 Token 添加到每一次对外部资源发出的新 Request 中去; - **借助拦截器自动化处理** : 创建一个专门负责附加必要 Headers 的 Interceptor 类型 Bean 并注册至上下文中即可[^5]。 以下是第二种方式的一个具体实现案例: ```java @Component public class AuthTokenInterceptor implements RequestInterceptor { private final AuthenticationHolder authHolder; public AuthTokenInterceptor(AuthenticationHolder authHolder){ this.authHolder=authHolder; } @Override public void apply(RequestTemplate template) { Optional<Authentication> optionalAuth = authHolder.get(); if(optionalAuth.isPresent()){ template.header("Authorization","Bearer "+optionalAuth.get().getCredentials()); } } } ``` --- #### 总结 综上所述,Feign 提供了一种优雅的方式来简化微服务架构下不同应用之间的交互流程。通过对以上知识点的学习可以帮助开发者快速掌握它的基本概念及其实际应用场景下的操作步骤。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值