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

扣除余额失败

Error message

扣除余额失败

What it means

Mirror of error 18: the order service calls the account service at http://127.0.0.1:8083/account/reduce-balance via Seata's DefaultHttpExecutor and throws RuntimeException('扣除余额失败') when the response body isn't true. The XID-propagating HTTP call makes the deduction a branch of the global transaction; the throw rolls the whole purchase back (order insert + stock deduction).

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:72

                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. Top up the test user's balance (or lower the order price) so reduce-balance succeeds.
  2. Ensure the account service (8083) and Seata server are running before creating orders.
  3. Confirm both /product and /account services join the same XID (check RootContext.getXID() logs on each service) so rollback spans them.
  4. Include the remote body/status in the thrown message to speed up diagnosis.

Example fix

// before
if (!success) {
    throw new RuntimeException("扣除余额失败");
}

// after
if (!success) {
    throw new RuntimeException("扣除余额失败: userId=" + userId + ", price=" + price);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm sufficient balance before creating the order:
Integer balance = accountClient.getBalance(userId);
if (balance == null || balance < price) {
    throw new OrderRejectedException("INSUFFICIENT_BALANCE");
}

Try / catch

try {
    reduceBalance(userId, price);
} catch (RuntimeException e) {
    logger.warn("balance branch failed: {}", e.getMessage());
    throw new OrderFailedException("BALANCE_FAILED", e);
}

Prevention

When it happens

Trigger: Order creation when the account service returns false — i.e., its reduce-balance failed with insufficient balance (errors 14/15). Requires account service on 127.0.0.1:8083 and the Seata infrastructure running.

Common situations: End-to-end rollback demo: once a test user's balance is drained, every subsequent order fails here, and observers should confirm the previously deducted stock and created order get rolled back. Pitfalls: wrong port (8083) or service not started; account service returning an error page (non-'true' body); missing XID propagation so the account deduction commits independently.

Related errors


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