根据手机号拉取买家订儿拉取买家订单。

This commit is contained in:
2026-06-06 08:27:36 +08:00
parent 1477ec943b
commit 7413485718
9 changed files with 309 additions and 118 deletions

View File

@@ -16,7 +16,9 @@ public final class LbThirdIntegrationConstants {
public static final String DEFAULT_USER_UPDATE_PATH = "/app/admin/user/update";
public static final String DEFAULT_GOODS_API_PATH = "/api/order/goods";
public static final String DEFAULT_BUY_API_PATH = "/api/order/buy";
public static final String DEFAULT_BUYER_ORDER_LIST_PATH = "/app/admin/order/buy/select";
public static final String DEFAULT_BUYER_ORDER_LIST_PATH = "/api/order/list";
public static final int DEFAULT_BUYER_ORDER_LIST_CATE = 1;
public static final int DEFAULT_BUYER_ORDER_LIST_TYPE = 3;
public static final String DEFAULT_FANS_API_PATH = "/api/share/select";
public static final String DEFAULT_LOGIN_API_PATH = "/api/user/login";

View File

@@ -49,7 +49,7 @@ public class LbBuyerShoppingController {
return toResponse(result);
}
@PostMapping("/pull-from-third")
@PostMapping("/pull-order-from-third")
@Operation(
summary = "从第三方拉取买方购物列表",
description =

View File

@@ -0,0 +1,12 @@
package com.rj.dto.hxr;
/**
* 买方订单列表 API 端点(由 {@code lb_third_integration_config.buyer_order_list_path} 解析而来)。
* <p>路径可带固定查询参数,例如 {@code /api/order/list?cate=1&type=3}。
*/
public record BuyerOrderListEndpoint(
String baseUrl,
int cate,
int type,
int pageLimit) {
}

View File

@@ -6,6 +6,8 @@ package com.rj.dto.hxr;
public record HxrBuyerOrderApiContext(
String buyerOrderListBaseUrl,
int pageLimit,
int cate,
int type,
String origin,
String referer,
String token,

View File

@@ -4,10 +4,10 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
import com.rj.dto.hxr.HxrOrderRow;
import com.rj.entity.LbBuyerShopping;
import com.rj.util.HxrGoodsSignUtil;
import com.rj.util.LbBuyerShoppingOrderParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
@@ -18,7 +18,6 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -72,7 +71,11 @@ public class HxrAdminBuyerOrderSelectService {
int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : 90;
int safePage = Math.max(1, page);
int cate = ctx.cate() > 0 ? ctx.cate() : 1;
int type = ctx.type() > 0 ? ctx.type() : 3;
String uri = UriComponentsBuilder.fromUriString(baseUrl)
.replaceQueryParam("cate", cate)
.replaceQueryParam("type", type)
.replaceQueryParam("page", safePage)
.replaceQueryParam("limit", pageLimit)
.build()
@@ -82,12 +85,19 @@ public class HxrAdminBuyerOrderSelectService {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = randomNoncestr();
Map<String, Object> signParams = new LinkedHashMap<>();
signParams.put("cate", cate);
signParams.put("type", type);
signParams.put("page", safePage);
signParams.put("limit", pageLimit);
signParams.put("timestamp", timestamp);
signParams.put("noncestr", noncestr);
String sign = HxrGoodsSignUtil.computeSign(signParams, appStr.trim());
if (log.isDebugEnabled()) {
log.debug("hxr buyer order list request uri={} cate={} type={} tokenPrefix={}",
uri, cate, type, abbreviate(resolvedToken, 8));
}
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NORMAL)
@@ -126,8 +136,8 @@ public class HxrAdminBuyerOrderSelectService {
int code = root.path("code").asInt(-1);
String msg = root.path("msg").asText("");
if (code != 0) {
log.warn("hxr buyer order list api code={} msg={} page={} bodyPrefix={}",
code, msg, safePage, abbreviate(bodyText, 400));
log.warn("hxr buyer order list api code={} msg={} page={} uri={} bodyPrefix={}",
code, msg, safePage, uri, abbreviate(bodyText, 400));
return Optional.empty();
}
@@ -137,47 +147,13 @@ public class HxrAdminBuyerOrderSelectService {
return Optional.empty();
}
List<HxrOrderRow> rows = parseOrderRows(dataNode);
int count = root.path("count").asInt(rows.size());
List<LbBuyerShopping> orders = LbBuyerShoppingOrderParser.parseOrderList(dataNode);
int count = root.path("count").asInt(orders.size());
String allMoney = root.path("all_money").asText("");
boolean hasMore;
int lastPage;
if (dataNode != null && dataNode.isObject()) {
hasMore = dataNode.path("hasmore").asBoolean(false);
lastPage = dataNode.path("last_page").asInt(safePage);
if (lastPage < 1) {
lastPage = safePage;
}
} else {
hasMore = rows.size() >= pageLimit;
lastPage = hasMore ? Integer.MAX_VALUE : safePage;
}
boolean hasMore = LbBuyerShoppingOrderParser.resolveHasMore(dataNode, orders.size(), pageLimit);
int lastPage = LbBuyerShoppingOrderParser.resolveLastPage(dataNode, safePage, hasMore);
return Optional.of(new BuyerOrderPageResult(rows, count, allMoney, hasMore, lastPage, safePage));
}
private static List<HxrOrderRow> parseOrderRows(JsonNode dataNode) throws Exception {
if (dataNode == null || dataNode.isNull()) {
return List.of();
}
JsonNode listNode;
if (dataNode.isArray()) {
listNode = dataNode;
} else if (dataNode.isObject()) {
listNode = dataNode.get("list");
} else {
return List.of();
}
if (listNode == null || !listNode.isArray() || listNode.isEmpty()) {
return List.of();
}
if (listNode.get(0).isTextual() || listNode.get(0).isNumber()) {
return List.of();
}
CollectionType listType =
JSON.getTypeFactory().constructCollectionType(List.class, HxrOrderRow.class);
List<HxrOrderRow> rows = JSON.convertValue(listNode, listType);
return rows != null ? rows : new ArrayList<>();
return Optional.of(new BuyerOrderPageResult(orders, count, allMoney, hasMore, lastPage, safePage));
}
private static String randomNoncestr() {
@@ -210,7 +186,7 @@ public class HxrAdminBuyerOrderSelectService {
}
public record BuyerOrderPageResult(
List<HxrOrderRow> rows,
List<LbBuyerShopping> orders,
int count,
String allMoney,
boolean hasMore,

View File

@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.dto.LbBuyerShoppingPullRequest;
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
import com.rj.dto.hxr.HxrOrderRow;
import com.rj.dto.hxr.HxrUserLoginApiContext;
import com.rj.entity.LbBuyerShopping;
import com.rj.mapper.LbBuyerShoppingMapper;
@@ -21,7 +20,6 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -461,17 +459,19 @@ public class LbBuyerShoppingServiceImpl
allMoney = pageResult.allMoney();
}
List<HxrOrderRow> rows = pageResult.rows();
if (rows == null || rows.isEmpty()) {
List<LbBuyerShopping> orders = pageResult.orders();
if (orders == null || orders.isEmpty()) {
break;
}
totalFetched += rows.size();
totalFetched += orders.size();
for (HxrOrderRow row : rows) {
LbBuyerShopping entity = toLbBuyerShopping(row, buyerMobile);
for (LbBuyerShopping entity : orders) {
if (entity.getId() == null) {
continue;
}
if (entity.getBuyerMobile() == null || entity.getBuyerMobile().isBlank()) {
entity.setBuyerMobile(buyerMobile);
}
applyDefaults(entity);
if (entity.getCreatedAt() == null) {
entity.setCreatedAt(LocalDateTime.now());
@@ -492,7 +492,7 @@ public class LbBuyerShoppingServiceImpl
int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : 90;
boolean hasMore = pageResult.hasMore();
int lastPage = pageResult.lastPage();
if (pageResult.rows().size() < pageLimit) {
if (orders.size() < pageLimit) {
break;
}
if (!hasMore || page >= lastPage) {
@@ -526,56 +526,6 @@ public class LbBuyerShoppingServiceImpl
}
}
private static LbBuyerShopping toLbBuyerShopping(HxrOrderRow row, String fallbackBuyerMobile) {
LbBuyerShopping entity = new LbBuyerShopping();
entity.setId(row.id());
entity.setSellerId(row.sellerId());
entity.setMerchandiseId(row.merchandiseId());
entity.setBuyerId(row.buyerId());
entity.setOrderSn(row.orderSn());
entity.setTotalMoney(parseMoney(row.totalMoney()));
entity.setCreatedAt(parseFlexibleDateTime(row.createdAt()));
entity.setBuyTime(parseFlexibleDateTime(row.buyTime()));
entity.setConfirmTime(parseFlexibleDateTime(row.confirmTime()));
entity.setSellerNickname(row.sellerName());
entity.setSellerMobile(row.sellerPhone());
entity.setBuyerNickname(row.buyerName());
String buyerMobile = row.buyerPhone();
if (buyerMobile == null || buyerMobile.isBlank()) {
buyerMobile = fallbackBuyerMobile;
}
entity.setBuyerMobile(buyerMobile);
return entity;
}
private static BigDecimal parseMoney(String money) {
if (money == null || money.trim().isEmpty()) {
return null;
}
try {
return new BigDecimal(money.trim());
} catch (NumberFormatException e) {
return null;
}
}
private static LocalDateTime parseFlexibleDateTime(String text) {
if (text == null || text.trim().isEmpty()) {
return null;
}
String trimmed = text.trim();
try {
return LocalDateTime.parse(trimmed, DATE_TIME_FORMATTER);
} catch (DateTimeParseException ignored) {
// continue
}
try {
return LocalDateTime.parse(trimmed, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (DateTimeParseException e) {
return null;
}
}
private static int toInt(Object value) {
if (value instanceof Number number) {
return number.intValue();

View File

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.common.LbThirdIntegrationConstants;
import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest;
import com.rj.dto.hxr.BuyerOrderListEndpoint;
import com.rj.dto.hxr.HxrAdminOrderApiContext;
import com.rj.dto.hxr.HxrAdminUserApiContext;
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
@@ -418,11 +419,15 @@ public class LbThirdIntegrationConfigServiceImpl
}
try {
BuyerOrderListEndpoint endpoint =
LbThirdIntegrationConfigUtil.resolveBuyerOrderListEndpoint(config);
return Optional.of(new HxrBuyerOrderApiContext(
LbThirdIntegrationConfigUtil.resolveBuyerOrderListBaseUrl(config),
LbThirdIntegrationConfigUtil.resolveBuyerOrderListLimit(config),
endpoint.baseUrl(),
endpoint.pageLimit(),
endpoint.cate(),
endpoint.type(),
LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config),
LbThirdIntegrationConfigUtil.resolveOrderReferer(config),
LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config),
token.trim(),
appStr.trim()));
} catch (IllegalStateException e) {

View File

@@ -0,0 +1,173 @@
package com.rj.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.rj.entity.LbBuyerShopping;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
/**
* 将 hxrd {@code /api/order/list} 响应中的订单节点解析为 {@link LbBuyerShopping}。
*/
public final class LbBuyerShoppingOrderParser {
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private LbBuyerShoppingOrderParser() {
}
public static List<LbBuyerShopping> parseOrderList(JsonNode dataNode) {
JsonNode listNode = extractListNode(dataNode);
if (listNode == null || !listNode.isArray() || listNode.isEmpty()) {
return List.of();
}
if (listNode.get(0).isTextual() || listNode.get(0).isNumber()) {
return List.of();
}
List<LbBuyerShopping> orders = new ArrayList<>(listNode.size());
for (JsonNode item : listNode) {
LbBuyerShopping entity = fromOrderNode(item);
if (entity != null) {
orders.add(entity);
}
}
return orders;
}
public static boolean resolveHasMore(JsonNode dataNode, int rowCount, int pageLimit) {
if (dataNode != null && dataNode.isObject()) {
if (dataNode.has("hasmore")) {
return dataNode.path("hasmore").asBoolean(false);
}
}
return rowCount >= pageLimit;
}
public static int resolveLastPage(JsonNode dataNode, int safePage, boolean hasMore) {
if (dataNode != null && dataNode.isObject()) {
int lastPage = dataNode.path("last_page").asInt(-1);
if (lastPage >= 1) {
return lastPage;
}
}
return hasMore ? Integer.MAX_VALUE : safePage;
}
public static LbBuyerShopping fromOrderNode(JsonNode node) {
if (node == null || node.isNull() || !node.isObject()) {
return null;
}
Long id = longValue(node.get("id"));
if (id == null) {
return null;
}
LbBuyerShopping entity = new LbBuyerShopping();
entity.setId(id);
entity.setSellerId(longValue(node.get("seller_id")));
entity.setMerchandiseId(longValue(node.get("merchandise_id")));
entity.setBuyerId(longValue(node.get("buyer_id")));
entity.setOrderSn(textValue(node.get("order_sn")));
entity.setTotalMoney(parseMoney(textValue(node.get("total_money"))));
entity.setNewTotal(parseMoney(textValue(node.get("new_total"))));
entity.setCreatedAt(parseDateTime(textValue(node.get("created_at"))));
entity.setBuyTime(parseDateTime(textValue(node.get("buy_time"))));
entity.setConfirmTime(parseDateTime(textValue(node.get("confirm_time"))));
JsonNode seller = node.get("seller");
if (seller != null && seller.isObject()) {
entity.setSellerNickname(textValue(seller.get("nickname")));
entity.setSellerMobile(textValue(seller.get("mobile")));
} else {
entity.setSellerNickname(textValue(node.get("seller_name")));
entity.setSellerMobile(textValue(node.get("seller_phone")));
}
JsonNode buyer = node.get("buyer");
if (buyer != null && buyer.isObject()) {
entity.setBuyerNickname(textValue(buyer.get("nickname")));
entity.setBuyerMobile(textValue(buyer.get("mobile")));
} else {
entity.setBuyerNickname(textValue(node.get("buyer_name")));
entity.setBuyerMobile(textValue(node.get("buyer_phone")));
}
return entity;
}
private static JsonNode extractListNode(JsonNode dataNode) {
if (dataNode == null || dataNode.isNull()) {
return null;
}
if (dataNode.isArray()) {
return dataNode;
}
if (dataNode.isObject()) {
return dataNode.get("list");
}
return null;
}
private static Long longValue(JsonNode node) {
if (node == null || node.isNull()) {
return null;
}
if (node.isNumber()) {
return node.longValue();
}
String text = node.asText(null);
if (text == null || text.isBlank()) {
return null;
}
try {
return Long.parseLong(text.trim());
} catch (NumberFormatException e) {
return null;
}
}
private static String textValue(JsonNode node) {
if (node == null || node.isNull()) {
return null;
}
String text = node.asText(null);
if (text == null) {
return null;
}
text = text.trim();
return text.isEmpty() ? null : text;
}
private static BigDecimal parseMoney(String money) {
if (money == null || money.isBlank()) {
return null;
}
try {
return new BigDecimal(money.trim());
} catch (NumberFormatException e) {
return null;
}
}
private static LocalDateTime parseDateTime(String text) {
if (text == null || text.isBlank()) {
return null;
}
String trimmed = text.trim();
try {
return LocalDateTime.parse(trimmed, DATE_TIME_FORMATTER);
} catch (DateTimeParseException ignored) {
// continue
}
try {
return LocalDateTime.parse(trimmed, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (DateTimeParseException e) {
return null;
}
}
}

View File

@@ -1,9 +1,13 @@
package com.rj.util;
import com.rj.common.LbThirdIntegrationConstants;
import com.rj.dto.hxr.BuyerOrderListEndpoint;
import com.rj.entity.LbThirdIntegrationConfig;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 从 {@link LbThirdIntegrationConfig} 解析第三方 API 地址与请求头。
*/
@@ -166,19 +170,56 @@ public final class LbThirdIntegrationConfigUtil {
}
public static String resolveBuyerOrderListBaseUrl(LbThirdIntegrationConfig config) {
String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl"));
String path = config.getBuyerOrderListPath();
if (!StringUtils.hasText(path)) {
path = LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_PATH;
}
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
return base + path;
return resolveBuyerOrderListEndpoint(config).baseUrl();
}
public static int resolveBuyerOrderListLimit(LbThirdIntegrationConfig config) {
return resolveBuyerOrderListEndpoint(config).pageLimit();
}
public static int resolveBuyerOrderListCate(LbThirdIntegrationConfig config) {
return resolveBuyerOrderListEndpoint(config).cate();
}
public static int resolveBuyerOrderListType(LbThirdIntegrationConfig config) {
return resolveBuyerOrderListEndpoint(config).type();
}
/**
* 解析买方订单列表 API支持 {@code buyer_order_list_path} 内嵌 {@code ?cate=&type=} 查询参数。
*/
public static BuyerOrderListEndpoint resolveBuyerOrderListEndpoint(LbThirdIntegrationConfig config) {
String adminBase = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl"));
String rawPath = config.getBuyerOrderListPath();
if (!StringUtils.hasText(rawPath)) {
rawPath = LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_PATH;
}
rawPath = rawPath.trim();
String pathOnly = rawPath;
int cate = LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_CATE;
int type = LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_TYPE;
int queryIdx = rawPath.indexOf('?');
if (queryIdx >= 0) {
pathOnly = rawPath.substring(0, queryIdx);
Map<String, String> queryParams = parseQueryString(rawPath.substring(queryIdx + 1));
cate = parsePositiveInt(queryParams.get("cate"), cate);
type = parsePositiveInt(queryParams.get("type"), type);
}
if (!pathOnly.startsWith("/")) {
pathOnly = "/" + pathOnly;
}
return new BuyerOrderListEndpoint(
adminBase + pathOnly,
cate,
type,
resolveBuyerOrderListLimitRaw(config));
}
private static int resolveBuyerOrderListLimitRaw(LbThirdIntegrationConfig config) {
Integer limit = config.getBuyerOrderListLimit();
if (limit == null || limit < 1) {
return LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_LIMIT;
@@ -186,6 +227,36 @@ public final class LbThirdIntegrationConfigUtil {
return limit;
}
private static Map<String, String> parseQueryString(String query) {
Map<String, String> params = new HashMap<>();
if (query == null || query.isBlank()) {
return params;
}
for (String pair : query.split("&")) {
if (pair.isBlank()) {
continue;
}
int eq = pair.indexOf('=');
if (eq <= 0) {
continue;
}
params.put(pair.substring(0, eq).trim(), pair.substring(eq + 1).trim());
}
return params;
}
private static int parsePositiveInt(String text, int defaultValue) {
if (text == null || text.isBlank()) {
return defaultValue;
}
try {
int value = Integer.parseInt(text.trim());
return value > 0 ? value : defaultValue;
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* 组装 Cookie 请求头:优先完整 cookie否则 {@code PHPSID=} + phpsid。
*/