Files
smartDriveEE/src/main/java/com/rj/service/HxrAdminBuyService.java
2026-05-28 19:16:08 +08:00

150 lines
5.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.rj.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.rj.config.HxrAdminProperties;
import com.rj.util.HxrGoodsSignUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 调用 hxrd {@code POST /api/order/buy} 抢购(请求头 {@code token} + {@code S/T/N} 鉴权)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class HxrAdminBuyService {
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 ObjectMapper JSON = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
private final HxrAdminProperties properties;
/**
* @return {@code apiCode} 为 -1 表示 HTTP 或解析失败
*/
public BuyApiResult buy(long goodsId, long sellerId, String token) throws Exception {
String resolvedToken = resolveToken(token);
if (resolvedToken == null) {
return BuyApiResult.failure(-1, "未提供 token 且未配置 hxr.admin.goods-api-token");
}
String appStr = properties.getGoodsApiAppStr();
if (appStr == null || appStr.isBlank()) {
return BuyApiResult.failure(-1, "未配置 hxr.admin.goods-api-app-str");
}
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = randomNoncestr();
Map<String, Object> signParams = new LinkedHashMap<>();
signParams.put("id", goodsId);
signParams.put("seller_id", sellerId);
signParams.put("timestamp", timestamp);
signParams.put("noncestr", noncestr);
String sign = HxrGoodsSignUtil.computeSign(signParams, appStr.trim());
Map<String, Object> body = new LinkedHashMap<>();
body.put("id", goodsId);
body.put("seller_id", sellerId);
String bodyJson = JSON.writeValueAsString(body);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(properties.getBuyApiUrl()))
.timeout(Duration.ofSeconds(120))
.header("Accept", "application/json,*/*")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.header("Content-Type", "application/json;charset=UTF-8")
.header("Origin", properties.getGoodsApiOrigin())
.header("Referer", properties.getGoodsApiReferer())
.header("User-Agent", USER_AGENT)
.header("token", resolvedToken)
.header("S", sign)
.header("T", String.valueOf(timestamp))
.header("N", noncestr)
.POST(HttpRequest.BodyPublishers.ofString(bodyJson, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
String bodyText = response.body();
if (status < 200 || status >= 300) {
log.warn("hxr /api/order/buy HTTP {} id={} sellerId={} bodyPrefix={}",
status, goodsId, sellerId, abbreviate(bodyText, 400));
return BuyApiResult.failure(-1, "HTTP " + status);
}
if (bodyText == null || bodyText.isBlank()) {
log.warn("hxr /api/order/buy empty body id={} sellerId={}", goodsId, sellerId);
return BuyApiResult.failure(-1, "响应为空");
}
JsonNode root = JSON.readTree(bodyText);
int code = root.path("code").asInt(-1);
String msg = root.path("msg").asText("");
if (code != 0) {
log.warn("hxr /api/order/buy api code={} msg={} id={} sellerId={}",
code, msg, goodsId, sellerId);
}
return new BuyApiResult(code == 0, code, msg);
}
/** 优先使用入参 token否则回退到配置 {@code goods-api-token}。 */
private String resolveToken(String token) {
if (token != null && !token.isBlank()) {
return token.trim();
}
String configured = properties.getGoodsApiToken();
if (configured != null && !configured.isBlank()) {
return configured.trim();
}
return null;
}
public record BuyApiResult(boolean success, int apiCode, String apiMsg) {
public static BuyApiResult failure(int code, String msg) {
return new BuyApiResult(false, code, msg);
}
}
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 String abbreviate(String s, int maxLen) {
if (s == null) {
return "";
}
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "...";
}
}