拉取买家订单列表

This commit is contained in:
2026-06-06 07:32:52 +08:00
parent 1a44a5befb
commit 1477ec943b
13 changed files with 714 additions and 2 deletions

View File

@@ -16,10 +16,12 @@ 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_FANS_API_PATH = "/api/share/select";
public static final String DEFAULT_LOGIN_API_PATH = "/api/user/login";
public static final int DEFAULT_ORDER_PAGE_LIMIT = 90;
public static final int DEFAULT_BUYER_ORDER_LIST_LIMIT = 90;
public static final int DEFAULT_USER_PAGE_LIMIT = 90;
public static final int DEFAULT_GOODS_PAGE_LIMIT = 20;
public static final int DEFAULT_FANS_PAGE_LIMIT = 10;

View File

@@ -1,5 +1,6 @@
package com.rj.controller;
import com.rj.dto.LbBuyerShoppingPullRequest;
import com.rj.entity.LbBuyerShopping;
import com.rj.service.ILbBuyerShoppingService;
import io.swagger.v3.oas.annotations.Operation;
@@ -48,6 +49,20 @@ public class LbBuyerShoppingController {
return toResponse(result);
}
@PostMapping("/pull-from-third")
@Operation(
summary = "从第三方拉取买方购物列表",
description =
"按 tenantId 从 lb_third_integration_config 读取 login_api_path、buyer_order_list_path 等配置,"
+ "对每个买方手机号模拟登录获取 token再分页调用买方订单列表 API 并解析入库;"
+ "未传 password 时默认密码为 123456")
public ResponseEntity<Map<String, Object>> pullFromThird(
@Parameter(description = "租户 id 与买方手机号列表", required = true)
@RequestBody LbBuyerShoppingPullRequest request) {
Map<String, Object> result = lbBuyerShoppingService.pullFromThirdParty(request);
return toResponse(result);
}
@GetMapping("/list")
@Operation(summary = "分页查询")
public ResponseEntity<Map<String, Object>> list(

View File

@@ -0,0 +1,23 @@
package com.rj.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 买方购物:按手机号批量模拟登录并拉取第三方订单列表。
*/
@Data
@Schema(description = "买方购物第三方拉取请求")
public class LbBuyerShoppingPullRequest {
@Schema(description = "租户 id关联 lb_third_integration_config.tenant_id", requiredMode = Schema.RequiredMode.REQUIRED)
private String tenantId;
@Schema(description = "买方手机号列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> mobiles;
@Schema(description = "登录密码;未传时默认为 123456")
private String password;
}

View File

@@ -0,0 +1,13 @@
package com.rj.dto.hxr;
/**
* 调用 hxrd 买方订单列表 API 所需的运行时配置(由 {@code lb_third_integration_config} 解析而来)。
*/
public record HxrBuyerOrderApiContext(
String buyerOrderListBaseUrl,
int pageLimit,
String origin,
String referer,
String token,
String appStr) {
}

View File

@@ -68,6 +68,10 @@ public class LbThirdIntegrationConfig implements Serializable {
@Schema(description = "抢购 API 路径")
private String buyApiPath;
@TableField("buyer_order_list_path")
@Schema(description = "买方订单列表 API 路径")
private String buyerOrderListPath;
@TableField("fans_api_path")
@Schema(description = "粉丝列表 API 路径")
private String fansApiPath;
@@ -80,6 +84,10 @@ public class LbThirdIntegrationConfig implements Serializable {
@Schema(description = "订单分页 limit")
private Integer orderPageLimit;
@TableField("buyer_order_list_limit")
@Schema(description = "买方订单列表分页 limit")
private Integer buyerOrderListLimit;
@TableField("user_page_limit")
@Schema(description = "用户分页 limit")
private Integer userPageLimit;

View File

@@ -0,0 +1,220 @@
package com.rj.service;
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.util.HxrGoodsSignUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.net.http.HttpClient;
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;
import java.util.Optional;
/**
* 调用 hxrd 买方订单列表 API请求头 {@code Token} + {@code S/T/N} 鉴权)并解析 JSON。
*/
@Slf4j
@Service
public class HxrAdminBuyerOrderSelectService {
private static final SecureRandom RANDOM = new SecureRandom();
private static final String USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0";
private static final String HEADER_TOKEN = "Token";
private static final ObjectMapper JSON = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
/**
* 分页拉取买方订单列表。
*
* @param page 页码,从 1 开始
*/
public Optional<BuyerOrderPageResult> fetchBuyerOrderPage(int page, HxrBuyerOrderApiContext ctx)
throws Exception {
if (ctx == null) {
log.warn("买方订单 API 配置为空,跳过拉取");
return Optional.empty();
}
String resolvedToken = ctx.token();
if (resolvedToken == null || resolvedToken.isBlank()) {
log.warn("未提供 token跳过买方订单列表拉取");
return Optional.empty();
}
String appStr = ctx.appStr();
if (appStr == null || appStr.isBlank()) {
log.warn("未配置 goodsApiAppStr跳过买方订单列表拉取");
return Optional.empty();
}
String baseUrl = ctx.buyerOrderListBaseUrl();
if (baseUrl == null || baseUrl.isBlank()) {
log.warn("未配置 buyerOrderListBaseUrl跳过买方订单列表拉取");
return Optional.empty();
}
int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : 90;
int safePage = Math.max(1, page);
String uri = UriComponentsBuilder.fromUriString(baseUrl)
.replaceQueryParam("page", safePage)
.replaceQueryParam("limit", pageLimit)
.build()
.encode()
.toUriString();
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = randomNoncestr();
Map<String, Object> signParams = new LinkedHashMap<>();
signParams.put("page", safePage);
signParams.put("limit", pageLimit);
signParams.put("timestamp", timestamp);
signParams.put("noncestr", noncestr);
String sign = HxrGoodsSignUtil.computeSign(signParams, appStr.trim());
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.timeout(Duration.ofSeconds(120))
.header("Accept", "application/json,*/*")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.header("Origin", ctx.origin())
.header("Referer", ctx.referer())
.header("User-Agent", USER_AGENT)
.header(HEADER_TOKEN, resolvedToken)
.header("S", sign)
.header("T", String.valueOf(timestamp))
.header("N", noncestr)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 200 || status >= 300) {
log.warn("hxr buyer order list HTTP {} page={} bodyPrefix={}",
status, safePage, abbreviate(response.body(), 400));
return Optional.empty();
}
String bodyText = response.body();
if (bodyText == null || bodyText.isBlank()) {
log.warn("hxr buyer order list empty body page={}", safePage);
return Optional.empty();
}
JsonNode root = JSON.readTree(bodyText);
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));
return Optional.empty();
}
JsonNode dataNode = root.get("data");
if (isErrorPayload(dataNode)) {
log.warn("hxr buyer order list 返回异常 data={} page={} msg={}", dataNode, safePage, msg);
return Optional.empty();
}
List<HxrOrderRow> rows = parseOrderRows(dataNode);
int count = root.path("count").asInt(rows.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;
}
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<>();
}
private static String randomNoncestr() {
String base36 = Long.toUnsignedString(Math.abs(RANDOM.nextLong()), 36);
if (base36.length() >= 5) {
return base36.substring(base36.length() - 5);
}
StringBuilder sb = new StringBuilder(base36);
while (sb.length() < 5) {
sb.append(Integer.toString(RANDOM.nextInt(36), 36));
}
return sb.toString();
}
private static boolean isErrorPayload(JsonNode dataNode) {
if (dataNode == null || dataNode.isNull()) {
return false;
}
if (!dataNode.isArray() || dataNode.isEmpty()) {
return false;
}
return dataNode.get(0).isTextual() || dataNode.get(0).isNumber();
}
private static String abbreviate(String s, int maxLen) {
if (s == null) {
return "";
}
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "...";
}
public record BuyerOrderPageResult(
List<HxrOrderRow> rows,
int count,
String allMoney,
boolean hasMore,
int lastPage,
int page) {
}
}

View File

@@ -1,6 +1,7 @@
package com.rj.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.rj.dto.LbBuyerShoppingPullRequest;
import com.rj.entity.LbBuyerShopping;
import java.math.BigDecimal;
@@ -35,4 +36,9 @@ public interface ILbBuyerShoppingService extends IService<LbBuyerShopping> {
String buyTimeEnd,
String confirmTimeStart,
String confirmTimeEnd);
/**
* 按手机号模拟第三方登录获取 token分页拉取买方订单列表并解析入库。
*/
Map<String, Object> pullFromThirdParty(LbBuyerShoppingPullRequest request);
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest;
import com.rj.dto.hxr.HxrAdminOrderApiContext;
import com.rj.dto.hxr.HxrAdminUserApiContext;
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
import com.rj.dto.hxr.HxrFansApiContext;
import com.rj.dto.hxr.HxrGoodsApiContext;
import com.rj.dto.hxr.HxrUserLoginApiContext;
@@ -57,6 +58,11 @@ public interface ILbThirdIntegrationConfigService extends IService<LbThirdIntegr
*/
Optional<HxrAdminOrderApiContext> resolveAdminOrderApiContext(String tenantId);
/**
* 按租户 id 解析后台买方订单列表 API 运行时配置buyer_order_list_path、buyer_order_list_limit
*/
Optional<HxrAdminOrderApiContext> resolveAdminBuyerOrderApiContext(String tenantId);
/**
* 按租户 id 解析用户登录 API 运行时配置login_api_path、Origin、Referer、appStr
*/
@@ -67,4 +73,10 @@ public interface ILbThirdIntegrationConfigService extends IService<LbThirdIntegr
* {@code token} 为登录用户 token必填。
*/
Optional<HxrFansApiContext> resolveFansApiContext(String tenantId, String token);
/**
* 按租户 id 解析买方订单列表 API 运行时配置buyer_order_list_path、buyer_order_list_limit、Origin、Referer、appStr
* {@code token} 为登录用户 token必填。
*/
Optional<HxrBuyerOrderApiContext> resolveBuyerOrderApiContext(String tenantId, String token);
}

View File

@@ -3,18 +3,31 @@ package com.rj.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;
import com.rj.service.HxrAdminBuyerOrderSelectService;
import com.rj.service.HxrAdminUserLoginService;
import com.rj.service.ILbBuyerShoppingService;
import com.rj.service.ILbThirdIntegrationConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
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;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 买方购物表 lb_buyer_shopping 服务实现
@@ -25,6 +38,17 @@ public class LbBuyerShoppingServiceImpl
extends ServiceImpl<LbBuyerShoppingMapper, LbBuyerShopping>
implements ILbBuyerShoppingService {
private static final String DEFAULT_SIMULATE_LOGIN_PASSWORD = "123456";
@Autowired
private ILbThirdIntegrationConfigService lbThirdIntegrationConfigService;
@Autowired
private HxrAdminUserLoginService hxrAdminUserLoginService;
@Autowired
private HxrAdminBuyerOrderSelectService hxrAdminBuyerOrderSelectService;
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -261,6 +285,311 @@ public class LbBuyerShoppingServiceImpl
}
}
@Override
public Map<String, Object> pullFromThirdParty(LbBuyerShoppingPullRequest request) {
Map<String, Object> result = new HashMap<>();
try {
if (request == null) {
result.put("success", false);
result.put("message", "请求体不能为空");
return result;
}
if (request.getTenantId() == null || request.getTenantId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "tenantId不能为空");
return result;
}
if (request.getMobiles() == null || request.getMobiles().isEmpty()) {
result.put("success", false);
result.put("message", "手机号列表不能为空");
return result;
}
String tenantId = request.getTenantId().trim();
Optional<HxrUserLoginApiContext> loginCtxOpt =
lbThirdIntegrationConfigService.resolveUserLoginApiContext(tenantId);
if (loginCtxOpt.isEmpty()) {
result.put("success", false);
result.put("message",
"未找到该租户的第三方集成配置或配置未启用、URL 不完整(请检查 lb_third_integration_config");
return result;
}
HxrUserLoginApiContext loginCtx = loginCtxOpt.get();
List<Map<String, Object>> items = new ArrayList<>();
int successCount = 0;
int failCount = 0;
int totalSaved = 0;
int totalFetched = 0;
String requestPassword = request.getPassword() != null ? request.getPassword().trim() : "";
for (String rawMobile : request.getMobiles()) {
Map<String, Object> item = new LinkedHashMap<>();
if (rawMobile == null || rawMobile.trim().isEmpty()) {
item.put("mobile", rawMobile);
item.put("loginSuccess", false);
item.put("orderFetchSuccess", false);
item.put("apiCode", -1);
item.put("apiMsg", "手机号为空");
items.add(item);
failCount++;
continue;
}
String mobile = rawMobile.trim();
item.put("mobile", mobile);
String password = !requestPassword.isEmpty()
? requestPassword
: DEFAULT_SIMULATE_LOGIN_PASSWORD;
try {
HxrAdminUserLoginService.LoginApiResult loginResult =
hxrAdminUserLoginService.login(mobile, password, loginCtx);
item.put("httpStatus", loginResult.httpStatus());
item.put("apiCode", loginResult.apiCode());
item.put("apiMsg", loginResult.apiMsg());
item.put("loginSuccess", loginResult.success());
item.put("parsed", loginResult.parsed());
if (!loginResult.success()) {
item.put("orderFetchSuccess", false);
failCount++;
items.add(item);
continue;
}
String token = HxrAdminUserLoginService.extractToken(loginResult.parsed());
if (token == null || token.isBlank()) {
item.put("orderFetchSuccess", false);
item.put("orderFetchMsg", "响应中无 data.userinfo.token");
failCount++;
items.add(item);
continue;
}
Optional<HxrBuyerOrderApiContext> orderCtxOpt =
lbThirdIntegrationConfigService.resolveBuyerOrderApiContext(tenantId, token);
if (orderCtxOpt.isEmpty()) {
item.put("orderFetchSuccess", false);
item.put("orderFetchMsg",
"未找到买方订单 API 配置或配置未启用、URL/凭证不完整(请检查 lb_third_integration_config");
failCount++;
items.add(item);
continue;
}
HxrBuyerOrderApiContext orderCtx = orderCtxOpt.get();
item.put("buyerOrderListUrl", orderCtx.buyerOrderListBaseUrl());
Map<String, Object> fetchResult = fetchAndSaveBuyerOrders(orderCtx, mobile);
item.putAll(fetchResult);
if (Boolean.TRUE.equals(fetchResult.get("orderFetchSuccess"))) {
successCount++;
totalSaved += toInt(fetchResult.get("ordersSaved"));
totalFetched += toInt(fetchResult.get("ordersFetched"));
} else {
failCount++;
}
} catch (Exception e) {
log.warn("买方购物第三方拉取异常 mobile={} tenantId={}", mobile, tenantId, e);
item.put("loginSuccess", false);
item.put("orderFetchSuccess", false);
item.put("apiCode", -1);
item.put("apiMsg", "请求异常:" + e.getMessage());
failCount++;
}
items.add(item);
}
result.put("success", true);
result.put("message", "买方购物列表拉取完成");
result.put("data", items);
result.put("successCount", successCount);
result.put("failCount", failCount);
result.put("ordersSaved", totalSaved);
result.put("ordersFetched", totalFetched);
result.put("loginApiUrl", loginCtx.loginApiUrl());
return result;
} catch (Exception e) {
log.error("买方购物第三方拉取异常", e);
result.put("success", false);
result.put("message", "拉取异常:" + e.getMessage());
return result;
}
}
private Map<String, Object> fetchAndSaveBuyerOrders(HxrBuyerOrderApiContext ctx, String buyerMobile) {
Map<String, Object> result = new LinkedHashMap<>();
int page = 1;
int totalSaved = 0;
int totalFetched = 0;
Integer remoteCount = null;
String allMoney = null;
HxrAdminBuyerOrderSelectService.BuyerOrderPageResult firstPage = null;
try {
while (true) {
Optional<HxrAdminBuyerOrderSelectService.BuyerOrderPageResult> opt =
hxrAdminBuyerOrderSelectService.fetchBuyerOrderPage(page, ctx);
if (opt.isEmpty()) {
if (page == 1) {
result.put("orderFetchSuccess", false);
result.put("orderFetchMsg",
"未拉取到买方订单(请检查 lb_third_integration_config 中 token、appStr、URL 或网络)");
result.put("ordersSaved", 0);
result.put("ordersFetched", 0);
return result;
}
result.put("orderFetchSuccess", false);
result.put("orderFetchMsg", "" + page + " 页拉取失败,已成功保存前 "
+ (page - 1) + " 页共 " + totalSaved + " 条订单");
result.put("ordersSaved", totalSaved);
result.put("ordersFetched", totalFetched);
if (remoteCount != null) {
result.put("remoteCount", remoteCount);
}
if (allMoney != null) {
result.put("allMoney", allMoney);
}
result.put("orderPages", page - 1);
return result;
}
HxrAdminBuyerOrderSelectService.BuyerOrderPageResult pageResult = opt.get();
if (firstPage == null) {
firstPage = pageResult;
remoteCount = pageResult.count();
allMoney = pageResult.allMoney();
}
List<HxrOrderRow> rows = pageResult.rows();
if (rows == null || rows.isEmpty()) {
break;
}
totalFetched += rows.size();
for (HxrOrderRow row : rows) {
LbBuyerShopping entity = toLbBuyerShopping(row, buyerMobile);
if (entity.getId() == null) {
continue;
}
applyDefaults(entity);
if (entity.getCreatedAt() == null) {
entity.setCreatedAt(LocalDateTime.now());
}
try {
LbBuyerShopping existing = this.getById(entity.getId());
boolean ok = existing != null ? this.updateById(entity) : this.save(entity);
if (ok) {
totalSaved++;
}
} catch (DuplicateKeyException e) {
if (this.updateById(entity)) {
totalSaved++;
}
}
}
int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : 90;
boolean hasMore = pageResult.hasMore();
int lastPage = pageResult.lastPage();
if (pageResult.rows().size() < pageLimit) {
break;
}
if (!hasMore || page >= lastPage) {
break;
}
page++;
}
result.put("orderFetchSuccess", true);
result.put("orderFetchMsg", totalSaved == 0 ? "接口成功,无订单数据" : "买方订单拉取完成");
result.put("ordersSaved", totalSaved);
result.put("ordersFetched", totalFetched);
result.put("remoteCount", remoteCount != null ? remoteCount : totalFetched);
if (allMoney != null && !allMoney.isBlank()) {
result.put("allMoney", allMoney);
}
result.put("lastPage", firstPage != null ? firstPage.lastPage() : page);
result.put("orderPages", page);
return result;
} catch (Exception e) {
log.warn("拉取买方订单异常 buyerMobile={}", buyerMobile, e);
result.put("orderFetchSuccess", false);
result.put("orderFetchMsg", "拉取买方订单异常:" + e.getMessage());
result.put("ordersSaved", totalSaved);
result.put("ordersFetched", totalFetched);
if (remoteCount != null) {
result.put("remoteCount", remoteCount);
}
result.put("orderPages", page > 1 ? page - 1 : 0);
return result;
}
}
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();
}
if (value == null) {
return 0;
}
try {
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
return 0;
}
}
private void applyDefaults(LbBuyerShopping entity) {
if (entity.getTotalMoney() == null) {
entity.setTotalMoney(BigDecimal.ZERO);

View File

@@ -8,6 +8,7 @@ import com.rj.common.LbThirdIntegrationConstants;
import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest;
import com.rj.dto.hxr.HxrAdminOrderApiContext;
import com.rj.dto.hxr.HxrAdminUserApiContext;
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
import com.rj.dto.hxr.HxrFansApiContext;
import com.rj.dto.hxr.HxrGoodsApiContext;
import com.rj.dto.hxr.HxrUserLoginApiContext;
@@ -398,6 +399,37 @@ public class LbThirdIntegrationConfigServiceImpl
}
}
@Override
public Optional<HxrBuyerOrderApiContext> resolveBuyerOrderApiContext(String tenantId, String token) {
if (!StringUtils.hasText(tenantId) || !StringUtils.hasText(token)) {
return Optional.empty();
}
LbThirdIntegrationConfig config = getByTenantIdOrNull(tenantId.trim());
if (config == null) {
return Optional.empty();
}
if (config.getEnabled() == null || config.getEnabled() != 1) {
return Optional.empty();
}
String appStr = config.getGoodsApiAppStr();
if (!StringUtils.hasText(appStr)) {
return Optional.empty();
}
try {
return Optional.of(new HxrBuyerOrderApiContext(
LbThirdIntegrationConfigUtil.resolveBuyerOrderListBaseUrl(config),
LbThirdIntegrationConfigUtil.resolveBuyerOrderListLimit(config),
LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config),
LbThirdIntegrationConfigUtil.resolveOrderReferer(config),
token.trim(),
appStr.trim()));
} catch (IllegalStateException e) {
return Optional.empty();
}
}
@Override
public Optional<HxrFansApiContext> resolveFansApiContext(String tenantId, String token) {
if (!StringUtils.hasText(tenantId) || !StringUtils.hasText(token)) {
@@ -431,6 +463,15 @@ public class LbThirdIntegrationConfigServiceImpl
@Override
public Optional<HxrAdminOrderApiContext> resolveAdminOrderApiContext(String tenantId) {
return resolveAdminOrderApiContext(tenantId, false);
}
@Override
public Optional<HxrAdminOrderApiContext> resolveAdminBuyerOrderApiContext(String tenantId) {
return resolveAdminOrderApiContext(tenantId, true);
}
private Optional<HxrAdminOrderApiContext> resolveAdminOrderApiContext(String tenantId, boolean buyerOrderList) {
if (!StringUtils.hasText(tenantId)) {
return Optional.empty();
}
@@ -448,9 +489,15 @@ public class LbThirdIntegrationConfigServiceImpl
}
try {
String baseUrl = buyerOrderList
? LbThirdIntegrationConfigUtil.resolveBuyerOrderListBaseUrl(config)
: LbThirdIntegrationConfigUtil.resolveOrderSelectBaseUrl(config);
int pageLimit = buyerOrderList
? LbThirdIntegrationConfigUtil.resolveBuyerOrderListLimit(config)
: LbThirdIntegrationConfigUtil.resolveOrderPageLimit(config);
return Optional.of(new HxrAdminOrderApiContext(
LbThirdIntegrationConfigUtil.resolveOrderSelectBaseUrl(config),
LbThirdIntegrationConfigUtil.resolveOrderPageLimit(config),
baseUrl,
pageLimit,
cookieHeader.trim(),
LbThirdIntegrationConfigUtil.resolveOrderReferer(config)));
} catch (IllegalStateException e) {
@@ -581,6 +628,9 @@ public class LbThirdIntegrationConfigServiceImpl
if (!StringUtils.hasText(entity.getBuyApiPath())) {
entity.setBuyApiPath(LbThirdIntegrationConstants.DEFAULT_BUY_API_PATH);
}
if (!StringUtils.hasText(entity.getBuyerOrderListPath())) {
entity.setBuyerOrderListPath(LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_PATH);
}
if (!StringUtils.hasText(entity.getFansApiPath())) {
entity.setFansApiPath(LbThirdIntegrationConstants.DEFAULT_FANS_API_PATH);
}
@@ -590,6 +640,9 @@ public class LbThirdIntegrationConfigServiceImpl
if (entity.getOrderPageLimit() == null) {
entity.setOrderPageLimit(LbThirdIntegrationConstants.DEFAULT_ORDER_PAGE_LIMIT);
}
if (entity.getBuyerOrderListLimit() == null) {
entity.setBuyerOrderListLimit(LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_LIMIT);
}
if (entity.getUserPageLimit() == null) {
entity.setUserPageLimit(LbThirdIntegrationConstants.DEFAULT_USER_PAGE_LIMIT);
}

View File

@@ -165,6 +165,27 @@ public final class LbThirdIntegrationConfigUtil {
return limit;
}
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;
}
public static int resolveBuyerOrderListLimit(LbThirdIntegrationConfig config) {
Integer limit = config.getBuyerOrderListLimit();
if (limit == null || limit < 1) {
return LbThirdIntegrationConstants.DEFAULT_BUYER_ORDER_LIST_LIMIT;
}
return limit;
}
/**
* 组装 Cookie 请求头:优先完整 cookie否则 {@code PHPSID=} + phpsid。
*/

View File

@@ -16,10 +16,12 @@ CREATE TABLE `lb_third_integration_config` (
`user_update_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/user/update' COMMENT '用户更新 API 路径',
`goods_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/order/goods' COMMENT '货品列表 API 路径',
`buy_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/order/buy' COMMENT '抢购 API 路径',
`buyer_order_list_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/order/buy/select' COMMENT '买方订单列表 API 路径',
`fans_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/share/select' COMMENT '粉丝列表 API 路径',
`login_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/user/login' COMMENT '后台登录 API 路径',
`order_page_limit` INT NOT NULL DEFAULT 90 COMMENT '订单分页 limit',
`buyer_order_list_limit` INT NOT NULL DEFAULT 90 COMMENT '买方订单列表分页 limit',
`user_page_limit` INT NOT NULL DEFAULT 90 COMMENT '用户分页 limit',
`goods_page_limit` INT NOT NULL DEFAULT 20 COMMENT '货品分页 limit',
`fans_page_limit` INT NOT NULL DEFAULT 10 COMMENT '粉丝分页 limit',

View File

@@ -0,0 +1,8 @@
-- 升级脚本lb_third_integration_config 增加买方订单列表 API 配置列
SET NAMES utf8mb4;
ALTER TABLE `lb_third_integration_config`
ADD COLUMN `buyer_order_list_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/order/buy/select'
COMMENT '买方订单列表 API 路径' AFTER `buy_api_path`,
ADD COLUMN `buyer_order_list_limit` INT NOT NULL DEFAULT 90
COMMENT '买方订单列表分页 limit' AFTER `order_page_limit`;