# 通过Java代码调用系统内微应用服务

本章节描述同一个系统内的微应用服务的Java代码调用规范:

  • provider:服务提供端在api-module定义好feignClient和相关api接口,并在impl-module模块中实现这些接口,api-module可直接打成jar供其他consumer消费端依赖。
  • consumer:服务调用端首先引入服务提供端提供的api-jar,直接使用注解@Autowired把feignClient注入当前应用中使用即可。调用端无需自己实现feignClient以及相关api接口。

# provider服务提供端

api模块pom引入相关依赖,EOS服务自带此依赖,费EOS服务需要引入

<dependency>
    <groupId>com.primeton.eos</groupId>
    <artifactId>eos-server-starter</artifactId>
</dependency>

启动类是否有@EnableFeignClients和@EnableHystrix注解

参数 说明
@EnableFeignClients SpringClouds原生注解,扫描和注册Feign客户端Bean定义
@EnableHystrix SpringClouds原生注解,启动熔断降级服务
feign.hystrix.enabled 是否开启hystrix,true:开启, false:不开启

FeignClient编写

在api模块创建feignClient接口,示例代码如下:

package com.primeton.eos.feign;

import com.primeton.eos.po.Stock;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;


@FeignClient(name = "STOCK-MANAGE", configuration = FeignAutoConfiguration.class, fallback = StockServerFallback.class)
public interface StockServerFeignClient {
    @RequestMapping(value = "/api/stock/{versionIds}", method = GET)
    Stock[] listStockByProductVersionIds(@PathVariable("versionIds") String versionIds);

    @RequestMapping(value = "/api/stock/bulk-delete", method = POST)
    void batchStockByVersionIds(@RequestBody Long[] versionIds);

    @RequestMapping(value = "/api/stock/{versionId}", method = POST)
    void deleteStockByVersionId(@PathVariable("versionId") Long versionId);
}

创建对应的fallback

@Component
public class StockServerFallback implements StockServerFeignClient {
    @Override
    public Stock[] listStockByProductVersionIds(String versionIds) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return null;
    }

    @Override
    public void batchStockByVersionIds(Long[] versionIds) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return;
    }

    @Override
    public void deleteStockByVersionId(Long versionId) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return;
    }
}

# 服务调用端

在服务调用端的pom中引入依赖

在pom文件中引入provider-api模块即可。

在开发中的用法

    @Autowired
    private StockServerFeignClient feignClient;

    public void delete(String[] productIds) {
        if (productIds == null) {
            return;
        }
        mapper.deleteBatch(productIds);
        versionMapper.deleteBatchByProductIds(productIds);

        Map<String,Object> param = new HashMap<>();
        List<Long> versionIds = new ArrayList<>();
        Arrays.stream(productIds).forEach(productId -> {
            param.put("productId",productId);
            List<ProductVersionVO> list = versionMapper.listByProductId(param);
            getVersionIds(list, versionIds);
        });

        //系统间不同应用调用:同步删除对应库存信息
        feignClient.batchStockByVersionIds(versionIds.toArray(new Long[versionIds.size()]));
    }

# 通过Java代码调用其他系统服务

本章节描述不同系统内的微应用服务的Java代码调用规范:

  • provider:服务提供端无需做特殊处理,正常提供rest api即可。
  • consumer:服务调用端通过restTemplate请求provider所在系统的网关,然后由网关route转发到provider。

# provider相关配置

服务提供者,发布api并授权,获取订阅码

服务提供者通过governor发布到当前系统中的网关上,并授权给服务消费者以及生成服务订阅凭证,服务发布操作如下:

服务发布

服务订阅授权:

服务订阅授权

网关配置路由:

网关配置路由

# consumer相关配置

订阅凭证和网关path配置

因为服务提供端和服务调用端分属不同的系统,运行期不能直接对服务进行访问(会被拦截,无权访问),需要通过网关路由转发服务调用请求,因此需要配置网关的PATH,供服务调用端发送服务请求,调用服务提供端发布的API。在运行期,网关PATH和订阅凭证可能会变更,因此不宜在代码中写死,需要使用Governor微服务管理平台提供的能力对应用中的网关PATH配置进行动态刷新。要使用Governor的配置能力。

Governor配置

配置完成后,在开发中使用@RefreseScope+@Value注解读取配置即可,如配置有变化,应用会监听配置进行热更新处理。

增加subkey配置类

ApiSubKeyConfig:用来配置apisubkey,跨系统调用api需要在header中填加apisubkey用来给网关验证调用者是否有该api调用的权限,subkey可选择存储在数据库、配置中心、配置文件等等,存储方式可自定义,用法唯一,本示例是根据subkey配置在配置文件中写的(支持Nacos热更新),如来源是其他地方,只需添加对应subkey获取方式即可。

package com.primeton.eos.config;

import com.primeton.eos.dap.sdk.api.webclient.SDKApiSubscriberProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@RefreshScope
public class ApiSubKeyConfig implements SDKApiSubscriberProvider {

    @Value("${provider.gateway.path}")
    private String gatewayPath;

    @Value("${product-stock.subkey}")
    private String subkey;

    @Override
    public ApiSubscriber getSubscriber(String host, String requestPath) {
        if (gatewayPath.indexOf(host) != -1) {
            return new ApiSubscriber(subkey);
        } else {
            return new ApiSubscriber();
        }
    }
} 

使用sdkRestTemplate进行远程调用:

@Service
@RefreshScope
public class OrderServiceImpl {

    @Value("${provider.gateway.path}")
    private String gatewayPath;

	@Value("${product-stock.subkey}")
    private String subkey;

    @Autowired
	@Qualifier("sdkRestTemplate")   //sdkRestTemplate
	@LoadBalanced
	private RestTemplate sdkRestTemplate;
	
	@Override
	public OrderVO queryOrderDetail(Long orderId) {
	    OrderVO vo = new OrderVO();
	    Order order = orderRepository.findById(orderId).orElse(null);
	
	    try {
	        String versionIds = getVersionIds(order);
	        //跨系统调用
	        List<ProductVersionVO> products =
	                Arrays.asList(sdkRestTemplate.getForObject(gatewayPath + API_PATH_PRODUCT_LIST_PRODUCT_VERSION + "/{versionIds}", ProductVersionVO[].class, versionIds));
	        vo.setProducts(products);
	    } catch (Exception e) {
	        //记录日志
	        logger.error("Error queryOrderDetail in '{}', error info '{}'", orderId, e.getMessage(), e);
	    }
	
	    BeanUtils.copyProperties(order, vo);
	    return vo;
	}
}
上次更新: 2023/3/20下午3:44:43