思路和需求
需求:拉取三方订单数据,保存到同一数据结构中,思路:将需求拆分为,数据拉取,数据转换,保存。主要运用到的技术,都是java中比较基础的东西,就自定义注解,反射,这块的知识。所以大家可以放心食用,不会塞牙的啦!!!
*
一:定义自定义注解
1. 淘宝,拼多多,京东,字段相关的注解
淘宝:
package com.gy.entity.annotation;
import java.lang.annotation.*;
/**
* 三方平台数据转换注解,实现效果,得到数据后
* 在字段上添加注解,就可以将数据,转换为平台所需要的数据
* 调用方法,
*/
@Target({ElementType.FIELD,ElementType.TYPE}) //Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) //Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME
@Documented
public @interface TbFiled {
String value() default "";
}
拼多多:
package com.gy.entity.annotation;
import java.lang.annotation.*;
/**
* 三方平台数据转换注解,实现效果,得到数据后
* 在字段上添加注解,就可以将数据,转换为平台所需要的数据
* 调用方法,
*/
@Target({ElementType.FIELD,ElementType.TYPE}) //Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) //Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME
@Documented
public @interface PddFiled {
String value() default "";
}
京东:
package com.gy.entity.annotation;
import java.lang.annotation.*;
/**
* 三方平台数据转换注解,实现效果,得到数据后
* 在字段上添加注解,就可以将数据,转换为平台所需要的数据
* 调用方法,
*/
@Target({ElementType.FIELD,ElementType.TYPE}) //Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) //Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME
@Documented
public @interface JdFiled {
String value() default "";
}
2. 执行器相关的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE})
@Documented
@Inherited
public @interface OrderFetchTaskAnnotation {
Class<?>[] components() default{};
}
二:自定义注解的使用
1. 实体类
package com.gy.entity.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gy.entity.annotation.JdFiled;
import com.gy.entity.annotation.PddFiled;
import com.gy.entity.annotation.TbFiled;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("third_orderInfo")
public class XhbThirdOrderInfo implements Serializable {
@ApiModelProperty("订单号")
@TbFiled(value = "tradeId")
@JdFiled(value = "orderId")
@PddFiled(value = "orderSn")
@TableId(value = "order_number")
private String orderNumber;
@ApiModelProperty("用户ID")
private Long userId;
@TbFiled(value = "itemId")
@PddFiled(value = "goodsId")
@JdFiled(value = "skuId")
@ApiModelProperty("商品ID")
private Long goodsId;
@TbFiled(value = "itemTitle")
@JdFiled(value = "skuName")
@PddFiled(value = "goodsName")
@ApiModelProperty("商品名称")
private String goodsName;
@ApiModelProperty("商品图片链接")
@PddFiled(value = "goodsThumbnailUrl")
@TbFiled(value = "itemImg")
@JdFiled(value = "imgUrl")
private String goodsThumbnailUrl;
@TbFiled(value = "tkPaidTime")
@PddFiled(value = "orderPayTime")
@JdFiled(value = "orderTime")
@ApiModelProperty("创建时间/淘宝订单取的是订单付款时间")
private Long createTime;
在这里讲一哈,可以根据需求去取自己需要的字段,只要做好类型转换就可以了。这里自定义注解的思路为,通过反射获取value值,然后获取的字段的属性值,在反射设置回model类对应的字段上来。
2:同步数据思路。
- 不管是那个平台的订单,总体来说,分为三个模块,
一:数据拉取,二:数据转换,三:数据处理(业务相关)四:数据保存
讲到这里,另外一个注解就需要上场了。
先给大家看代码。
package com.gy.schedule.task;
import com.gy.schedule.task.component.impl.*;
import com.gy.schedule.task.constant.OrderFetchTaskAnnotation;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@OrderFetchTaskAnnotation(components = {
DefaultRunStrategy.class,
TbStartEndBuilder.class,
TbDataFetcher.class,
TbDataAssembler.class,
ThirdDataParser.class,
ThirdPostProcessor.class,
ThirdDataPersist.class,
})
@Component
public class TbOrderFetchTask extends AbstractOrderFetchTask {
/**
* 定时任务
* @throws Exception
*/
@Override
@Scheduled(cron = "0 0/5 * * * ? ")
// @Scheduled(cron = "*/10 * * * * ?")
public void execute()throws Exception {
this.process();
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}
父类:AbstractOrderFetchTask
@Data
public abstract class AbstractOrderFetchTask implements InitializingBean, ApplicationContextAware {
private static ApplicationContext applicationContext;
protected IStartEndBuilder startEndBuilder;
protected IDataFetcher dataFetcher;
protected IDataParser dataParser;
protected IDataAssembler dataAssembler;
protected IDataPersist persist;
protected IPostProcessor postProcessor;
protected IJobRunStrategy fetchStrategy;
public AbstractOrderFetchTask(){
//ReflectUtils.setFields(AbstractOrderFetchTask.class.getDeclaredFields(), annotation.components(), this);
}
public void process() throws Exception {
try {
OrderTaskContext context = new OrderTaskContext();
context.initContext(startEndBuilder, dataFetcher, dataParser, dataAssembler, persist, postProcessor);
fetchStrategy.process(context);
}catch (Exception e){
FileLogUtil.info("ThirdTaskLog", "ThirdTaskLogDetails--->exception:{}",
ExceptionUtils.printStackTrace(e));
}
}
public abstract void execute() throws Exception;
@Override
public void afterPropertiesSet() {
OrderFetchTaskAnnotation annotation = this.getClass().getAnnotation(OrderFetchTaskAnnotation.class);
Field[] declaredFields = AbstractOrderFetchTask.class.getDeclaredFields();
for (Field f:declaredFields){
for (Class c:annotation.components()){
String name = f.getName();
Object o = this.getBean(c);
Class<?>[] interfaces = o.getClass().getInterfaces();
Class<?> type = f.getType();
Boolean isImplements = checkIsImplements(type, interfaces);
if(isImplements) {
String set = Joiner.on("").join("set", ReflectUtils.upperCase(f.getName()));
try {
this.getClass().getMethod(set,interfaces[0]).invoke(this,o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AbstractOrderFetchTask.applicationContext=applicationContext;
}
public static <T> T getBean(Class<T> clazz){
return AbstractOrderFetchTask.applicationContext.getBean(clazz);
}
private Boolean checkIsImplements( Class<?> type, Class<?>[] interfaces){
for (Class c:interfaces){
if (type.equals(c))
return true;
}
return false;
}
}
具体的效果就是,不过是那个平台的拉取任务,都实现AbstractOrderFetchTask这个类,然后通过SpringBean容器将容器中所有的相关对应的子类,赋值给父类,举个栗子:
淘宝订单拉取:把
@OrderFetchTaskAnnotation(components = {
DefaultRunStrategy.class,
TbStartEndBuilder.class,
TbDataFetcher.class,
TbDataAssembler.class,
ThirdDataParser.class,
ThirdPostProcessor.class,
ThirdDataPersist.class,
})
@Component
public class TbOrderFetchTask extends AbstractOrderFetchTask {
这里的@OrderFetchTaskAnnotation中所有的类从springBean容器中取出来,赋值给AbstractOrderFetchTask 中的父类,然后在process()方法中构建OrderTaskContext上下文,并且初始化。
这里需要注意的是,所有的子类都加了@Component注解 不然无法从springBean容器中取出来,这里需要注意的是,父类AbstractOrderFetchTask 实现了InitializingBean, ApplicationContextAware这二个接口,大家可以去了解一哈,具体就不多叙述了。只需要注意SpringBean的生命周期就可以了。
再贴一部分OrderTaskContex代码:
Data
public class OrderTaskContext {
private IStartEndBuilder startEndBuilder;
private IDataFetcher dataFetcher;
private IDataParser dataParser;
private IDataAssembler dataAssembler;
private IDataPersist dataPersist;
private IPostProcessor postProcessor;
private Date startDate;
private Date endDate;
private Date now = new Date();
private List<TbkOrderDetailsGetResponse.PublisherOrderDto> taobaoOrder;
private Map<String,PromotionGoodsResp> promotionGoodsResp;
private List<PddDdkOrderListIncrementGetResponse.OrderListGetResponseOrderListItem> pddOrder;
private OrderResp[] JdOrder ;
private List<XhbThirdOrderInfo> thirdOrderInfos ;
public void initContext(IStartEndBuilder startEndBuilder, IDataFetcher dataFetcher, IDataParser dataParser, IDataAssembler dataAssembler, IDataPersist dataPersist, IPostProcessor postProcessor){
this.dataFetcher = dataFetcher;
this.dataParser = dataParser;
this.dataAssembler = dataAssembler;
this.dataPersist = dataPersist;
this.postProcessor = postProcessor;
this.startEndBuilder = startEndBuilder;
}
}
在构建好上下文之后,执行指定好的抓取策越就可以了
DefaultRunStrategy:
@Component
public class DefaultRunStrategy implements IJobRunStrategy {
@Override
public void process(OrderTaskContext context) throws Exception {
context.getStartEndBuilder().build(context);
context.getDataFetcher().fetch(context);
context.getDataAssembler().assemble(context);
context.getDataParser().parse(context);
context.getPostProcessor().postProcess(context);
context.getDataPersist().persist(context);
}
}
这样就可以分别去执行不同的逻辑,然后进行处理了,
然后就是数据拉取:
还是给大家看哈淘宝怎么操作:
TbDataFetcher:
/**
* @description:
* @author: error_0
* @create:
**/
@Component
public class TbDataFetcher implements IDataFetcher {
@Value("${key}")
protected String appkey;
@Value("${appsecret}")
protected String appsecret;
protected final String SERVER_URL = "";
protected final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
protected final OrderQueryType DEFAULT_QUERY_TYPE = OrderQueryType.QUERY_BY_CREATE_TIME;
protected final Long DEFAULT_PAGE_NO = 1L;
protected final Long DEFAULT_PAGE_SIZE = 20L;
protected final MemberType DEFAULT_MEMBER_TYPE = MemberType.ALL;
protected final TbkOrderStatus DEFAULT_ORDER_STATUS = TbkOrderStatus.ALL;
protected final JumpType DEFAULT_JUMPTYPE = JumpType.BACKWARD;
protected final OrderScheneType DEFAULT_ORDER_SCHENE = OrderScheneType.NORMAL;
@Override
public void fetch(OrderTaskContext context) {
String startTime = null;
String endTime = null;
startTime = DateUtil.format(context.getStartDate(), DEFAULT_DATE_FORMAT);
endTime = DateUtil.format(context.getEndDate(), DEFAULT_DATE_FORMAT);
taobaoOrder = getTaobaoOrder(startTime, endTime, OrderScheneType.CHANNEL);
context.setTaobaoOrder(taobaoOrder);
List<String> orderIds = taobaoOrder.stream().map(x -> x.getTradeId()).collect(Collectors.toList());
FileLogUtil.info("TbFetchLog", "TbFetchDetails--->StartTime:{},EndTime:{},orderCount:{},orderId:{}",
startTime, endTime, taobaoOrder.size(), JsonUtils.obj2Json(orderIds));
}
protected List<PublisherOrderDto> getTaobaoOrder(String startTime, String endTime, OrderScheneType orderSchene) {
List<PublisherOrderDto> results = new ArrayList<>();
Long page_no = DEFAULT_PAGE_NO;
//调用API拿到数据
while (true) {
OrderPage orderPage = getOrders(DEFAULT_QUERY_TYPE, null, page_no,
DEFAULT_PAGE_SIZE, DEFAULT_MEMBER_TYPE, DEFAULT_ORDER_STATUS,
startTime, endTime, DEFAULT_JUMPTYPE, orderSchene);
if (orderPage == null) {
break;
}
List<PublisherOrderDto> res = orderPage.getResults();
if (CollectionUtils.isEmpty(res)) {
break;
}
results.addAll(res);
page_no++;
}
return results;
}
/**
* @param queryType 查询时间类型,1:按照订单淘客创建时间查询,2:按照订单淘客付款时间查询,3:按照订单淘客结算时间查询
* @param positionIndex 位点,除第一页之外,都需要传递;前端原样返回
* @param pageNo 第几页,默认1,1~100
* @param pageSize 页大小,默认20,1~100
* @param memberType 推广者角色类型,2:二方,3:三方,不传,表示所有角色
* @param orderStatus 淘客订单状态,12-付款,13-关闭,14-确认收货,3-结算成功;不传,表示所有状态
* @param startTime 订单查询开始时间
* @param endTime 订单查询结束时间,订单开始时间至订单结束时间,中间时间段日常要求不超过3个小时,但如618、双11、年货节等大促期间预估时间段不可超过20分钟,超过会提示错误,调用时请务必注意时间段的选择,以保证亲能正常调用!
* @param jumpType 跳转类型,当向前或者向后翻页必须提供,-1: 向前翻页,1:向后翻页
* @param orderSchene 场景订单场景类型,1:常规订单,2:渠道订单,3:会员运营订单,默认为1
* @return
*/
protected OrderPage getOrders(OrderQueryType queryType, String positionIndex, Long pageNo, Long pageSize,
MemberType memberType, TbkOrderStatus orderStatus, String startTime, String endTime, JumpType jumpType,
OrderScheneType orderSchene) {
TaobaoClient client = new DefaultTaobaoClient(SERVER_URL, this.appkey, this.appsecret);
TbkOrderDetailsGetRequest req = new TbkOrderDetailsGetRequest();
req.setQueryType(queryType == null ? this.DEFAULT_QUERY_TYPE.value() : queryType.value());
req.setPositionIndex(positionIndex);
req.setPageNo(pageNo == null ? this.DEFAULT_PAGE_NO : pageNo);
req.setPageSize(pageSize == null ? this.DEFAULT_PAGE_SIZE : pageSize);
req.setMemberType(memberType == null ? this.DEFAULT_MEMBER_TYPE.value() : memberType.value());
req.setTkStatus(orderStatus == null ? this.DEFAULT_ORDER_STATUS.value() : orderStatus.value());
req.setStartTime(startTime);
req.setEndTime(endTime);
req.setJumpType(jumpType == null ? this.DEFAULT_JUMPTYPE.value() : jumpType.value());
req.setOrderScene(orderSchene == null ? this.DEFAULT_ORDER_SCHENE.value() : orderSchene.value());
TbkOrderDetailsGetResponse rsp = null;
try {
rsp = client.execute(req);
return rsp.getData();
} catch (ApiException e) {
}
return null;
}
}
这里就是常规的数据拉取,然后把数据设置到上下文中去
重头来了,数据转换:
TbDataAssembler:
/**
* @description:
* @author: error_0
* @create:
**/
@Component
public class TbDataAssembler implements IDataAssembler {
@Override
public void assemble(OrderTaskContext context) throws Exception {
List<TbkOrderDetailsGetResponse.PublisherOrderDto> taobaoOrder = context.getTaobaoOrder();
List<XhbThirdOrderInfo> list =new ArrayList<>();
for (TbkOrderDetailsGetResponse.PublisherOrderDto orderDto : taobaoOrder){
XhbThirdOrderInfo orderInfo =new XhbThirdOrderInfo();
Object o = ThirdDataTranfromUtils.ThirdDataToInternal(orderInfo, orderDto, 1,null);
XhbThirdOrderInfo info =(XhbThirdOrderInfo) o;
//设置为淘宝订单
String url = info.getGoodsThumbnailUrl();
if (url.startsWith("//")) {
url = "http:" + url;
}
info.setGoodsThumbnailUrl(url);
info.setOrderSource(OrderSource.TAOBAO.value());
list.add(info);
}
context.setThirdOrderInfos(list);
}
我们可以看到这里很简单就把对应类传到ThirdDataTranfromUtils.ThirdDataToInternal()方法中就可以了。
这是一个工具类,我们来看下:
/**
* @description:
* @author: error_0
* @create:
**/
/**
* 三方数据转换工具类
*/
public class ThirdDataTranfromUtils {
public static Object ThirdDataToInternal(Object targetData, Object sourcesData, Integer orderType,Integer IsSyncData) throws Exception{
//首先拿到目标对象,注解的value值(对应的就是输入目标的对象Fild值),
//接下来用一个map存储数据key:目标对象的属性的value值,value:对应输入对象的value值
//接下来设置到输出对象中去
//orderType 1:淘宝,2京东,3,拼多多
if(targetData==null||sourcesData==null){
return null;
}
Class<? extends Object> targetDataClass = targetData.getClass();
Class<? extends Object> sourcesDataClass= sourcesData.getClass();
Field[] targetFields = targetDataClass.getDeclaredFields();
Field[] sourcesFileds = sourcesDataClass.getDeclaredFields();
// Map<String,String> mapfiled = new HashMap<>();
if(orderType.equals(ThirdOrderType.TB.value())){
//遍历目标所有的字段
for (Field f: targetFields){
boolean isanno = f.isAnnotationPresent(TbFiled.class);
if(isanno){
//如果字段上存在TbFiled注解,拿到其value值,并且拿到sourceData对应字段的
//属性值
TbFiled annotation = f.getAnnotation(TbFiled.class);
String value = annotation.value();
dataTransFrom(f,sourcesFileds,targetData,sourcesData,value,IsSyncData);
}
}
}
if(orderType.equals(ThirdOrderType.JD.value())){
for (Field f: targetFields){
boolean isanno = f.isAnnotationPresent(JdFiled.class);
if(isanno){
//如果字段上存在TbFiled注解,拿到其value值,并且拿到sourceData对应字段的
//属性值
JdFiled annotation = f.getAnnotation(JdFiled.class);
String value = annotation.value();
dataTransFrom(f,sourcesFileds,targetData,sourcesData,value,IsSyncData);
}
}
}
if(orderType.equals(ThirdOrderType.PDD.value())){
for (Field f: targetFields){
boolean isanno = f.isAnnotationPresent(PddFiled.class);
if(isanno){
//如果字段上存在TbFiled注解,拿到其value值,并且拿到sourceData对应字段的
//属性值
PddFiled annotation = f.getAnnotation(PddFiled.class);
String value = annotation.value();
dataTransFrom(f,sourcesFileds,targetData,sourcesData,value,IsSyncData);
}
}
}
return targetData;
}
/**
*
* @param targetFiled 目标字段
* @param sourcesField 输入类对象字段集合
* @param targetData target类Class对象
* @param sourcesData sources类Class对象
* @param value 注解上的value值
* @param IsSyncData 是否为同步数据操作
* @throws Exception
*/
private static void dataTransFrom(Field targetFiled,Field[] sourcesField,Object targetData,Object sourcesData,String value,Integer IsSyncData) throws Exception{
for (Field sfield:sourcesField){
String sfName = sfield.getName();
if(sfName.equals(value)){
sfield.setAccessible(true);
Class<?> type = sfield.getType();
//拿到当前字段的值
Object o = sfield.get(sourcesData);
targetFiled.setAccessible(true);
Object convertValue=null;
convertValue = convertValType(o, type, sfName);
if(convertValue!=null){
targetFiled.set(targetData,convertValue);
}
}
}
}
/**
* 淘宝订单的类型转换器
* @param value
* @param fieldTypeClass
* @param filedName
* @return
*/
private static Object convertValType(Object value, Class<?> fieldTypeClass,String filedName){
if(isEmpty(value)){
return null;
}
Object retVal =null;
if(filedName.equals("tkStatus")){
return retVal=Integer.parseInt(value.toString());
}else if(filedName.equals("alipayTotalPrice")){
retVal=Long.parseLong(String.format("%.0f", Double.parseDouble(value.toString())* 100));
}else if(filedName.equals("totalCommissionRate")){
retVal= Long.parseLong(String.format("%.0f", Double.parseDouble(value.toString()) * 10));
}else if(filedName.equals("estimateFee")){
return retVal =Long.parseLong(String.format("%.0f", Double.parseDouble(value.toString()) * 100));
}else if(filedName.equals("commissionRate")){
return retVal = Long.parseLong(String.format("%.0f", Double.parseDouble(value.toString()) * 10));
}else if(filedName.equals("estimateCosPrice")){
return retVal = Long.parseLong(String.format("%.0f", Double.parseDouble(value.toString()) * 100));
}else if(filedName.equals("tkPaidTime")){
try {
return retVal = DateUtils.parseDateDefault(value.toString()).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
}
else if(filedName.equals("orderId")){
return retVal =value.toString();
}else if(filedName.equals("itemPrice")){
return Double.parseDouble(value.toString());
}else if(filedName.equals("goodsPrice")){
return Double.parseDouble(value.toString());
}
else {
retVal=value;
}
return retVal;
}
@SuppressWarnings("rawtypes")
public static boolean isEmpty(Object obj)
{
if (obj == null)
{
return true;
}
if ((obj instanceof List))
{
return ((List) obj).size() == 0;
}
if ((obj instanceof String))
{
return ((String) obj).trim().equals("");
}
return false;
}
/**
* 判断对象不为空
*
* @param obj
* 对象名
* @return 是否不为空
*/
public static boolean isNotEmpty(Object obj)
{
return !isEmpty(obj);
}
这里就是类型转换和赋值的工具类了,实现思路在上文已经说了,就不在赘述了。
进行转换之后我们就可以进行保存了。
注:可以依据需求不同添加不懂的执行流程,
最后贴一下另外二个平台的人口代码吧
@OrderFetchTaskAnnotation(components = {
DefaultRunStrategy.class,
JdStartEndBuilder.class,
JdDataFetcher.class,
JdDataAssembler.class,
ThirdDataPersist.class,
})
@Component
public class JdOrderFetchTask extends AbstractOrderFetchTask {
/**
* 定时任务
* @throws Exception
*/
@Override
@Scheduled(cron = "0 0/1 * * * ? ")
// @Scheduled(cron = "*/10 * * * * ?")
public void execute()throws Exception {
this.process();
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}
package com.gy.schedule.task;
import com.gy.schedule.task.component.impl.*;
import com.gy.schedule.task.constant.OrderFetchTaskAnnotation;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @description:
* @author: error_0
* @create:
**/
@OrderFetchTaskAnnotation(components = {
DefaultRunStrategy.class,
PddStartEndBuilder.class,
PddDataFetcher.class,
PddDataAssembler.class,
ThirdDataPersist.class,
})
@Component
public class PddOrderFetchTask extends AbstractOrderFetchTask {
/**
* 定时任务
* @throws Exception
*/
@Override
@Scheduled(cron = "0 0/5 * * * ? ")
// @Scheduled(cron = "*/10 * * * * ?")
public void execute()throws Exception {
this.process();
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}