高代码开发

一、准备工作

下载并安装Maven插件 https://maven.apache.org/install.html 下载并安装JDK1.8尽量使用jdk8,否则部分功能可能会有兼容问题
安装开发IDE(例如IDEA、VSCode、Eclipse等)
申请开发者证书(纷享销客网页版-后台管理-定制开发平台/开发者证书)
  • 申请证书时需要给企业ID、员工ID,该证书会与租户以及员工进行绑定
  • 每次申请的证书有效期为30天/60天/90天/长期有效
  • 证书绑定的员工如果停用、禁止登录或者修改密码,该证书10分钟左右失效
  • 长期有效证书特殊说明:证书被删除、90天内从未使用、证书绑定员工修改密码等也会导致证书失效
注:920版本目前正在灰度,该版本由于底层变动不向下兼容,后续所有脚手架都采用该版本---920脚手架需要与920版开发者证书搭配使用;历史版本升级可通过手动下载下方maven插件更新
  • 解压脚手架,建议解压到企业帐号/企业信息目录下,例如D:\workspace\facishare, 得到以下目录结构
|-- lib apl-sdk-maven-plugin.jar maven插件 fs-paas-function-api.jar 代码依赖的api包 |-- src |-- main |-- java |-- fx |-- custom |-- apl |-- jar 代码示例 |-- resources application.properties 租户开发证书 pom.xml README.md 本说明文件
  • 打开IDE导入项目

二、环境准备工作

修改pom.xml中的 artifactId为Jar包ApiName
apiName 命名规则:
1.只允许英文字母开头
2.中间允许使用英文字母,数字,下划线
3.下划线不允许连续出现,生成后会在apl平台以__c结尾显示
4.不能超过50个字符

设置开发者证书

首次安装apl插件

  • IDE进入控制台
  • 在控制台输入以下命令
mvn install:install-file "-Dpackaging=maven-plugin" "-Dfile=lib/apl-sdk-maven-plugin.jar" "-DgroupId=com.facishare" "-DartifactId=apl-sdk-maven-plugin" "-Dversion=1.0-SNAPSHOT"
920最新插件-该插件与历史版本不兼容,后续所有更新可通过下方命令自动更新
apl-sdk-maven-plugin.jar
7.8 MB
安装apl插件后,在maven项目的plugin中可以看到目前支持的插件列表
所有插件可通过双击该插件,或maven命令行操作;如mvn apl-sdk:sdk
插件功能说明
1. apl-sdk:pluginUpgrade  该命令作用为apl插件的自更新,函数有新的插件功能时可以通过该命令更新

2. apl-sdk:pluginUpgrade-dev  该命令作用同样为apl插件的自更新(包含灰度测试阶段的插件)

3. apl-sdk:pushJar  该命名作用为上传jar包,需要提前手动对项目进行打包,通过该命令将该包上传到函数服务器中

4. apl-sdk:pkgAndPushJar  该命令是pushJar的升级版,在上传jar包前会自动进行打包,简化上传jar包操作

5.apl-sdk:sdk  该命令是用来下载/升级api-jar包的,首先会拉取最新版本api包自动下载到lib/fs-paas-function-api.jar,其次会进行api-jar包打包到本地mvn仓库,最后会进行项目打包,生成新的项目jar包

6.apl-sdk:sdk-dev 该命令是用来下载/升级api-jar“灰度包”的,包含函数内部灰度的一些apu功能也会在该jar包体现
注:
1.该插件需要配置mvn环境变量配置,支持全局mvn命令操作
2.windows电脑如果使用插件报错"CreateProcess error=2, 系统找不到指定的文件",尝试使用上方的插件自更新命令尝试解决;如果更新后仍无效则联系技术同学支持
3.如果安装了新版本插件后,仍没有图中插件提示,可以清理idea缓存(File/Invalidate Caches),重新加载maven后查看;
4.当前项目idea的maven配置,需要与电脑中的mvn环境配置,保持一致,否则下载sdk会失败

首次安装sdk(下载函数api依赖jar包)

通过上方插件的apl-sdk:sdk /apl-sdk:sdk-dev来进行函数api jar包的下载与升级
该命令是用来升级api-jar包的,首先会拉取最新版本api包自动下载到lib/fs-paas-function-api.jar,其次会进行api-jar包打包到本地mvn仓库,最后会进行项目打包,生成新的项目jar包;
通过该命令下载/升级函数sdk
如果idea有缓存没有显示插件列表,可以通过如下命令行进行操作
mvn apl-sdk:sdk
下载/升级结果
版本一致则无需升级/下载
版本不一致升级/下载并打包

三、开始开发

3.1 新建代码

在fx.custom.apl.jar下新建代码
实现函数的命名空间对应的接口,所有已支持的接口在 com.fxiaoke.functions.template, 如下图
各个命名空间对应接口的实现功能example如下
一个新的APL代码新建就好了,如下图所示
增加main函数,以方便进行代码调试
DebugHelper helper = new DebugHelper(); //构造一个调试器
helper.init(); //对调试器进行初始化
FunctionContext context = helper.context("AccountObj", "63100e7915d6a300017121cc"); //获取一个调试上下文,以方便写代码
Map argument = Maps.newHashMap(); //如果函数需要有其他参数,可以构造一个Map,进行参数传递
最后效果如下图

3.2 开始开发

  • 在代码中,可以使用Fx进行对象的查询、修改、以及新增,例如 QueryResult ret = Fx.object.select(String.format("SELECT _id, name FROM AccountObj WHERE name = '%s'", search)).result();
  • 在代码需要按照接口的约定,进行参数返回,如下图所示

3.3 调试

  • 点击main函数最左边绿色的箭头,可以对进行项目的编译以及调试
  • 可以通过IDE,对代码进行断点分析,如下图所示

四、将代码发布到租户

发布
方式1:自动打包并上传
打开插件,找到Plugins的apl-sdk插件,点击apl-sdk:pushJar,会自动打包并上传jar包
方式2:手动打包并上传
部分场景需要手动打包,打开IDE的Maven插件. 找到Lifecycle中的,package/install进行打包,如图所示;或者直接通过命令行mvn clean package 或者 mvn clean install打包
找到Plugins的apl-sdk插件,点击apl-sdk:pushJar,进行Jar包上传,如图所示
开发Jar管理
  1. Jar包上传时进行默认安装,如果代码存在编译问题,会在上传阶段提示报错信息,请按照提示信息进行修改代码后重新上传代码
  2. Jar包管理后台地址:纷享销客网页版/后台管理/开发Jar包管理,也可在后台直接搜索开发Jar包管理
  3. 可以在Jar包管理根据artifactId找到刚才开发Jar的ApiName
  4. 更新Jar包方式,1)直接本地修改后重新上传Jar包;2)可以通过Jar包管理平台进行手动卸载并安装Jar包
手动上传并安装/卸载
代码上传报错提示
关联场景
找到需要执行代码的地方,新建函数
选择Jar包函数. ApiName输入 类名 + __c结尾,以便定位到具体类名. 新建函数的命名空间、返回值与类对应的接口必须一致,否则会执行失败
新建完成后,就可以进行验证

五、注意事项

首次安装成功后,第二次推送Jar包,会直接更新Jar包内容,同时也会直接影响租户使用.
实施/用户按照标准代码管理规范,进行代码分支管理.
Jar限制大小为10兆,切勿将第三方Jar包一并打包上传,如需上传第三方Jar包,请到管理后台进行上传.
APL自带FastJson以及Guava等通用工具包,如果上传了通用工具包,会以APL优先. 用户证书30天有效,如果用户停用、禁止登录、修改密码会失效,需要重新申请

六、代码Demo

这里举例个别代码示例,如按钮前验证代码,流程代码,范围规则代码,更多示例代码详见test路径fx.custom.apl.example包下代码
按钮前验证代码示例
package fx.custom.apl.jar; import com.fxiaoke.functions.FunctionContext; import com.fxiaoke.functions.Fx; import com.fxiaoke.functions.client.DebugHelper; import com.fxiaoke.functions.model.ButtonValidateResult; import com.fxiaoke.functions.model.QueryResult; import com.fxiaoke.functions.template.IButtonBeforeAction; import com.fxiaoke.functions.utils.Maps; import com.google.common.collect.ImmutableMap; import com.mycompany.pkg.MyTest; import java.io.IOException; import java.util.Map; import static com.fxiaoke.functions.Fx.log; /** * 1. 根据命名空间以及返回,选择对应的Action * 2. 这个Action是函数触发入口,对象勾子触发时,会到Jar包中查找这个类并且检查接口以及接口提供的方法 * <p> * <p> * 全部已支持的action详见以下package * * @author APL * @see com.fxiaoke.functions.template */ public class ButtonBlockAction implements IButtonBeforeAction { MyTest test = new MyTest(); @Override public ButtonValidateResult validate(FunctionContext context, Map<String, Object> map) { //从模拟的上下文获取到调试的name字段 String name = (String) context.getData().get("name"); //可以自定义包名以及类名,执行逻辑 String testName = test.DemoFunc(name); //使用APL平台提供的 log.info 进行日志输出 //切勿使用 System.out.println(); 或者其他日志组件进行打印日志 log.info(testName); //假设我们需要从代码里查询名字为test的客户 String search = "test"; QueryResult ret = Fx.object.select(String.format("SELECT _id, name FROM AccountObj WHERE name = '%s'", search)).result(); Map<String, Object> obj = (Map<String, Object>) ret .getDataList() .stream() .findFirst() .orElseGet(() -> ImmutableMap.of("name", "测试")); return new ButtonValidateResult( true, "客户名字是:" + obj.get("name"), true); } /** * 调试入口 */ public static void main(String[] args) throws IOException { //调试器 DebugHelper helper = new DebugHelper(); //调试器初始化,包括身份以及服务器的地址 //身份信息联系APL平台获取,具体查看application.properties配置 helper.init(); //构造当前执行类 ButtonBlockAction example = new ButtonBlockAction(); //模拟调试的上下文,例如开发时想模拟一个客户对象的上下文,以方便开发 FunctionContext context = helper.context("AccountObj", "63100e7915d6a300017121cc"); //构造被触发时的参数 Map<String, Object> argument = Maps.newHashMap(); //执行函数 example.validate(context, argument); } }
按钮前验证代码示例
package fx.custom.apl.jar; import com.fxiaoke.functions.FunctionContext; import com.fxiaoke.functions.Fx; import com.fxiaoke.functions.client.DebugHelper; import com.fxiaoke.functions.model.APIResult; import com.fxiaoke.functions.template.IFlowAction; import com.fxiaoke.functions.tools.ActionAttribute; import com.fxiaoke.functions.utils.Maps; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static com.fxiaoke.functions.Fx.log; public class FlowActionExample implements IFlowAction { public static void main(String[] args) throws IOException { //调试器 DebugHelper helper = new DebugHelper(); //调试器初始化,包括身份以及服务器的地址 //身份信息联系APL平台获取,具体查看application.properties配置 helper.init(); //构造当前执行类 FlowActionExample example = new FlowActionExample(); //模拟调试的上下文,例如开发时想模拟一个客户对象的上下文,以方便开发 FunctionContext context = helper.context("AccountObj", "63100e7915d6a300017121cc"); //构造被触发时的参数 Map<String, Object> argument = Maps.newHashMap(); //执行函数 example.execute(context, argument); } @Override public void execute(FunctionContext functionContext, Map<String, Object> map) { ActionAttribute attribute = ActionAttribute.create(); //使用APL平台提供的 log.info 进行日志输出 //切勿使用 System.out.println(); 或者其他日志组件进行打印日志 log.info(functionContext); HashMap<Object, Object> account = new HashMap<>(); account.put("name", System.currentTimeMillis()); APIResult result = Fx.object.create("AccountObj", account, Maps.newHashMap(), attribute); log.info(result); } }
范围规则代码示例
package fx.custom.apl.jar; import com.fxiaoke.functions.FunctionContext; import com.fxiaoke.functions.client.DebugHelper; import com.fxiaoke.functions.model.QueryTemplate; import com.fxiaoke.functions.template.IRangeRuleTemplateAction; import com.fxiaoke.functions.tools.QueryOperator; import com.fxiaoke.functions.utils.Lists; import com.fxiaoke.functions.utils.Maps; import java.io.IOException; import java.util.Map; import static com.fxiaoke.functions.Fx.log; /** * 返回规则的Demo,注意返回值选择的是QueryTemplate */ public class RangeRuleExample implements IRangeRuleTemplateAction { public static void main(String[] args) throws IOException { //调试器 DebugHelper helper = new DebugHelper(); //调试器初始化,包括身份以及服务器的地址 //身份信息联系APL平台获取,具体查看application.properties配置 helper.init(); //构造当前执行类 RangeRuleExample example = new RangeRuleExample(); //模拟调试的上下文,例如开发时想模拟一个客户对象的上下文,以方便开发 FunctionContext context = helper.context("AccountObj", "63100e7915d6a300017121cc"); //构造被触发时的参数 Map<String, Object> argument = Maps.newHashMap(); //执行函数 example.execute(context, argument); } @Override public QueryTemplate execute(FunctionContext context, Map<String, Object> args) { //使用APL平台提供的 log.info 进行日志输出 //切勿使用 System.out.println(); 或者其他日志组件进行打印日志 log.info(context); return QueryTemplate.AND( Maps.of("life_status", QueryOperator.NE(Lists.newArrayList("invalid")) )); } }

七、常见问题

1. java.security.InvalidKeyException: Illegal key size 异常
参考:https://blog.csdn.net/zhuwangxiangbie/article/details/105124612
2.关于jar包引用
函数默认支撑大部分通用第三方包,如代码对其有依赖可直接使用,默认支持的jar,包括但不限于以下举例
http相关
org.apache.httpcomponents:httpclient:jar:4.5.2
org.apache.httpcomponents.client5:httpclient5:jar:5.2.1
com.squareup.okhttp3:okhttp:jar:3.14.9
util相关
org.apache.commons:commons-lang3:jar:3.12.0
org.apache.commons:commons-collections4:jar:4.4
dom4j:dom4j:jar:1.6.1
commons-codec:commons-codec:jar:1.15
org.codehaus.woodstox:stax2-api:jar:4.1
2.jar缺失相关报错如何解决?
cannot find matching method fx.custom.apl.jar.GetStation#afterjava.lang.NoClassDefFoundError: Unable to load class xxx due to missing dependency org/dom4j/DocumentException
查看对应的jar包,如果是自定义的包,可以通过插件/手动在开发jar包里上传对应jar包;
如果是一些第三方通用包,且函数未支持,可以手动上传jar包后在高代码群里反馈,函数后续考虑做支持;
如果对应jar包都已上传,仍然提示类/方法/变量不存在的情况,可以在开发jar包管理中将对应jar包下载下来,反编译后对比本地代码内容是否一致;
4.如何使用jar包中的代码?
如果jar包中的类只是一些工具类,并未与流程,按钮等命名空间关联,则可以在groovy代码中直接引入相关工具类,或使用全限定名直接调用;
如果jar包为一些命名空间的实现类,则需要创建对应jar类型APL函数代码
5.关于权限问题
目前高代码为了保障函数应用可用,做了一些安全性限制
如不允许写入系统变量,文件读取权限等,使用上如果出现相关报错,可以在高代码群反馈,由技术同学判断是否可以放开对应权限
5.关于网络问题(灰度中)
目前函数支持通过代理的方式访问外网网络权限
okhttp设置代理写法举例
private static Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("egress-proxy.egress", 9999)); private static OkHttpClient client = new OkHttpClient().newBuilder().proxy(proxy).build();
apache 设置代理写法举例
private static HttpClient httpClient = new HttpClient(); static { // 创建HostConfiguration对象,并设置代理 HostConfiguration hostConfig = new HostConfiguration(); hostConfig.setProxy("egress-proxy.egress", 9999); httpClient.setHostConfiguration(hostConfig); }
6.关于异常处理
jar包中的代码,要注意一些错误写法,该写法会丢失异常栈信息,无法排查对应问题
一些固定异常可以通过 Fx.message.throwErrorMessage("xxx错误"); 抛出
一些未知异常可以通过throw异常抛出
7.自定义控制器问题
注意:不推荐使用同步自定义控制器,推荐使用公共类
自定义控制器在devjar场景本地调试的话,保留这两行代码;如果要上传到服务器使用,要把这两行代码注释掉;
DebugHelper helper = new DebugHelper();
2024-09-02
4 23