package com.rj.service; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.rj.config.HxrAdminProperties; import com.rj.dto.hxr.HxrOrderRow; import com.rj.dto.hxr.HxrOrderSelectResponse; import lombok.RequiredArgsConstructor; 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.time.Duration; import java.util.Optional; /** * 调用 hxrd 后台订单列表接口并解析 JSON。 */ @Slf4j @Service @RequiredArgsConstructor public class HxrAdminOrderSelectService { private static final ObjectMapper JSON = new ObjectMapper() .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); private final HxrAdminProperties properties; /** * 拉取订单列表;配置未就绪或 HTTP 非 2xx 时返回 empty。 *

使用 {@link HxrAdminProperties#getOrderSelectUrl()} 原样请求(与定时任务一致)。 */ public Optional fetchOrderSelect() throws Exception { return fetchOrderSelectInternal(properties.getOrderSelectUrl()); } /** * 拉取未支付订单,使用 {@link HxrAdminProperties#getOrderSelectUnPayUrl()}。 */ public Optional fetchUnpaidOrderSelect() throws Exception { return fetchOrderSelectInternal(properties.getOrderSelectUnPayUrl()); } /** * 按购买时间区间、转卖状态等条件拉取订单列表(在配置的 {@code orderSelectUrl} 上覆盖查询参数)。 * * @param buyTimeStart 购买时间起,对应 {@code buy_time[0]},可为 null 或空串表示不限制 * @param buyTimeEnd 购买时间止,对应 {@code buy_time[1]} * @param isResell 转卖筛选,对应 {@code is_resell};为 null 时不改写该参数 */ public Optional fetchOrderSelect( String buyTimeStart, String buyTimeEnd, Integer isResell) throws Exception { return fetchOrderSelect(buyTimeStart, buyTimeEnd, isResell, 1); } /** * 按购买时间区间、转卖状态及页码拉取订单列表。 * * @param page 页码,从 1 开始;与配置 URL 中的 {@code limit} 配合分页 */ public Optional fetchOrderSelect( String buyTimeStart, String buyTimeEnd, Integer isResell, int page) throws Exception { return fetchOrderSelectInternal(buildOrderSelectUrl(buyTimeStart, buyTimeEnd, isResell, page)); } /** * 按购买时间区间及页码拉取未支付订单({@code status=0},基于 {@link HxrAdminProperties#getOrderSelectUnPayUrl()})。 */ public Optional fetchUnpaidOrderSelect( String buyTimeStart, String buyTimeEnd, int page) throws Exception { return fetchOrderSelectInternal( buildStatusOrderSelectUrl(properties.getOrderSelectUnPayUrl(), buyTimeStart, buyTimeEnd, page)); } /** * 按购买时间区间及页码拉取已支付订单({@code status=1},基于 {@link HxrAdminProperties#getOrderSelectPaidUrl()})。 */ public Optional fetchPaidOrderSelect( String buyTimeStart, String buyTimeEnd, int page) throws Exception { return fetchOrderSelectInternal( buildStatusOrderSelectUrl(properties.getOrderSelectPaidUrl(), buyTimeStart, buyTimeEnd, page)); } private Optional fetchOrderSelectInternal(String requestUri) throws Exception { String cookieHeader = properties.resolveCookieHeader(); if (cookieHeader == null || cookieHeader.isBlank()) { log.warn("hxr.admin 未配置 cookie 或 phpsid,跳过拉取"); return Optional.empty(); } HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(35)) .followRedirects(HttpClient.Redirect.NORMAL) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(requestUri)) .timeout(Duration.ofSeconds(160)) .header("Accept", "application/json, text/javascript, */*; q=0.01") .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") .header("Cookie", cookieHeader) .header("Priority", "u=1, i") .header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/order/index") .header("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") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); int status = response.statusCode(); if (status < 200 || status >= 300) { log.warn("hxr admin order/select HTTP {} bodyPrefix={}", status, abbreviate(response.body(), 500)); return Optional.empty(); } HxrOrderSelectResponse body = JSON.readValue(response.body(), HxrOrderSelectResponse.class); if (body.code() != 0) { log.warn("hxr admin order/select api code={} msg={}", body.code(), body.msg()); return Optional.empty(); } return Optional.of(body); } /** * 在 {@link HxrAdminProperties#getOrderSelectUrl()} 上覆盖 {@code buy_time[0]}、{@code buy_time[1]}、{@code is_resell}。 */ private String buildOrderSelectUrl(String buyTimeStart, String buyTimeEnd, Integer isResell, int page) { UriComponentsBuilder b = UriComponentsBuilder.fromUriString(properties.getOrderSelectUrl()); b.replaceQueryParam("buy_time[0]", buyTimeStart == null ? "" : buyTimeStart); b.replaceQueryParam("buy_time[1]", buyTimeEnd == null ? "" : buyTimeEnd); if (isResell != null) { b.replaceQueryParam("is_resell", isResell); } b.replaceQueryParam("page", Math.max(1, page)); return b.build().encode().toUriString(); } private String buildStatusOrderSelectUrl(String baseUrl, String buyTimeStart, String buyTimeEnd, int page) { UriComponentsBuilder b = UriComponentsBuilder.fromUriString(baseUrl); b.replaceQueryParam("buy_time[0]", buyTimeStart == null ? "" : buyTimeStart); b.replaceQueryParam("buy_time[1]", buyTimeEnd == null ? "" : buyTimeEnd); b.replaceQueryParam("page", Math.max(1, page)); return b.build().encode().toUriString(); } /** * 拉取并逐条打日志(一行一条 JSON)。 */ public void fetchAndLogEachOrder() throws Exception { fetchAndLogEachOrder(fetchOrderSelect(), "未寄卖"); } /** * 拉取未支付订单({@code status=0})并逐条打日志。 */ public void fetchAndLogUnpaidOrders() throws Exception { fetchAndLogEachOrder(fetchUnpaidOrderSelect(), "未支付"); } private void fetchAndLogEachOrder( Optional opt, String label) throws Exception { if (opt.isEmpty()) { return; } HxrOrderSelectResponse body = opt.get(); log.info("{} order/select ok count={} allMoney={}", label, body.count(), body.allMoney()); // for (HxrOrderRow row : body.data()) { // log.info("hxr order row {}", JSON.writeValueAsString(row)); // } } private static String abbreviate(String s, int maxLen) { if (s == null) { return ""; } return s.length() <= maxLen ? s : s.substring(0, maxLen) + "..."; } }