API适配器代码位置

目的:

  • 为了归档适配器相关代码,通过Git进行版本管理;
  • 借助IDEA的Groovy的语法检查与调试运行,解决在线编码效率低
  • 单一脚本需要不断复制重复代码,修改复杂度高

从而设计了公共代码并开发调度辅助组件包,管理代码和提高开发效率。

演示目录结构

/src/main/groovy
建议:以每个各类或版本或模块建立子目录

规范约定

  • 在Java Maven工程中引入Groovy依赖包

       <parent>
            <artifactId>pai-common</artifactId>
            <groupId>cn.flyrise</groupId>
            <version>1.1.0-SNAPSHOT</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>cn.flyrise</groupId>
                <artifactId>pai-common-adapter-groovy-dev</artifactId>
                <!--直接运行groovy脚本时,需先注释掉 scope 声明,否则会提示找不到类-->
                <scope>provided</scope>
            </dependency>
        </dependencies>

    注意:

    • parent 必需是 1.1.0-SNAPSHOT版以上才支持
    • scope 要设置为 provided 最终打包时才不会包含进去
    • 配置完成后要对pom.xml做reimport
  • 将此目标设置为源码目录
    groovy 文件夹右键 > Make Directory as > Resources Root

  • 新建脚本文件
    groovy 文件夹右键 > New > Groovy Script

  • 编码规范

    • 整个适配器代码最终必需返回一个JSON结果字符串

    • 整个适配器代码以main方法为入口

    • 标准上下文中提供了4个内置对象:

      • param 适配器参数 JSONObject
      • data 接口请求参数 JSONObject
      • header 请求头 Map
      • cache 缓存对象 CacheUtil
    • 统一使用Hutool的包来辅助简化代码逻辑,如: JSONUtilJSONObjectJSONArray

    • 日志输出统一使用Hutool包提供的 StaticLog

    • 异常处理:

      • 全局默认会后捕获异常并输入到日志中心,在 Kibana 中可通过接口协议的 uri 检索定位日志信息,注意是 升序

        String uri = request.getRequestURI();
        log.error("请求失败(同步):{}", uri, ex);
        log.error("请求失败(异步):{}", uri, ex);
      • 局部异常处理, 需把异常往外抛终止代码往下执行

        try{
            // 代码略...
        } catch (RuntimeException re) {
            throw new BizException("500", "关键词 -> 错误信息", re)
        }

本地调试示例

示例代码

创建包名: helloworld.v1

测试参数 demo.json

/src/main/groovy/helloworld/v1/demo.json

{
  "param_desc": "适配器参数",
  "param": {
    "host": "192.168.32.1",
    "port": "8080"
  },
  "data_desc": "协议参数",
  "data": {
    "ip": "192.168.1.2"
  },
  "header_desc": "请求头参数",
  "header": {
    "name": "这是一个请求头的值"
  }
}

适配器代码 demo.groovy

/src/main/groovy/helloworld/v1/demo.groovy

package helloworld.v1

import cn.hutool.json.JSONUtil
import cn.hutool.json.JSONObject
import cn.hutool.log.StaticLog

/**
 * 主入口且必需返回JSON
 * @return JSON结果字符串
 */
String main() {
    // 请在这里编写业务逻辑
    StaticLog.debug("适配器代码主入口....")

    StaticLog.debug("******** 模板方法调用 ********")
    StaticLog.debug("header - name: {} ", header.get("name"))
    StaticLog.debug("data - ip: {} ", data.getStr("ip"))
    StaticLog.debug("param - host: {} ", param.getStr("host"))
    StaticLog.debug("param - port: {} ", param.getStr("port"))

    JSONObject result = JSONUtil.createObj().set("status", "OK")
    StaticLog.debug("******** 返回JSON结果 ********")
    StaticLog.debug(result.toString())

    return result
}

/**
 * 这里是主入口,千万不能删除,且main方法必需返回JSON 字符串
 */
main()

本地调试

/src/test/java/cn/flyrise/api/adapter/TestGroovyRunner.java
包名:cn.flyrise.api.adapter

完整代码

package cn.flyrise.api.adapter;

import cn.flyrise.common.adapter.GroovyRunner;
import org.junit.Test;

public class TestGroovyRunner {

    @Test
    public void testRun() throws Exception {
        GroovyRunner.run("helloworld/v1/demo");
    }

    @Test
    public void testDebug() throws Exception {
        GroovyRunner.debug("helloworld/v1/demo");
    }
}

代码分解

  • 调试方式1:直接通过 GroovyRunner 运行(推荐)
    通过GroovyRunner.run来仿真在线调试执行(必需先配置相应的json, 如:demo.json)

      @Test
      public void testRun() throws Exception {
          GroovyRunner.run("helloworld/v1/demo");
      }
  • 调试方式2:直接通过 Groovy脚本 运行
    通过GroovyRunner.debug辅助生成运行参数配置,并把配置添加到grovvy脚本后再右键运行脚本

    @Test
    public void testDebug() throws Exception {
        GroovyRunner.debug("helloworld/v1/demo");
    }

得到如下内容:

// ===================== 请将此代码置于要调试的groovy脚本文件开始处,提交前请删除 ====================
@groovy.transform.Field File debugJsonFile = cn.flyrise.common.adapter.GroovyScriptUtil.getJsonFile("helloworld/v1/demo")
@groovy.transform.Field cn.hutool.json.JSONObject debugJsonObject = cn.hutool.json.JSONUtil.parseObj(cn.hutool.core.io.FileUtil.readUtf8String(debugJsonFile))
@groovy.transform.Field cn.hutool.json.JSONObject param = debugJsonObject.getJSONObject("param")
@groovy.transform.Field cn.hutool.json.JSONObject data = debugJsonObject.getJSONObject("data")
@groovy.transform.Field Map<String, Object> header = debugJsonObject.getJSONObject("header").toBean(HashMap.class)
@groovy.transform.Field cn.flyrise.common.adapter.MemoryCacheUtil cache = new cn.flyrise.common.adapter.MemoryCacheUtil()
// ===================== 重要事情说三遍:提交前请删除!提交前请删除!提交前请删除! ====================
  • 将上面的结果贴到要调试的groovy代码开始处(import下方)
  • 修改pom.xml 把 pai-common-adapter-groovy-dev 依赖配置中的 provided 注释掉,否则会找不到类(注意:要记得reimport)
  • 直接在groovy文件右键 run “xxx” 或 debug “xxx” (xxx为groovy文件名称)
  • 这个方式可能会遇到 [CreateProcess error=206, 文件名或扩展名太长。]异常,因此还是建议用方式1,同样可在groovy代码中打断点调试

公共代码使用示例

GroovyScriptUtil方法

  1. 调用公共代码模块common_default
    GroovyScriptUtil.invokeMethod(方法名,参数)
  2. 调用自定义代码模块 common_自定义名称
    GroovyScriptUtil.invokeMethod(common_自定义名称,方法名)

common_default使用示例

在公共代码(common_default)中定义如下代码:

/src/main/groovy/helloworld/v1/common_default.groovy

package helloworld.v1

import cn.hutool.log.StaticLog


String login() {
    StaticLog.debug("common_default_common.default.login")
    return "common_default.common_default.login"
}

String logout() {
    StaticLog.debug("common_default_common.default.logout")
    return "common_default.common_default.logout"
}

在适配代码中调用

/src/main/groovy/helloworld/v1/demo.groovy

代码分解

  • 引入公共方法工具包

    import cn.flyrise.pai.apistore.util.GroovyScriptUtil
  • 增加公共方法

    login

    调用

    GroovyScriptUtil.invokeMethod("login")

完整代码

package helloworld.v1

import cn.hutool.json.JSONUtil
import cn.hutool.json.JSONObject
import cn.hutool.log.StaticLog

/**
 * 主入口且必需返回JSON
 * @return JSON结果字符串
 */
String main() {
    // 请在这里编写业务逻辑
    StaticLog.debug("适配器代码主入口....")

    StaticLog.debug("******** 模板方法调用 ********")
    StaticLog.debug("header - name: {} ", header.get("name"))
    StaticLog.debug("data - ip: {} ", data.getStr("ip"))
    StaticLog.debug("param - host: {} ", param.getStr("host"))
    StaticLog.debug("param - port: {} ", param.getStr("port"))

    // 调用公共代码模块common_default
    GroovyScriptUtil.invokeMethod("login")

    JSONObject result = JSONUtil.createObj().set("status", "OK")
    StaticLog.debug("******** 返回JSON结果 ********")
    StaticLog.debug(result.toString())

    return result
}

/**
 * 这里是主入口,千万不能删除,且main方法必需返回JSON 字符串
 */
main()

运行结果

[2021-08-16 12:43:20] [DEBUG] org.codehaus.groovy.reflection.CachedMethod: common_default_common.default.login

common_自定义名称使用示例

在公共代码(common_自定义名称)中定义如下代码:

/src/main/groovy/helloworld/v1/common_custom.groovy

package helloworld.v1

import cn.hutool.log.StaticLog

String method1() {
    StaticLog.debug("helloworld.v1.common_custom.method1")
    return "helloworld.v1.common_custom.method1"
}

String method2() {
    StaticLog.debug("helloworld.v1.common_custom.method2")
    return "helloworld.v1.common_custom.method2"
}

在适配代码中调用:

/src/main/groovy/helloworld/v1/demo.groovy

代码分解

  • 引入公共方法工具包

    import cn.flyrise.pai.apistore.util.GroovyScriptUtil
  • 增加公共方法

    method1

    调用

    GroovyScriptUtil.invokeMethod("common_custom", "method1")

完整代码

package helloworld.v1

import cn.hutool.json.JSONUtil
import cn.hutool.json.JSONObject
import cn.hutool.log.StaticLog

/**
 * 主入口且必需返回JSON
 * @return JSON结果字符串
 */
String main() {
    // 请在这里编写业务逻辑
    StaticLog.debug("适配器代码主入口....")

    StaticLog.debug("******** 模板方法调用 ********")
    StaticLog.debug("header - name: {} ", header.get("name"))
    StaticLog.debug("data - ip: {} ", data.getStr("ip"))
    StaticLog.debug("param - host: {} ", param.getStr("host"))
    StaticLog.debug("param - port: {} ", param.getStr("port"))

    GroovyScriptUtil.invokeMethod("login")
    GroovyScriptUtil.invokeMethod("common_custom", "method1")

    JSONObject result = JSONUtil.createObj().set("status", "OK")
    StaticLog.debug("******** 返回JSON结果 ********")
    StaticLog.debug(result.toString())

    return result
}

/**
 * 这里是主入口,千万不能删除,且main方法必需返回JSON 字符串
 */
main()

运行结果

[2021-08-16 12:43:20] [DEBUG] org.codehaus.groovy.reflection.CachedMethod: helloworld.v1.common_custom.method1

附录

缓存工具 - CacheUtil

  • 写入缓存 ValueOperations<String, T> set(String key, T value, Integer timeoutSeconds)

    timeoutSeconds 为 -1 是表示永不过期,但不推荐

  • 读取缓存 T get(String key)

调用示例


String main() {
    // 写入
    cache.set("demo:name","zhangsan", 120)
    // 读取
    cache.get("demo:name")
}

业务重试实现 - Retry

定义公共重试方法

/**
 * 这里的代码也许写的比较罗嗦,我们是为了赋予三个区间含义:
 * 1)如果重试次数小于零,将无限重试;
 * 2)如果重试次数等于零,将执行一次(而不重试)。
 * 3)如果重试次数大于零,将执行 times + 1 次(即重试 times 次)
 */
static def retry(int times = 5, Closure errorHandler = { e-> e.printStackTrace()}, Closure body) {
    int retries = -1
    while(times < 0 || ++retries <= times) {
        try {
            return body.call()
        } catch(e) {
            errorHandler.call(e)
        }
    }
    //throw new Exception("Failed after $times retries!")
}

调用示例

retry(3) {
    println("retry 1 ....")
    def i = 2/0
}

retry(2,{e-> println(e.getMessage())}) {
    println("retry 2....")
    def i = 2/0
}
文档更新时间: 2023-07-31 09:39   作者:姚连洲