背景
最近公司接入钉钉审批来完成一些功能。比如在线签约合同、财务报销等等。在整个流程中,是由钉钉发起审批,然后将结果推送到我们的服务中,然后在解析钉钉的审批实例表单来获取用户填写的数据,参与业务处理。
因为钉钉审批模版的原因,每个不同的审批模版,其审批实例信息不一样,之前是为每个不同的审批模版,写一套解析方法,通用型很差。针对公司的业务需求,写了一个解析审批实体信息的工具类。
注意:该工具类只提取用户输入的信息,钉钉模版组件自带的扩展信息没有处理。因为在我们的业务中还没有使用到,后续会根据具体的需求进行优化完善。
实现
定义通用表单对象
通过对比业务中使用的各个审批模版,定义了一个通用对象来封装钉钉组件中的数据。
public class ApprovalTemplateTableRes {
private String id;
private String name;
private String value;
private String label;
private String extValue;
//表单中表格内嵌使用的不是extValue
private String extendValue;
}
定义一个FormCells注解
@Retention(RetentionPolicy.RUNTIME)
public @interface FormCells {
/**
* 审批表单名称
*/
String value() default "";
/**
* 适配解析扩展的值。默认解析value字段值
*/
boolean analyzeValue() default true;
/**
* 因为钉钉审批模版中可以创建表格,在钉钉审批实例中表格数据是一个嵌套集合的形式存在,所以在这里需要声明一下字段的名称
*
*/
String targetCollectionName() default "";
/**
* 表单中该值是否是数组 默认是true 该字段用于对象属性上
* @return
*/
boolean isArray() default true;
}
使用反射实现解析工具类
import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author wyz
*/
public class DingTalkAnalyzeUtils {
private DingTalkAnalyzeUtils(){}
/**
* 解析
* @param resList
* @param cls 解析后转换的实体类class 对象
* @return 返回解析后实体类
* @param <T>
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> T process(List<ApprovalTemplateTableRes> resList, Class<T> cls) throws IllegalAccessException, InstantiationException {
// 反射创建对象
T obj = cls.newInstance();
//反射获取待带有@FormCells 注解的所有字段属性 并将 @FormCells 的值作为key Field 作为value 转化为map
Field[] fields = cls.getDeclaredFields();
Map<String, Field> fieldMap = Arrays.stream(fields).filter(field -> field.isAnnotationPresent(FormCells.class
)).collect(Collectors.toMap(field -> field.getAnnotation(FormCells.class).name(), Function.identity()));
// 遍历报文
for (ApprovalTemplateTableRes res : resList) {
// 获取钉钉审批表单项的名字
String name = res.getName()==null ? res.getLabel() : res.getName();
// 根据名称获取Field
Field field = fieldMap.get(name);
if (field == null){
continue;
}
field.setAccessible(true);
// 获取注解信息
FormCells annotation = field.getAnnotation(FormCells.class);
String value = annotation.analyzeValue()?res.getValue():res.getExtValue();
//如果为null 说明是表格中内嵌的,在取一下extendValue值
value = value != null ? value : res.getExtendValue();
//取了两次没取到值,跳过
if (value == null) {
continue;
}
// 判断字段类型
if (field.getType().isAssignableFrom(String.class)) {
// 直接将value 赋值
field.set(obj,value);
continue;
}
// 字段类型是否为集合
if(isCollection(field)){
List<?> target;
if ("".equals(annotation.targetCollectionName())){
//直接转化
target = JSONObject.parseArray(value,getTargetClass(field));
}else{
// 获取钉钉审批表单项的值 转化为List 并递归解析获取解析后的列表
List<JSONObject> list = JSONObject.parseArray(value ,JSONObject.class);
target = list.stream().map(jsonObject -> {
String s = jsonObject.getString(annotation.targetCollectionName());
List<ApprovalTemplateTableRes> approvalTemplateTableRes = JSONObject.parseArray(s, ApprovalTemplateTableRes.class);
try {
return process(approvalTemplateTableRes, getTargetClass(field));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
}
// 反射赋值
field.set(obj,target);
continue;
}
//todo 目前还没有遇到需要转化为对象的数据结构 暂时这样处理
if (annotation.isArray()) {
JSONObject jsonObject = JSONObject.parseObject(res.getValue(),JSONObject.class);
//如果是对象
List<ApprovalTemplateTableRes> list = JSONObject.parseArray(jsonObject.toString(),ApprovalTemplateTableRes.class);
// 递归解析
Object process = process(list, getTargetClass(field));
// 将解析后的对象 进行反射赋值
field.set(obj,process);
} else{
Object object = JSONObject.parseObject(value,getTargetClass(field));
// 将解析后的对象 进行反射赋值
field.set(obj,object);
}
}
return obj;
}
/**
* 获取Field 数据类型的class对象,如果是集合则返回集合范型的class 对象
* 比如:
* List<T> 则返回 T.class
* String 则返回 String.class
* @param field
* @return
*/
public static Class<?> getTargetClass(Field field){
Type type = field.getGenericType();
if (type instanceof ParameterizedType){
for (Type actualTypeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
if (actualTypeArgument instanceof Class){
return (Class) actualTypeArgument;
}
}
}
return field.getType();
}
/**
* 判断Field 是否是集合
* @param field
* @return
*/
public static boolean isCollection(Field field) {
return field.getType().isAssignableFrom(List.class)||
field.getType().isAssignableFrom(Set.class)||
field.getType().isAssignableFrom(Collection.class);
}
}
验证
审批表单数据
[
{
"id": "DepartmentField_1CJEB0424S680",
"name": "门店",
"value": "黄xx区域",
"bizAlias": "",
"extValue": "[{\"number\":150,\"itemId\":\"859722845\",\"name\":\"黄xx区域\",\"id\":\"859722845\"}]",
"componentType": "DepartmentField"
},
{
"id": "DDSelectField_1DLJZ6ABOY1S0",
"name": "出款卡",
"value": "福州/厦门/盐城 营业额卡",
"bizAlias": "",
"extValue": "{\"label\":\"福州/厦门/盐城 营业额卡\",\"key\":\"option_B9K28MU0FPS0\"}",
"componentType": "DDSelectField"
},
{
"id": "DDSelectField_16YR31UGA22K0",
"name": "费用是否需要多门店分摊",
"value": "是",
"bizAlias": "",
"extValue": "{\"label\":\"是\",\"key\":\"option_0\"}",
"componentType": "DDSelectField"
},
{
"id": "DDMultiSelectField_1XGUVGJSRAF40",
"name": "费用项目(可多选)",
"value": "[\"奖金/绩效\"]",
"bizAlias": "",
"extValue": "[{\"label\":\"奖金/绩效\",\"key\":\"option_1Y9PKK1BLYTC0\"}]",
"componentType": "DDMultiSelectField"
},
{
"id": "DDMultiSelectField_1SIB5630HWBK0",
"name": "费用发生周期",
"value": "[\"7月\"]",
"bizAlias": "",
"extValue": "[{\"label\":\"7月\",\"key\":\"option_PIZFGAFJT740\"}]",
"componentType": "DDMultiSelectField"
},
{
"id": "InnerContactField_1ZYZ2D7EOEG00",
"name": "寿星/被介绍人",
"bizAlias": "",
"componentType": "InnerContactField"
},
{
"id": "TextField_1G2PWI104B0G0",
"name": "业务描述",
"value": "7月管理分红营运基金分摊",
"bizAlias": "",
"componentType": "TextField"
},
{
"id": "TableField_1HUBAFEXIIYO0",
"name": "多项目报销须添加明细",
"value": "[{\"rowValue\":[{\"bizAlias\":\"\",\"label\":\"项目名称\",\"value\":\"7月绩效管理费用\",\"key\":\"TextField_N9L1IDXS4E80\",\"mask\":false},{\"bizAlias\":\"\",\"label\":\"数量\",\"value\":\"1\",\"key\":\"NumberField_10BMA0B2WM9C0\",\"mask\":false},{\"bizAlias\":\"\",\"label\":\"单价(元)\",\"value\":\"1723.8\",\"key\":\"MoneyField_IJ2P3XLTCM80\",\"mask\":false},{\"bizAlias\":\"\",\"label\":\"小计金额(元)\",\"value\":\"1723.8\",\"key\":\"CalculateField_YFQHZV3OIZK0\",\"mask\":false}],\"rowNumber\":\"TableField_1HUBAFEXIIYO0_1TC2MNOW37BB4\"}]",
"bizAlias": "",
"extValue": "{\"statValue\":[],\"componentName\":\"TableField\"}",
"componentType": "TableField"
},
{
"id": "MoneyField_1JUY3KWFUEYO0",
"name": "合计总金额",
"value": "1723.8",
"bizAlias": "",
"extValue": "{\"upper\":\"壹仟柒佰贰拾叁元捌角\",\"componentName\":\"MoneyField\"}",
"componentType": "MoneyField"
},
{
"id": "DDSelectField_1PSZW55OHU9S0",
"name": "是否录入系统",
"bizAlias": "",
"componentType": "DDSelectField"
},
{
"id": "RecipientAccountField-JVLZMITA",
"name": "收款账号",
"value": "黄xx",
"bizAlias": "",
"extValue": "{\"identityType\":\"PERSONAL_BANK_CARD\",\"instProvince\":\"福建省\",\"name\":\"黄xx\",\"instCity\":\"福州市\",\"instBranchName\":\"中国建设银行股份有限公司福州师大支行\",\"id\":\"202305271648xxxx\",\"instCode\":\"CCB\",\"instName\":\"中国建设银行\",\"cardNo\":\"6217001xxxxxx\"}",
"componentType": "RecipientAccountField"
},
{
"id": "TextField_1VIP4V7Z20HS",
"name": "收款账号所属开户行",
"value": "62170018200xxxxxx 中国建设银行福州师大支行",
"componentType": "TextField"
},
{
"id": "TextField_R353BQ7OR0W0",
"name": "备注",
"bizAlias": "",
"componentType": "TextField"
},
{
"id": "RelateField_RFFZEC7G3TC0",
"name": "关联审批单",
"bizAlias": "",
"componentType": "RelateField"
},
{
"id": "DDPhotoField_15OQX42UGALC0",
"name": "报销面单&报销凭证",
"value": "[\"https://static.dingtalk.com/media/lQLPM5ejsYExxxxxxx.png\"]",
"bizAlias": "",
"componentType": "DDPhotoField"
},
{
"id": "DDAttachment_1G1TPEVEFVPC0",
"name": "费用多门店摊销附件",
"value": "[{\"spaceId\":\"4152693639\",\"fileName\":\"2023.7月损益月表汇总-黄先明区域(分红).xlsx\",\"fileSize\":\"72907\",\"fileType\":\"xlsx\",\"fileId\":\"112942110479\"}]",
"bizAlias": "",
"componentType": "DDAttachment"
},
{
"id": "DDPhotoField_DIH7U9J4V1C0",
"name": "后附单据",
"bizAlias": "",
"componentType": "DDPhotoField"
},
{
"id": "DDAttachment_IW1IJGI4IQO0",
"name": "文件附件",
"bizAlias": "",
"componentType": "DDAttachment"
},
{
"id": "DDSelectField_1OW0M3G7RQOW0",
"name": "11",
"bizAlias": "",
"componentType": "DDSelectField"
},
{
"id": "TextNote_MR574BV2ECW0",
"bizAlias": "",
"componentType": "TextNote"
},
{
"id": "TextNote_AUJZIXEWB8C0",
"value": "员工福利审批流程:\n》申请人→区域人资→区域经理→单店财务→出纳 \n区域人资划分:\n苏州",
"bizAlias": "",
"componentType": "TextNote"
}
]
定义对应的实体对象
import lombok.Data;
import java.util.List;
@Data
public class StoreProcessInstance {
@FormCells(value = "门店")
private String store;
@FormCells(value = "出款卡")
private String cardNo;
@FormCells(value = "费用是否需要多门店分摊")
private String isShare;
@FormCells(value = "费用项目(可多选)")
private String feeItem;
//费用发生周期
@FormCells(value = "费用发生周期")
private String feeCycle;
//业务描述
@FormCells(value = "业务描述")
private String businessDesc;
@FormCells(value = "多项目报销须添加明细", targetCollectionName = "rowValue")
private List<Detail> detail;
@FormCells(value = "收款账号")
private String account;
}
@Data
public class Detail{
@FormCells(value = "项目名称")
private String projectName;
@FormCells(value = "数量")
private String num;
@FormCells(value = "单价(元)")
private String price;
@FormCells(value = "小计金额(元)")
private String total;
}
使用
@PostMapping("/store")
public void test2(@RequestBody List<ApprovalTemplateTableRes> resList) throws IllegalAccessException, InstantiationException {
StoreProcessInstance instance = DingTalkAnalyzeUtils.process(resList, StoreProcessInstance.class);
log.info("instance:{}",instance);
}
结果输出
instance:StoreProcessInstance(store=黄xx区域, cardNo=福州/厦门/盐城 营业额卡, isShare=是, feeItem=["奖金/绩效"], feeCycle=["7月"], businessDesc=7月管理分红营运基金分摊, detail=[Detail(projectName=7月绩效管理费用, num=1, price=1723.8, total=1723.8)], account=黄xx)
说明
该工具类主要是通过反射类解析审批表单数据,提取用户输出的数据,该工具类,是根据公司业务使用的审批模版开发出来的,仅供参考。