Java实现系统统一对外开放网关入口设计_程序员小强的博客-CSDN博客_java 网关

Java系统开放接口统一网关设计

本文主要讲解开放接口设计,主要是以SpringBoot web 项目,基于自定义注解+反射+非对称加密RSA签名等实现的灵活的统一开放接口设计,文末附源码地址。

1.背景

互联网公司随着业务的发展,系统间或多或少会开放一些对外接口,这些接口都会以API的形式提供给外部。
为了方便统一管理,统一鉴权,统一签名认证机制,流量预警等引入了统一网关。API网关是一是对外接口唯一入口。
在这里插入图片描述

2.开放接口需要有哪些功能?需要注意什么?

除了业务功能还需要有

  • 统一鉴权
  • 流量监控
  • 路由分发
  • 架构简单易懂,方便维护
  • 方便追加功能,不需要重复写鉴权等逻辑代码

对外开放的接口,数据安全性是第一位

  • 如何保证安全通信,防止数据被恶意篡改等攻击呢?
  • 怎么证明是你发的请 求呢?

比较流行的方式一般是

  • 加密 -> 密文传输,接收方需要解密
  • 加签 -> 双方建立秘钥-请求方加签,接收方验签防止数据中途被篡改

# 3.开放接口设计 本文用到的主要技术点

  • 反射机制
  • RSA签名算法
  • SpringBoot
  • Hibernate-validator注解式参数校验

3.1统一网关接口参数

公共参数

参数

类型

是否必填

最大长度

描述

app_id

String

32

业务方appId

method

String

200

请求方法

version

String

10

默认:1.0

api_request_id

String

32

随机请求标识,用于区分每一次请求

charset

String

16

默认:UTF-8

sign_type

String

10

签名类型:RSA或RSA2

sign

String

-

签名

content

String

-

业务内容 :json 格式字符串

返回内容

参数

类型

是否必填

最大长度

描述

success

boolean

16

是否成功

data

Object

-

返回业务信息(具体见业务接口)

error_code

String

10

错误码(success为false时必填)

error_msg

String

128

错误信息码(success为false时必填)

**

3.1签名规则

这里是使用的是RSA签名
规则如下:

  • 签名参数剔除sign_type 、 sign ;
  • 将剩余参数第一个字符按照ASCII码排序(字母升序排序),遇到相同字母则按第二个字符ASCII码排序,以此类推;
  • 将排序后的参数按照组合“参数=参数值”的格式拼接,并用&字符连接起来,生成的字符串为待签名字符串;
  • 使用RSA算法通过私钥生成签名

RSA === SHA1 --> base64
RSA2 === SHA256 --> base64

3.3代码实践

注:源码见文章末,依赖这里只简单列出了父pom中部分依赖,,详见源码
工程结构图:在这里插入图片描述

3.3.1Maven依赖

注:依赖这里只列举了一部分,文末附源码地址
open-api-project > pom

<properties>
        <project.version>1.0.0</project.version>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <fasterxml.version>2.10.2</fasterxml.version>
        <commons-lang3.version>3.4</commons-lang3.version>
        <commons-collections.version>3.2.2</commons-collections.version>
        <fastjson.version>1.2.70</fastjson.version>
        <slf4j.version>1.7.25</slf4j.version>
        <guava.version>21.0</guava.version>
        <javax.servlet.version>3.1.0</javax.servlet.version>
        <alipay-sdk.version>4.8.10.ALL</alipay-sdk.version>
        <hutool.version>5.0.0</hutool.version>
        <lombok.version>1.16.4</lombok.version>
        <hibernate-validator.version>5.4.1.Final</hibernate-validator.version>
    </properties>

    <!-- 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>${fasterxml.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </dependency>
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>${commons-collections.version}</version>
            </dependency>

            <!--fastjson json-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
            </dependency>

            <!-- guava -->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>

            <!-- 校验工具 -->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>${hibernate-validator.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alipay.sdk</groupId>
                <artifactId>alipay-sdk-java</artifactId>
                <version>${alipay-sdk.version}</version>
            </dependency>

            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- lombok   -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

open-api-web > pom

<dependencies>
        <dependency>
            <groupId>com.open.api</groupId>
            <artifactId>open-api-common</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- spring boot starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-validation</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3.3.2配置文件

server.port=8821

#日志配置
logging.level.root=WARN
logging.level.net.sf=WARN
logging.level.com.open.api=debug


#是否校验签名
open.api.common.key.isCheckSign=true

#开放接口公钥
open.api.common.key.publicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwdK0le7UypaYWEWBoQkGTpu2nlYnM+iX8pa7Gz9neSnANfcuxrMgmmXrG+Dw6f3OQpiHl4mbKelyVjJTBLh4cvo1am2OSZvBjefZNshphx4ctBtx6BpGIRwlTvJRsjajMCY3RyF6px+Ehz0zeDBf7w2M6GZnSv2YhPp2YIZZo/01GYVJ4RgzzfkEEKyC+96+shqANHVOaiiG4byMJL8zv9q3kshSNCA1NT8r7toq8wPYhUKwCas/i5GauyRCIX+KhCpD9+/HTkFmr0PUWoNIZ61lRpTMbiTfDWU/5tJ3UPwdk6oVM3ZwkLoBAO8HXHvPk6avCupXq63u4wGrn30eiwIDAQAB

#开放接口私钥
open.api.common.key.privateKey=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDB0rSV7tTKlphYRYGhCQZOm7aeVicz6JfylrsbP2d5KcA19y7GsyCaZesb4PDp/c5CmIeXiZsp6XJWMlMEuHhy+jVqbY5Jm8GN59k2yGmHHhy0G3HoGkYhHCVO8lGyNqMwJjdHIXqnH4SHPTN4MF/vDYzoZmdK/ZiE+nZghlmj/TUZhUnhGDPN+QQQrIL73r6yGoA0dU5qKIbhvIwkvzO/2reSyFI0IDU1Pyvu2irzA9iFQrAJqz+LkZq7JEIhf4qEKkP378dOQWavQ9Rag0hnrWVGlMxuJN8NZT/m0ndQ/B2TqhUzdnCQugEA7wdce8+Tpq8K6lerre7jAauffR6LAgMBAAECggEBAIv3vF9V5KcUD5oXP6BqIvrbagp3zsGmoywVe7MWm4OdCeguw8HME6xME3fDflaL6cqf2bMuNTYUFnR2zQroqFrno3FjAlDXwPPYTT1JhyODNFlARIbHioNYjvyu8x5OZJRd1KdyXt+XXB5JrQSLcovwbiRZ5xf5gI3vTVMxUkSgTxf2P2smaXLZ2k6epSlvFr8u+SJaGOgjKCvbGf+jXyL0L8kntukNLocnSXU3sfFmAmd87DxPFdXAFDnS09tWOcjHfIZmwjHMX3qVP/2jj1DWOjIW0Ow/VRegbYTpLmSQTMcOUaFRprvwd0ZKaZ0aQMNPqPrqkHzrfQsnfjxY+akCgYEA+vbt40rIBDsN5HCPGbyDBU0/+A3wsGh4nqvY9JKACaMpg/FvyMz37GpL8AOMy/mUCVXjVyMoNUFZf/fEhMblBuYQBmgMVk1IQaVESvUlZ33Vgot0TU8YHY2Hpk541e3vKL0X0X6XLgS6CZ63cMx04uZxoFEWlJJm/qqLru3MQp8CgYEAxbZFVgnQ9XTQlHHgpUiS/R7qHo5joBDzlF+m29CYplI5nJUmntoChnHZ6RBoiPW58A3NJOlfIL6J2+Mwwd6kHWD2DSwVDRfk7Hb5Dw6o+tOf+In+zuPZtopr6L7oiKQtwXtGV88ZhDdqX9z5ge9EYP4Psd0Mfchv5vkJENreqJUCgYEAw8easTQHcXV4UvuURymOtLYc7zBA0f3OC0pYiAM5q0sD+hCBeg6cYmxSLT03u3BKEjZUkge1OEZwwanSPxrCVG1plvXYmgLUGZIKAsfXlDLQO3T7F8xaLcPZTN3u2kUxy4Aocp/k5Ft/nj2ZMX/ut4u6nKxlhyXm/0igi6irLlUCgYAWpalNkLRJ2Yam6mB0Llr/+ZGRzHem9yofndFMLpm9u39z6zXQTmKpqdLvOnzu607QK5SLHNxTsN+zu1NzcaBU6S1mFt2WcV08pOgkjGZYzPLvEkeIxVrD6RkxQOT7+epv1kIZftSKa5qYvoQqGRE5FwEPO6XZpqMCzxX1w0xr/QKBgDeom9MiI9a125jr/n9ghSWfvaxCXgPrdojr8QZrlo028iT711ND8QUwCbb9GDr+pyXesANCm78zhdltfeNFimEUktyS0F8Li2+GbYjTvLNXtwxTKZcOXRR+MC5bMHq4hY/+71NhnGazy3yidHn0doReezqGvkotJuRJSr+l1qmU

3.3.3自定义注解

作用说明:自定义注解主要用于标记开放接口
开放接口方法注解

/**
 * 开放接口注解
 *
 * @author 程序员小强
 */
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {

    /**
     * api 方法名
     */
    String method();

    /**
     * 方法描述
     */
    String desc() default "";
}

开放接口实现类注解

/**
 * 开放接口实现类注解
 *
 * @author 程序员小强
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpenApiService {
}

3.3.4API接口初始化容器与扫描器

作用说明:在项目启动的时候,将添加自定义注解的实现加载到容器

/**
 * Api 初始化容器
 */
@Service
public class ApiContainer extends HashMap<String, ApiModel> {
}
/**
 * api接口对象
 */
public class ApiModel {

    /**
     * 类 spring bean
     */
    private String beanName;

    /**
     * 方法对象
     */
    private Method method;

    /**
     * 业务参数
     */
    private String paramName;

    public ApiModel(String beanName, Method method, String paramName) {
        this.beanName = beanName;
        this.method = method;
        this.paramName = paramName;
    }
    //省略 get/set 
}
//导包 太长,这里省略了,详见源码

/**
 * Api接口扫描器
 *
 * @author 程序员小强
 */
@Component
public class ApiScanner implements CommandLineRunner {


    private static final Logger LOGGER = LoggerFactory.getLogger(ApiScanner.class);

    /**
     * 方法签名拆分正则
     */
    private static final Pattern PATTERN = Pattern.compile("\\s+(.*)\\s+((.*)\\.(.*))\\((.*)\\)", Pattern.DOTALL);

    /**
     * 参数分隔符
     */
    private static final String PARAMS_SEPARATOR = ",";

    /**
     * 统计扫描次数
     */
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    @Resource
    private ApiContainer apiContainer;

    @Override
    public void run(String... var1) throws Exception {
        //扫描所有使用@OpenApiService注解的类
        Map<String, Object> openApiServiceBeanMap = ApplicationContextHelper.getBeansWithAnnotation(OpenApiService.class);

        if (null == openApiServiceBeanMap || openApiServiceBeanMap.isEmpty()) {
            LOGGER.info("open api service bean map is empty");
            return;
        }

        for (Map.Entry<String, Object> map : openApiServiceBeanMap.entrySet()) {
            //获取扫描类下所有方法
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(map.getValue().getClass());
            for (Method method : methods) {
                atomicInteger.incrementAndGet();
                //找到带有OpenApi 注解的方法
                OpenApi openApi = AnnotationUtils.findAnnotation(method, OpenApi.class);
                if (null == openApi) {
                    continue;
                }
                //获取业务参数对象
                String paramName = getParamName(method);
                if (StringUtils.isBlank(paramName)) {
                    LOGGER.warn("Api接口业务参数缺失 >> method = {}", openApi.method());
                    continue;
                }

                //组建ApiModel- 放入api容器
                apiContainer.put(openApi.method(), new ApiModel(map.getKey(), method, paramName));
                LOGGER.info("Api接口加载成功 >> method = {} , desc={}", openApi.method(), openApi.desc());
            }
        }
        LOGGER.info("Api接口容器加载完毕 >> size = {} loopTimes={}", apiContainer.size(), atomicInteger.get());
    }

    /**
     * 获取业务参数对象
     *
     * @param method
     * @return
     */
    private String getParamName(Method method) {
        ArrayList<String> result = new ArrayList<>();
        final Matcher matcher = PATTERN.matcher(method.toGenericString());
        if (matcher.find()) {
            int groupCount = matcher.groupCount() + 1;
            for (int i = 0; i < groupCount; i++) {
                result.add(matcher.group(i));
            }
        }

        //获取参数部分
        if (result.size() >= 6) {
            String[] params =
                    StringUtils.splitByWholeSeparatorPreserveAllTokens(result.get(5), PARAMS_SEPARATOR);
            if (params.length >= 2) {
                return params[1];
            }
        }
        return null;
    }

}

3.3.5API请求处理客户端

作用说明:统一网关controller中调用一下方法进行验签与执行相应的业务方法。

//导包 太长,这里省略了,详见源码
/**
 * Api请求客户端
 *
 * @author 程序员小强
 */
@Service
public class ApiClient {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ApiClient.class);

    /**
     * jackson 序列化工具类
     */
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    /**
     * Api本地容器
     */
    private final ApiContainer apiContainer;

    public ApiClient(ApiContainer apiContainer) {
        this.apiContainer = apiContainer;
    }


    @Resource
    private ApplicationProperty applicationProperty;

    /**
     * 验签
     *
     * @param params          请求参数
     * @param requestRandomId 请求随机标识(用于日志中分辨是否是同一次请求)
     * @param charset         请求编码
     * @param signType        签名格式
     * @author 程序员小强
     */
    public void checkSign(Map<String, Object> params, String requestRandomId, String charset, String signType) {

        try {
            //校验签名开关
            if (!applicationProperty.getIsCheckSign()) {
                LOGGER.warn("【{}】>> 验签开关关闭", requestRandomId);
                return;
            }

            //map类型转换
            Map<String, String> map = new HashMap<>(params.size());
            for (String s : params.keySet()) {
                map.put(s, params.get(s).toString());
            }

            LOGGER.warn("【{}】 >> 验签参数 {}", requestRandomId, map);
            boolean checkSign = AlipaySignature.rsaCheckV1(map, applicationProperty.getPublicKey(), charset, signType);
            if (!checkSign) {
                LOGGER.info("【{}】 >> 验签失败 >> params = {}", requestRandomId, JSON.toJSONString(params));
                throw new BusinessException(ApiExceptionEnum.INVALID_SIGN);
            }
            LOGGER.warn("【{}】 >> 验签成功", requestRandomId);

        } catch (Exception e) {
            LOGGER.error("【{}】 >> 验签异常 >> params = {}, error = {}",
                    requestRandomId, JSON.toJSONString(params), ExceptionUtils.getStackTrace(e));
            throw new BusinessException(ApiExceptionEnum.INVALID_SIGN);

        }

    }


    /**
     * Api调用方法
     *
     * @param method          请求方法
     * @param requestRandomId 请求随机标识
     * @param content         请求体
     * @author 程序员小强
     */
    public ResultModel invoke(String method, String requestRandomId, String content) throws Throwable {
        //获取api方法
        ApiModel apiModel = apiContainer.get(method);

        if (null == apiModel) {
            LOGGER.info("【{}】 >> API方法不存在 >> method = {}", requestRandomId, method);
            throw new BusinessException(ApiExceptionEnum.API_NOT_EXIST);
        }

        //获得spring bean
        Object bean = ApplicationContextHelper.getBean(apiModel.getBeanName());
        if (null == bean) {
            LOGGER.warn("【{}】 >> API方法不存在 >> method = {}, beanName = {}", requestRandomId, method, apiModel.getBeanName());
            throw new BusinessException(ApiExceptionEnum.API_NOT_EXIST);
        }

        //处理业务参数
        // 忽略JSON字符串中存在,而在Java中不存在的属性
        JSON_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        // 设置下划线序列化方式
        JSON_MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        Object result = JSON_MAPPER.readValue(content, Class.forName(apiModel.getParamName()));

        //校验参数
        ValidateUtils.validate(result);

        //执行对应方法
        try {
            Object obj = apiModel.getMethod().invoke(bean, requestRandomId, result);
            return ResultModel.success(obj);
        } catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                throw ((InvocationTargetException) e).getTargetException();
            }
            throw new BusinessException(ApiExceptionEnum.SYSTEM_ERROR);
        }

    }
}

3.3.6启动类

/**
 * 统一网关平台-启动类
 *
 * @author 程序员小强
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.3.7统一异常处理

/**
 * 统一异常处理
 *
 * @author 程序员小强
 */
@Slf4j
@ControllerAdvice
@EnableAspectJAutoProxy
public class ExceptionAdvice {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAdvice.class);

    @CrossOrigin
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public ResultModel defaultExceptionHandler(Exception exception, HttpServletResponse response) {
        ResultModel result;

        try {
            LOGGER.warn("全局业务处理异常 >> error = {}", exception.getMessage(), exception);
            throw exception;
        } catch (BusinessException e) {
            result = ResultModel.error(e.getCode(), e.getMsg());

        } catch (HttpRequestMethodNotSupportedException e) {
            String errorMsg = MessageFormat.format(ApiExceptionEnum.INVALID_REQUEST_ERROR.getMsg(), e.getMethod(), e.getSupportedHttpMethods());
            result = ResultModel.error(ApiExceptionEnum.INVALID_REQUEST_ERROR.getCode(), errorMsg);

        } catch (MissingServletRequestParameterException e) {
            String errorMsg = MessageFormat.format(ApiExceptionEnum.INVALID_PUBLIC_PARAM.getMsg(), e.getMessage());
            result = ResultModel.error(ApiExceptionEnum.INVALID_PUBLIC_PARAM.getCode(), errorMsg);
        } catch (Exception e) {
            result = ResultModel.error(ApiExceptionEnum.SYSTEM_ERROR.getCode(), ApiExceptionEnum.SYSTEM_ERROR.getMsg());

        }
        return result;
    }
}

3.3.8统一网关controller

//导包 太长,这里省略了,详见源码

/**
 * 统一网关
 */
@RestController
@RequestMapping("/open")
public class OpenApiController {

    private static final Logger LOGGER = LoggerFactory.getLogger(OpenApiController.class);

    @Autowired
    private ApiClient apiClient;


    /**
     * 统一网关入口
     *
     * @param method       请求方法
     * @param version      版本
     * @param apiRequestId 请求标识(用于日志中分辨是否是同一次请求)
     * @param charset      请求编码
     * @param signType     签名格式
     * @param sign         签名
     * @param content      业务内容参数
     * @author 程序员小强
     */
    @PostMapping("/gateway")
    public ResultModel gateway(@RequestParam(value = "app_id", required = true) String appId,
                               @RequestParam(value = "method", required = true) String method,
                               @RequestParam(value = "version", required = true) String version,
                               @RequestParam(value = "api_request_id", required = true) String apiRequestId,
                               @RequestParam(value = "charset", required = true) String charset,
                               @RequestParam(value = "sign_type", required = true) String signType,
                               @RequestParam(value = "sign", required = true) String sign,
                               @RequestParam(value = "content", required = true) String content,
                               HttpServletRequest request) throws Throwable {

        Map<String, Object> params = WebUtils.getParametersStartingWith(request, StringPool.EMPTY);
        LOGGER.info("【{}】>> 网关执行开始 >> method={} params = {}", apiRequestId, method, JSON.toJSONString(params));
        long start = SystemClock.millisClock().now();

        //验签
        apiClient.checkSign(params, apiRequestId, charset, signType);

        //请求接口
        ResultModel result = apiClient.invoke(method, apiRequestId, content);

        LOGGER.info("【{}】>> 网关执行结束 >> method={},result = {}, times = {} ms",
                apiRequestId, method, JSON.toJSONString(result), (SystemClock.millisClock().now() - start));

        return result;
    }


}

3.3.9测试实战DEMO

入参BO

/**
 * 使用注解做参数校验
 */
public class Test1BO implements Serializable {
    private static final long serialVersionUID = -1L;

    @Valid
    @NotEmpty(message = "集合不为空!")
    @Size(min = 1, message = "最小为{min}")
    private List<Item> itemList;
    
    //省略 get/set
    
    /**
     * 内部类
     */
    public static class Item {
        @NotBlank(message = "username 不能为空!")
        private String username;

        @NotBlank(message = "password 不能为空!")
        private String password;

        @NotBlank(message = "realName 不能为空!")
        private String realName;

        //省略 get/set
    }
}

测试service接口
注意:注解@OpenApi 使用 ,method就是入参中的方法

/**
 * 测试开放接口1
 */
public interface TestOneService {

    /**
     * 方法1
     */
    @OpenApi(method = "open.api.test.one.method1", desc = "测试接口1,方法1")
    void testMethod1(String requestId, Test1BO test1BO);
}

测试接口实现类

/**
 * 测试开放接口1
 * <p>
 * 注解@OpenApiService > 开放接口自定义注解,用于启动时扫描接口
 */
@Service
@OpenApiService
public class TestOneServiceImpl implements TestOneService {

    /**
     * 日志
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(TestOneServiceImpl.class);

    /**
     * 方法1
     */
    @Override
    public void testMethod1(String requestId, Test1BO test1BO) {
        LOGGER.info("【{}】>> 测试开放接口1 >> 方法1 params={}", requestId, JSON.toJSONString(test1BO));
    }
}

3.4项目启动与测试

访问地址:http://localhost:8821/open/gateway

3.4.1不验签连通性测试

注:为了方便调试先将配置文件中的验签开关修改为 false

open.api.common.key.isCheckSign=false

正常访问
在这里插入图片描述
异常情况
在这里插入图片描述
在这里插入图片描述

3.4.2验签测试

将配置文件验签开关开启

open.api.common.key.isCheckSign=true

由于签名是根据私钥+动态的参数根据规则生成的,在PostMan上测试还得先生成签名,在源码文件中有一个测试Demo如下在这里插入图片描述**
带签名正常访问
在这里插入图片描述
签名异常情况下访问在这里插入图片描述

4.总结与注意事项

  • 通过以上方式实现的统一网关,在后续添加业务的时候,只需要添加对应的service,并加好相应注解即可。
  • 目前配置的参数入参默认是下划线方式,如果业务中要用驼峰,可以改以下方法。

    • com.open.api.config.gateway.ApiClient#invoke
    • 目前配置的是,默认下划线方式,参考了大厂的开放接口,大部分都是下划线方式的参数
      在这里插入图片描述
  • 需要自定义业务接口请参考 3.3.9测试实战DEMO。

因为使用反射方式调用方法,所以自定义接口的时候也需要遵循规范,参数为2个
参数1 String requestId,
参数2 业务类型对象参数,实际是 网关入口,content 内容JSON转换后的对象参数。在这里插入图片描述

5.源码地址

源码地址:传送门

关注程序员小强公众号更多编程趣事,知识心得与您分享
在这里插入图片描述


原网址: 访问
创建于: 2021-08-26 11:28:43
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论