项目中与外部系统的数据交互肯定少不了,大部分项目的对外接口其实可以固定,基本是万年不变。但是现在在做的百度CMS系统由于对接了太多的业务线,各个业务线推送合同的字段不一样,并且由于安全的考虑,并不会放开所有字段推送给外部系统。这就造成一个问题,随着各业务线的业务需要,可能会时不时的改动数据传输的接口,这就增加了开发的工作量,由于基本都是后台定时任务,QA测试也并不是很方便,因此就想搞一个前台可配置的定时任务,可以让PM通过配置需要传输的字段,而不是代码字段的修改,实现业务上的满足。
定一个需要传输的对象,我们定义一个User类,设置几个属性,并生成一下getter和setter。
public class User implements Serializable {
private String name;
private String addr;
private int age;
private User leader;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User getLeader() {
return leader;
}
public void setLeader(User leader) {
this.leader = leader;
}
}
public class Field implements Serializable {
private String code;
private String fieldName;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
}
定义一个简单的自动任务类,里面包含需要传输的字段,cron表达式,执行时间等字段。
public class TransimissionJob {
// 自动任务名称
private String jobName;
// 自动任务表达式
private String cronExp;
// 上次执行时间
private Date lastExecTime;
// 下次执行时间
private Date nextExecTime;
// 传输地址
private String url;
// 传输字段
private List<MyField> info = new ArrayList<MyField>(0);
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getCronExp() {
return cronExp;
}
public void setCronExp(String cronExp) {
this.cronExp = cronExp;
}
public Date getLastExecTime() {
return lastExecTime;
}
public void setLastExecTime(Date lastExecTime) {
this.lastExecTime = lastExecTime;
}
public Date getNextExecTime() {
return nextExecTime;
}
public void setNextExecTime(Date nextExecTime) {
this.nextExecTime = nextExecTime;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<MyField> getBasicInfo() {
return basicInfo;
}
public void setBasicInfo(List<MyField> basicInfo) {
this.basicInfo = basicInfo;
}
}
这些基本的对象都有了,但还差一点东西,既然要满足前台配置,而且是通过反射进行对象属性的封装,那么就得要有一个映射关系:
人员名—name;
人员地址—addr;
人员年龄—age;
上级人员名—leader.name;
比如说,要勾选人员名、地址、年龄、上级名这四个字段进行数据传输,TransimissionJob.info那个列表,就应该是四个对象,Field.code属性分别是name、addr、age、leader.name。
TransimissionJob job = // 从数据库拿出某条任务对象
// 定义一个Set集合,将TransimissionJob.info对象的code装进去
Set<String> properties = new HashSet<String>();
if (CollectionUtils.isNotEmpty(job.getInfo())) {
for (Field field : job.getInfo()) {
if (StringUtils.isNotBlank(field.getCode())) {
properties.add(field.getCode());
}
}
}
// 获取一级属性
Set<String> simpleProperties = getSimpleProperties(properties);
List<User> data = // 从数据库拿出用户对象
// 存放组织的数据
List<Map<String, Object> > targets = new ArrayList<Map<String, Object>>();
for (User user : datas) {
// 每一条数据的kv键值对
Map<String, Object> target = getData(user, user.class, simpleProperties);
targets.add(target);
}
// 转换得到的Json串
String jsonData = JSON.toJSONString(result);
// 具体调外部系统接收数据api
......
/**
* 获取一级属性,通过.来分割
*/
public static Set<String> getSimpleProperties(Set<String> properties) {
Set<String> simpleProperties = new HashSet<String>();
for (String property : properties) {
if (property.contains(".")) {
int suffixIx = property.indexOf(".");
if (suffixIx > 0) {
String simpleProperty = property.substring(0, suffixIx);
simpleProperties.add(simpleProperty);
}
} else {
simpleProperties.add(property);
}
}
return simpleProperties;
}
/**
* 获取properties中parent下面的子字段名列表
*/
public static Set<String> getChildProperties(Set<String> properties, String parent) {
Set<String> result = new HashSet<String>();
for (String property : properties) {
if (property.startsWith(parent + ".")) {
int suffixIx = property.indexOf(".");
if (suffixIx > 0) {
String simpleProperty = property.substring(suffixIx + 1);
result.add(simpleProperty);
}
}
}
return result;
}
/**
* 根据实体entity和字段属性名得到需要的字段
* @param entity
* @param clazz
* @param needProperties
* @return
* @throws Exception
*/
public static Map<String, Object> getData(Object entity, Class<?> clazz,
Set<String> needProperties) throws Exception {
Set<String> simpleProperties = getSimpleProperties(needProperties);
Set<String> properties = getPropertyNames(clazz, simpleProperties);
Map<String, Object> data = new HashMap<String, Object>();
for (String property : properties) {
if (!simpleProperties.contains(property)) {
continue;
}
try {
Set<String> childrenProperties = getChildProperties(needProperties, property);
Method gMethod = clazz.getMethod("get".concat(StringUtils.capitalize(property)));
Object value = gMethod.invoke(entity);
if (value == null) {
continue;
} else if (CollectionUtils.isEmpty(childrenProperties)) {
if (value instanceof java.sql.Date) {
value = new java.util.Date(((java.sql.Date) value).getTime());
}
// 如果没有子属性,就把他当做整体输出
data.put(property, value);
continue;
}
if (value instanceof Collection) {
// 集合类对象全部以list形式输出
Collection c = (Collection) value;
List list = new ArrayList();
for (Object o : c) {
Map<String, Object> element = getData(o, o.getClass(), childrenProperties);
list.add(element);
}
data.put(property, list);
} else {
// 一对一级联对象
data.put(property, getData(value, value.getClass(), childrenProperties));
}
} catch (Exception e) {
logger.error("transfer error", e);
}
}
return data;
}
/**
* 根据需要的字段属性名获取对象中的字段属性名
* 只取对象中有的属性,忽略needProperties中多余的属性,保证程序不报错
* @param clazz
* @return
*/
public static Set<String> getPropertyNames(Class<?> clazz, Set<String> needProperties) {
final BeanWrapper src = new BeanWrapperImpl(clazz);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> properties = new HashSet<String>();
for (java.beans.PropertyDescriptor pd : pds) {
if (!"class".equals(pd.getName()) && needProperties.contains(pd.getName())) {
// 排除掉getClass方法
properties.add(pd.getName());
}
}
return properties;
}