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
- Check why the product service returned false — usually stock exhausted (errors 16/17); restock or lower the order amount.
- Verify the product service is up on 127.0.0.1:8082 and the Seata server is started before driving orders.
- Ensure DefaultHttpExecutor (with the Seata propagation interceptor) is the executor used, so the XID travels and rollback is global.
- 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
- Ensure DefaultHttpExecutor (Seata XID propagation) is used for all cross-service HTTP calls in the transaction.
- Start the Seata server and participant services (8082/8083) before driving orders.
- Log the raw response body when parsing fails — 'false' often hides an HTML error page.
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.