yudaocode/SpringBoot-Labs · error · java.lang.RuntimeException

扣除库存失败

Error message

扣除库存失败

What it means

In the Seata AT + HttpClient demo, the order service remotely calls the product service via DefaultHttpExecutor.executePost('http://127.0.0.1:8082', '/product/reduce-stock', ...) and throws RuntimeException('扣除库存失败') when the response body does not parse to boolean true. The whole point of the lab: DefaultHttpExecutor propagates the Seata XID (RootContext.getXID()) in the request header so the HTTP call joins the same global transaction — and the thrown RuntimeException triggers rollback across services.

Source

Thrown at lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderServiceImpl.java:58

        OrderDO order = new OrderDO().setUserId(userId).setProductId(productId).setPayAmount(amount * price);
        orderDao.saveOrder(order);
        logger.info("[createOrder] 保存订单: {}", order.getId());

        // 返回订单编号
        return order.getId();
    }

    private void reduceStock(Long productId, Integer amount) throws IOException {
        // 参数拼接
        JSONObject params = new JSONObject().fluentPut("productId", String.valueOf(productId))
                .fluentPut("amount", String.valueOf(amount));
        // 执行调用
        HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8082", "/product/reduce-stock",
                params, HttpResponse.class);
        // 解析结果
        Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
        if (!success) {
            throw new RuntimeException("扣除库存失败");
        }
    }

    private void reduceBalance(Long userId, Integer price) throws IOException {
        // 参数拼接
        JSONObject params = new JSONObject().fluentPut("userId", String.valueOf(userId))
                .fluentPut("price", String.valueOf(price));
        // 执行调用
        HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8083", "/account/reduce-balance",
                params, HttpResponse.class);
        // 解析结果
        Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
        if (!success) {
            throw new RuntimeException("扣除余额失败");
        }
    }

}

View on GitHub (pinned to 6c12efaed0)

Solutions

  1. Check why the product service returned false — usually stock exhausted (errors 16/17); restock or lower the order amount.
  2. Verify the product service is up on 127.0.0.1:8082 and the Seata server is started before driving orders.
  3. Ensure DefaultHttpExecutor (with the Seata propagation interceptor) is the executor used, so the XID travels and rollback is global.
  4. Log the raw response body on failure — parsing an error page as Boolean silently becomes 'false' and masks the real cause.

Example fix

// before: bare boolean parse masks the real failure
Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
if (!success) throw new RuntimeException("扣除库存失败");

// after: keep the body for diagnostics
String body = EntityUtils.toString(response.getEntity());
if (!Boolean.parseBoolean(body)) {
    throw new RuntimeException("扣除库存失败: product-service said: " + body);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight check before placing the order:
// 1) product service reachable
// 2) stock sufficient (read-only query) — avoids the doomed remote write
Integer stock = productClient.getStock(productId);
if (stock == null || stock < amount) {
    throw new OrderRejectedException("INSUFFICIENT_STOCK");
}

Try / catch

// Wrap the remote participant call; keep cause chain for Seata diagnosis:
try {
    reduceStock(productId, amount);
} catch (RuntimeException e) {
    logger.warn("stock branch failed: {}", e.getMessage());
    throw new OrderFailedException("STOCK_FAILED", e); // triggers global rollback
}

Prevention

When it happens

Trigger: Order creation flow (POST order endpoint on the order service) where the product service returns false (its own reduce-stock failed, e.g. insufficient stock per errors 16/17). Requires product service on 127.0.0.1:8082, Seata server running, and DefaultHttpExecutor registered for the propagation filter.

Common situations: Multi-service rollback verification. Typical breakages: the product service is not listening on 8082 (ConnectException surfaces before this branch); boolean parsing of the body fails (HTML error page yields false); or XID propagation missing because a plain HttpClient was used instead of DefaultHttpExecutor, so the remote branch never joins the global transaction and rollback does not span services.

Related errors


AI-assisted analysis of yudaocode/SpringBoot-Labs@6c12efaed0 (2026-08-14). Data as JSON: /api/errors/3d4f88b063d13e82. Report an issue: GitHub.