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的包来辅助简化代码逻辑,如:
JSONUtil
、JSONObject
、JSONArray
日志输出统一使用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方法
- 调用公共代码模块common_default
GroovyScriptUtil.invokeMethod(方法名,参数) - 调用自定义代码模块 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
}