拉取买家订单列表
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
23
src/main/java/com/rj/dto/LbBuyerShoppingPullRequest.java
Normal file
23
src/main/java/com/rj/dto/LbBuyerShoppingPullRequest.java
Normal 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;
|
||||
}
|
||||
13
src/main/java/com/rj/dto/hxr/HxrBuyerOrderApiContext.java
Normal file
13
src/main/java/com/rj/dto/hxr/HxrBuyerOrderApiContext.java
Normal 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) {
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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。
|
||||
*/
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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`;
|
||||
Reference in New Issue
Block a user