@@ -53,6 +53,7 @@ public class GoofishOrderPipeline {
try {
ErpGoofishOrder row = upsertFromNotify ( appid , notifyBody , notifyBody . toJSONString ( ) ) ;
tryLinkJdOrder ( row ) ;
mergeSummaryFromOrderDetailShape ( row , notifyBody ) ;
refreshDetail ( row ) ;
syncWaybillFromRedis ( row ) ;
tryAutoShip ( row ) ;
@@ -67,6 +68,7 @@ public class GoofishOrderPipeline {
}
ErpGoofishOrder row = upsertFromNotify ( appKey , item , lastNotifyJson ) ;
tryLinkJdOrder ( row ) ;
mergeSummaryFromOrderDetailShape ( row , item ) ;
refreshDetail ( row ) ;
syncWaybillFromRedis ( row ) ;
tryAutoShip ( row ) ;
@@ -151,7 +153,7 @@ public class GoofishOrderPipeline {
if ( jo ! = null & & jo . getIntValue ( " code " ) = = 0 ) {
JSONObject data = jo . getJSONObject ( " data " ) ;
if ( data ! = null ) {
mergeSummaryFromDetail ( row , data ) ;
mergeSummaryFromOrder DetailShape ( row , data ) ;
}
}
} catch ( Exception ex ) {
@@ -159,34 +161,212 @@ public class GoofishOrderPipeline {
}
}
private void mergeSummaryFromDetail ( ErpGoofishOrder row , JSONObject data ) {
/**
* 合并「订单详情 / 订单列表 / 推送」等同一 schema( order_detail) 上的展示与发货摘要字段。
* 列表与推送含 prov_name、city_name、area_name、town_name、address, 详情接口常不含明文地址。
*/
private void mergeSummaryFromOrderDetailShape ( ErpGoofishOrder row , JSONObject data ) {
if ( row = = null | | row . getId ( ) = = null | | data = = null ) {
return ;
}
ErpGoofishOrder patch = new ErpGoofishOrder ( ) ;
patch . setId ( row . getId ( ) ) ;
boolean any = false ;
JSONObject goods = data . getJSONObject ( " goods " ) ;
if ( goods ! = null ) {
String title = goods . getString ( " title " ) ;
if ( StringUtils . isNotEmpty ( title ) ) {
patch . setGoodsTitle ( title . trim ( ) ) ;
}
String cover = firstGoodsCoverUrl ( goods ) ;
if ( StringUtils . isNotEmpty ( cover ) ) {
patch . setGoodsImageUrl ( cover ) ;
}
Long gPid = goods . getLong ( " product_id " ) ;
if ( gPid ! = null ) {
patch . setProductId ( gPid ) ;
}
Long gIid = goods . getLong ( " item_id " ) ;
if ( gIid ! = null ) {
patch . setItemId ( gIid ) ;
}
}
String wb = data . getString ( " waybill_no " ) ;
if ( StringUtils . isNotEmpty ( wb ) ) {
patch . setDetailWaybillNo ( wb . trim ( ) ) ;
}
String dec = data . getString ( " express_code " ) ;
if ( StringUtils . isNotEmpty ( dec ) ) {
patch . setDetailExpressCode ( dec . trim ( ) ) ;
}
String den = data . getString ( " express_name " ) ;
if ( StringUtils . isNotEmpty ( den ) ) {
patch . setDetailExpressName ( den . trim ( ) ) ;
}
String bn = data . getString ( " buyer_nick " ) ;
if ( StringUtils . isNotEmpty ( bn ) ) {
patch . setBuyerNick ( bn . trim ( ) ) ;
}
Long payFen = data . getLong ( " pay_amount " ) ;
if ( payFen ! = null ) {
patch . setPayAmount ( payFen ) ;
}
String seller = data . getString ( " seller_name " ) ;
if ( StringUtils . isEmpty ( row . getUserName ( ) ) & & StringUtils . isNotEmpty ( seller ) ) {
patch . setUserName ( seller . trim ( ) ) ;
}
patch . setReceiverName ( firstNonEmpty ( data , " receiver_name " , " ship_name " , " consignee_name " , " contact_name " ) ) ;
patch . setReceiverMobile ( firstNonEmpty ( data , " receiver_mobile " , " ship_mobile " , " receiver_phone " , " contact_mobile " ) ) ;
patch . setReceiverAddress ( firstNonEmpty ( data , " receiver_address " , " ship_address " , " detail_address " ,
" full_address " , " address " ) ) ;
String prov = firstNonEmpty ( data , " receiver_province " , " ship_prov_name " , " prov " , " prov_name " ) ;
String city = firstNonEmpty ( data , " receiver_city " , " ship_city_name " , " city " , " city_name " ) ;
String area = firstNonEmpty ( data , " receiver_district " , " receiver_area " , " ship_area_name " , " district " , " area_name " ) ;
String town = firstNonEmpty ( data , " town_name " , " receiver_town " , " ship_town_name " , " street_name " ) ;
String regionJoined = joinWithSpace ( prov , city , area , town ) ;
if ( StringUtils . isNotEmpty ( regionJoined ) ) {
patch . setReceiverRegion ( regionJoined ) ;
}
if ( StringUtils . isNotEmpty ( prov ) ) {
patch . setRecvProvName ( prov . trim ( ) ) ;
}
if ( StringUtils . isNotEmpty ( city ) ) {
patch . setRecvCityName ( city . trim ( ) ) ;
}
if ( StringUtils . isNotEmpty ( area ) ) {
patch . setRecvAreaName ( area . trim ( ) ) ;
}
if ( StringUtils . isNotEmpty ( town ) ) {
patch . setRecvTownName ( town . trim ( ) ) ;
}
JSONObject recv = data . getJSONObject ( " receiver " ) ;
if ( recv ! = null ) {
if ( StringUtils . isEmpty ( patch . getReceiverName ( ) ) ) {
patch . setReceiverName ( recv . getString ( " name " ) ) ;
}
if ( StringUtils . isEmpty ( patch . getReceiverMobile ( ) ) ) {
String m = recv . getString ( " mobile " ) ;
if ( StringUtils . isEmpty ( m ) ) {
m = recv . getString ( " phone " ) ;
}
patch . setReceiverMobile ( m ) ;
}
if ( StringUtils . isEmpty ( patch . getReceiverAddress ( ) ) ) {
patch . setReceiverAddress ( recv . getString ( " address " ) ) ;
}
}
Integer os = data . getInteger ( " order_status " ) ;
if ( os ! = null ) {
patch . setOrderStatus ( os ) ;
row . setOrderStatus ( os ) ;
any = true ;
}
Integer rs = data . getInteger ( " refund_status " ) ;
if ( rs ! = null ) {
patch . setRefundStatus ( rs ) ;
row . setRefundStatus ( rs ) ;
any = true ;
}
Long mt = data . getLong ( " modify_time " ) ;
if ( mt = = null ) {
mt = data . getLong ( " order_modify_time " ) ;
}
if ( mt = = null ) {
mt = data . getLong ( " update_time " ) ;
}
if ( mt ! = null ) {
patch . setModifyTime ( mt ) ;
row . setModifyTime ( mt ) ;
any = true ;
}
if ( any ) {
patch . setUpdateTime ( DateUtils . getNowDate ( ) ) ;
erpGoofishOrderMapper . update ( patch ) ;
fillReceiverFromJdOrder ( patch , row ) ;
patch . setUpdateTime ( DateUtils . getNowDate ( ) ) ;
erpGoofishOrderMapper . update ( patch ) ;
applySummaryPatchToRow ( row , patch ) ;
}
/** 闲鱼详情常不返回明文地址:用已关联的 jd_order.address 兜底;平台已回收货人/分级地址时不覆盖姓名 */
private void fillReceiverFromJdOrder ( ErpGoofishOrder patch , ErpGoofishOrder row ) {
if ( row . getJdOrderId ( ) = = null ) {
return ;
}
boolean platformCare = StringUtils . isNotEmpty ( patch . getReceiverName ( ) )
| | StringUtils . isNotEmpty ( patch . getReceiverMobile ( ) )
| | StringUtils . isNotEmpty ( patch . getRecvProvName ( ) )
| | StringUtils . isNotEmpty ( patch . getRecvCityName ( ) )
| | StringUtils . isNotEmpty ( patch . getRecvAreaName ( ) )
| | StringUtils . isNotEmpty ( patch . getRecvTownName ( ) ) ;
JDOrder jd = jdOrderService . selectJDOrderById ( row . getJdOrderId ( ) ) ;
if ( jd = = null ) {
return ;
}
if ( StringUtils . isEmpty ( patch . getReceiverAddress ( ) ) & & StringUtils . isNotEmpty ( jd . getAddress ( ) ) ) {
patch . setReceiverAddress ( jd . getAddress ( ) . trim ( ) ) ;
}
if ( platformCare ) {
return ;
}
if ( StringUtils . isEmpty ( patch . getReceiverName ( ) ) & & StringUtils . isNotEmpty ( jd . getBuyer ( ) ) ) {
patch . setReceiverName ( jd . getBuyer ( ) . trim ( ) ) ;
}
}
private void applySummaryPatchToRow ( ErpGoofishOrder row , ErpGoofishOrder patch ) {
if ( patch . getGoodsTitle ( ) ! = null ) {
row . setGoodsTitle ( patch . getGoodsTitle ( ) ) ;
}
if ( patch . getGoodsImageUrl ( ) ! = null ) {
row . setGoodsImageUrl ( patch . getGoodsImageUrl ( ) ) ;
}
if ( patch . getBuyerNick ( ) ! = null ) {
row . setBuyerNick ( patch . getBuyerNick ( ) ) ;
}
if ( patch . getPayAmount ( ) ! = null ) {
row . setPayAmount ( patch . getPayAmount ( ) ) ;
}
if ( patch . getDetailWaybillNo ( ) ! = null ) {
row . setDetailWaybillNo ( patch . getDetailWaybillNo ( ) ) ;
}
if ( patch . getDetailExpressCode ( ) ! = null ) {
row . setDetailExpressCode ( patch . getDetailExpressCode ( ) ) ;
}
if ( patch . getDetailExpressName ( ) ! = null ) {
row . setDetailExpressName ( patch . getDetailExpressName ( ) ) ;
}
if ( patch . getReceiverName ( ) ! = null ) {
row . setReceiverName ( patch . getReceiverName ( ) ) ;
}
if ( patch . getReceiverMobile ( ) ! = null ) {
row . setReceiverMobile ( patch . getReceiverMobile ( ) ) ;
}
if ( patch . getReceiverAddress ( ) ! = null ) {
row . setReceiverAddress ( patch . getReceiverAddress ( ) ) ;
}
if ( patch . getReceiverRegion ( ) ! = null ) {
row . setReceiverRegion ( patch . getReceiverRegion ( ) ) ;
}
if ( patch . getRecvProvName ( ) ! = null ) {
row . setRecvProvName ( patch . getRecvProvName ( ) ) ;
}
if ( patch . getRecvCityName ( ) ! = null ) {
row . setRecvCityName ( patch . getRecvCityName ( ) ) ;
}
if ( patch . getRecvAreaName ( ) ! = null ) {
row . setRecvAreaName ( patch . getRecvAreaName ( ) ) ;
}
if ( patch . getRecvTownName ( ) ! = null ) {
row . setRecvTownName ( patch . getRecvTownName ( ) ) ;
}
if ( patch . getUserName ( ) ! = null ) {
row . setUserName ( patch . getUserName ( ) ) ;
}
if ( patch . getOrderStatus ( ) ! = null ) {
row . setOrderStatus ( patch . getOrderStatus ( ) ) ;
}
if ( patch . getRefundStatus ( ) ! = null ) {
row . setRefundStatus ( patch . getRefundStatus ( ) ) ;
}
if ( patch . getModifyTime ( ) ! = null ) {
row . setModifyTime ( patch . getModifyTime ( ) ) ;
}
if ( patch . getProductId ( ) ! = null ) {
row . setProductId ( patch . getProductId ( ) ) ;
}
if ( patch . getItemId ( ) ! = null ) {
row . setItemId ( patch . getItemId ( ) ) ;
}
}
@@ -223,7 +403,11 @@ public class GoofishOrderPipeline {
if ( row . getShipStatus ( ) ! = null & & row . getShipStatus ( ) = = 1 ) {
return ;
}
if ( StringUtils . isEmpty ( row . getLocalWaybillNo ( ) ) ) {
String waybill = row . getLocalWaybillNo ( ) ;
if ( StringUtils . isEmpty ( waybill ) ) {
waybill = row . getDetailWaybillNo ( ) ;
}
if ( StringUtils . isEmpty ( waybill ) ) {
return ;
}
ErpOpenConfig cfg = erpOpenConfigService . selectByAppKey ( row . getAppKey ( ) ) ;
@@ -234,9 +418,27 @@ public class GoofishOrderPipeline {
return ;
}
ShipAddressParts addr = parseShipAddress ( row . getDetailJson ( ) ) ;
if ( addr = = null | | StringUtils . isEmpty ( addr . shipName ) | | StringUtils . isEmpty ( addr . shipMobile )
if ( addr = = null ) {
addr = new ShipAddressParts ( ) ;
}
enrichShipAddressFromJd ( row , addr ) ;
if ( StringUtils . isEmpty ( addr . shipAddress ) ) {
ShipAddressParts fromRow = receiverFieldsToShipParts ( row ) ;
if ( fromRow ! = null ) {
if ( StringUtils . isEmpty ( addr . shipName ) ) {
addr . shipName = fromRow . shipName ;
}
if ( StringUtils . isEmpty ( addr . shipMobile ) ) {
addr . shipMobile = fromRow . shipMobile ;
}
if ( StringUtils . isEmpty ( addr . shipAddress ) ) {
addr . shipAddress = fromRow . shipAddress ;
}
}
}
if ( StringUtils . isEmpty ( addr . shipName ) | | StringUtils . isEmpty ( addr . shipMobile )
| | StringUtils . isEmpty ( addr . shipAddress ) ) {
patchShipError ( row , " 详情中 缺少收货人/手机/地址,请核对开放平台订单详情 JSON 字段" ) ;
patchShipError ( row , " 缺少收货人/手机/地址:详情无字段时请关联京东单并维护地址,或等平台返回收货 字段 " ) ;
return ;
}
try {
@@ -258,7 +460,7 @@ public class GoofishOrderPipeline {
if ( StringUtils . isNotEmpty ( addr . shipAreaName ) ) {
ship . setShipAreaName ( addr . shipAreaName ) ;
}
ship . setWaybillNo ( row . getLocalWaybillNo ( ) ) ;
ship . setWaybillNo ( waybill . trim ( ) ) ;
ship . setExpressCode ( expressCode ) ;
ship . setExpressName ( expressName ) ;
String resp = ship . getResponseBody ( ) ;
@@ -308,6 +510,47 @@ public class GoofishOrderPipeline {
String shipAreaName ;
}
private void enrichShipAddressFromJd ( ErpGoofishOrder row , ShipAddressParts p ) {
if ( row = = null | | p = = null | | row . getJdOrderId ( ) = = null ) {
return ;
}
JDOrder jd = jdOrderService . selectJDOrderById ( row . getJdOrderId ( ) ) ;
if ( jd = = null ) {
return ;
}
if ( StringUtils . isEmpty ( p . shipAddress ) & & StringUtils . isNotEmpty ( jd . getAddress ( ) ) ) {
p . shipAddress = jd . getAddress ( ) . trim ( ) ;
}
if ( StringUtils . isEmpty ( p . shipName ) & & StringUtils . isNotEmpty ( jd . getBuyer ( ) ) ) {
p . shipName = jd . getBuyer ( ) . trim ( ) ;
}
}
private ShipAddressParts receiverFieldsToShipParts ( ErpGoofishOrder row ) {
if ( row = = null ) {
return null ;
}
String regionLine = row . getReceiverRegion ( ) ;
if ( StringUtils . isEmpty ( regionLine ) ) {
regionLine = joinWithSpace ( row . getRecvProvName ( ) , row . getRecvCityName ( ) , row . getRecvAreaName ( ) , row . getRecvTownName ( ) ) ;
}
if ( StringUtils . isEmpty ( row . getReceiverAddress ( ) ) & & StringUtils . isEmpty ( regionLine ) ) {
return null ;
}
ShipAddressParts p = new ShipAddressParts ( ) ;
p . shipName = row . getReceiverName ( ) ;
p . shipMobile = row . getReceiverMobile ( ) ;
p . shipAddress = row . getReceiverAddress ( ) ;
if ( StringUtils . isNotEmpty ( regionLine ) ) {
if ( StringUtils . isNotEmpty ( p . shipAddress ) ) {
p . shipAddress = regionLine + " " + p . shipAddress ;
} else {
p . shipAddress = regionLine ;
}
}
return p ;
}
private ShipAddressParts parseShipAddress ( String detailJson ) {
if ( StringUtils . isEmpty ( detailJson ) ) {
return null ;
@@ -323,11 +566,15 @@ public class GoofishOrderPipeline {
ShipAddressParts p = new ShipAddressParts ( ) ;
p . shipName = firstNonEmpty ( data , " ship_name " , " receiver_name " , " consignee " , " contact_name " ) ;
p . shipMobile = firstNonEmpty ( data , " ship_mobile " , " receiver_mobile " , " receiver_phone " , " contact_mobile " ) ;
p . shipAddress = firstNonEmpty ( data , " ship_address " , " receiver_address " , " detail_address " ) ;
p . shipAddress = firstNonEmpty ( data , " ship_address " , " receiver_address " , " detail_address " , " address " );
p . shipDistrictId = firstInt ( data , " ship_district_id " , " receiver_district_id " ) ;
p . shipProvName = firstNonEmpty ( data , " ship_prov_name " , " receiver_province " , " prov " ) ;
p . shipCityName = firstNonEmpty ( data , " ship_city_name " , " receiver_city " , " city " ) ;
p . shipAreaName = firstNonEmpty ( data , " ship_area_name " , " receiver_area " , " area " , " district " ) ;
p . shipProvName = firstNonEmpty ( data , " ship_prov_name " , " receiver_province " , " prov " , " prov_name " );
p . shipCityName = firstNonEmpty ( data , " ship_city_name " , " receiver_city " , " city " , " city_name " );
p . shipAreaName = firstNonEmpty ( data , " ship_area_name " , " receiver_area " , " area " , " district " , " area_name " );
String shipTown = firstNonEmpty ( data , " town_name " , " ship_town_name " , " receiver_town " ) ;
if ( StringUtils . isNotEmpty ( shipTown ) & & StringUtils . isEmpty ( p . shipAddress ) ) {
p . shipAddress = shipTown ;
}
JSONObject recv = data . getJSONObject ( " receiver " ) ;
if ( recv ! = null ) {
if ( StringUtils . isEmpty ( p . shipName ) ) {
@@ -346,6 +593,43 @@ public class GoofishOrderPipeline {
return p ;
}
/** 开放平台 goods.images 首元素,或其它常见单图字段 */
private static String firstGoodsCoverUrl ( JSONObject goods ) {
if ( goods = = null ) {
return null ;
}
for ( String k : new String [ ] { " image " , " pic " , " cover " , " main_image " , " img " , " item_pic " } ) {
String v = goods . getString ( k ) ;
if ( StringUtils . isNotEmpty ( v ) ) {
return v . trim ( ) ;
}
}
JSONArray images = goods . getJSONArray ( " images " ) ;
if ( images = = null | | images . isEmpty ( ) ) {
return null ;
}
Object first = images . get ( 0 ) ;
if ( first = = null ) {
return null ;
}
String u = first . toString ( ) . trim ( ) ;
return StringUtils . isNotEmpty ( u ) ? u : null ;
}
/** 省、市、区、街道等与列表接口 prov_name/city_name/area_name/town_name 对齐拼接 */
private static String joinWithSpace ( String . . . parts ) {
StringBuilder sb = new StringBuilder ( ) ;
for ( String p : parts ) {
if ( StringUtils . isNotEmpty ( p ) ) {
if ( sb . length ( ) > 0 ) {
sb . append ( ' ' ) ;
}
sb . append ( p . trim ( ) ) ;
}
}
return sb . length ( ) = = 0 ? null : sb . toString ( ) ;
}
private static String firstNonEmpty ( JSONObject o , String . . . keys ) {
if ( o = = null ) {
return null ;
@@ -372,23 +656,76 @@ public class GoofishOrderPipeline {
return null ;
}
/**
* 增量拉单:按 update_time ∈ [now− lookbackHours, now]
*/
public int pullForAppKey ( String appKey , int lookbackHours ) {
if ( StringUtils . isEmpty ( appKey ) ) {
return 0 ;
}
IERPAccount cred = erpAccountResolver . resolve ( appKey ) ;
long now = System . currentTimeMillis ( ) / 1000 ;
long start = now - ( long ) lookbackHours * 3600L ;
return pullForAppKeyUpdateTimeRange ( appKey , start , now ) ;
}
/**
* 长历史全量:从「当前时间 − pullFullHistoryDays」起, 按 pullTimeChunkSeconds 切 update_time 窗口逐段拉取;
* 每段内 page_size、page_no 与开放平台上限对齐,降低 page_no× page_size 超 1 万的风险。
*/
public int pullForAppKeyFullHistory ( String appKey ) {
if ( StringUtils . isEmpty ( appKey ) ) {
return 0 ;
}
long now = System . currentTimeMillis ( ) / 1000 ;
int days = goofishProperties . getPullFullHistoryDays ( ) ;
if ( days < 1 ) {
days = 1 ;
}
long start = now - ( long ) days * 86400L ;
int chunk = goofishProperties . getPullTimeChunkSeconds ( ) ;
if ( chunk < 3600 ) {
chunk = 86400 ;
}
int total = 0 ;
for ( long t = start ; t < = now ; t + = chunk ) {
long end = Math . min ( t + chunk - 1 , now ) ;
if ( end < t ) {
break ;
}
total + = pullForAppKeyUpdateTimeRange ( appKey , t , end ) ;
try {
Thread . sleep ( 150 ) ;
} catch ( InterruptedException ie ) {
Thread . currentThread ( ) . interrupt ( ) ;
break ;
}
}
return total ;
}
/**
* 按开放平台「订单列表」接口,限定 update_time 时间戳区间(秒,闭区间)拉取并落库。
*/
public int pullForAppKeyUpdateTimeRange ( String appKey , long updateTimeStartSec , long updateTimeEndSec ) {
if ( StringUtils . isEmpty ( appKey ) | | updateTimeEndSec < updateTimeStartSec ) {
return 0 ;
}
IERPAccount cred = erpAccountResolver . resolve ( appKey ) ;
List < Long > authorizeIds = fetchAuthorizeIds ( cred ) ;
int pageSize = Math . min ( 100 , Math . max ( 1 , goofishProperties . getPullPageSize ( ) ) ) ;
int maxPages = Math . min ( 100 , Math . max ( 1 , goofishProperties . getPullMaxPagesPerShop ( ) ) ) ;
if ( ( long ) maxPages * pageSize > 10000L ) {
maxPages = 10000 / pageSize ;
log . warn ( " 闲管家拉单: pull-max-pages 与 pull-page-size 乘积超过 10000, 已收敛 maxPages={} " , maxPages ) ;
}
int saved = 0 ;
int maxPages = goofishProperties . getPullMaxPagesPerShop ( ) ;
for ( Long aid : authorizeIds ) {
int page = 1 ;
while ( page < = maxPages ) {
OrderListQueryRequest q = new OrderListQueryRequest ( cred ) ;
q . setAuthorizeId ( aid ) ;
q . setUpdateTime ( s tart, now ) ;
q . setPage ( page , 20 ) ;
q . setUpdateTime ( updateTimeS tartSec , updateTimeEndSec ) ;
q . setPage ( page , pageSize ) ;
String resp ;
try {
resp = q . getResponseBody ( ) ;
@@ -411,7 +748,12 @@ public class GoofishOrderPipeline {
applyListOrNotifyItem ( appKey , item , item . toJSONString ( ) ) ;
saved + + ;
}
if ( list . size ( ) < 20 ) {
if ( list . size ( ) < pageSize ) {
break ;
}
if ( page = = maxPages ) {
log . warn ( " 闲管家拉单已达最大页数 appKey={} aid={} 区间[{},{}];若订单更多请缩小 pull-time-chunk-seconds " ,
appKey , aid , updateTimeStartSec , updateTimeEndSec ) ;
break ;
}
page + + ;