waynboot-mall: a Spring Boot 3 mall backend with inventory and payment reconciliation
这是一套全部开源的微商城项目,包含一个运营后台、H5商城前台和服务端接口。 实现了商城所需的首页展示、商品分类、商品详情、sku详情、商品搜索、购物车、结算下单、线上支付,商品评论等一系列功能。 技术上基于最新得Springboot3.0、jdk17,整合了MySql、Redis、RabbitMQ、ElasticSearch等常用中间件,对于初学者而言根据readme中的本地开发指南就能成功启动项目。还提供 docker-compose 一键部署脚本,只需要十多分钟就能启动商城前后台所有服务。
At a glance
- What is it?
- waynboot-mall is a multi-module Maven project on Spring Boot 3.1.4 and Java 17 that splits a shop into mobile API, admin API, and per-domain modules, with a local message table for reliable MQ delivery, a two-state inventory model, and scheduled reconciliation jobs for stock, inventory, and payments.
- Who is it for?
- Adopt waynboot-mall if you want a runnable reference for a Chinese e-commerce backend, especially the inventory consistency, local message table, and reconciliation pieces, which are the parts most worth reading. Do not adopt it as a starting skeleton for a new product without reading the module boundaries first.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 106 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The module split is the design
waynboot-mall is a Maven multi-module mall backend on Spring Boot 3 and Java 17, covering the mobile shop, the admin console, message consumption, scheduled tasks, Redis, and Elasticsearch. What it is not is a demo CRUD application. The README states that the project currently focuses on splitting things around products, orders, inventory, payment, MQ, local message compensation, and reconciliation governance, with the goal of making the trade chain easier to maintain and extend under high concurrency.
The module list is the clearest statement of the architecture. There are two entry modules, waynboot-admin-api for the admin console and waynboot-mobile-api for the H5 and mobile clients. There is waynboot-common for shared configuration, aspects, strategy interfaces, common models, and infrastructure. There is waynboot-domain-api, which holds only cross-domain contracts, meaning entities, mapper and service interfaces, VOs, and enums.
Then there is one module per domain: waynboot-domain-trade for orders, payment orchestration, the state machine, local messages, and reconciliation; waynboot-domain-inventory for stock freezing, stock flow records, the Redis stock snapshot, and inventory reconciliation; waynboot-domain-goods for products, SKUs, categories, search, and Elasticsearch synchronisation; waynboot-domain-cart for cart reads and writes and selected item aggregation; and waynboot-domain-promotion for coupons, marketing placements, and the Diamond strategy implementation.
Around those sit waynboot-payment-channel, which adapts payment and refund operations for WeChat, Alipay, and epay; a waynboot-data directory with the Redis and Elasticsearch access layers; a waynboot-message directory with the RabbitMQ core and the consumers; waynboot-util for shared utilities, exceptions, enums, and constants; db-init for the database initialisation and governance SQL; and directories holding the Redis, RabbitMQ, Elasticsearch, and nginx configuration, along with the docker-compose orchestration files.
The rule that governs the boundaries is stated directly: the entry modules do not carry core business logic. The admin API and mobile API are adapters, and trade, inventory, goods, cart, and promotion implementations are collected in the domain modules. The enforcement mechanism is the development convention that a controller only does parameter validation, permission checks, logging, and VO response wrapping, with no core business logic in a controller at all.
Five layers, and where consistency is actually handled
The README describes the layering in five bands, and this is the part of the design that distinguishes the project from a typical shop demo.
The controller entry layer does parameter validation, permission checks, logging, and VO response wrapping. The service and support orchestration layer holds the order responsibility chain, payment callbacks, order state transitions, and compensation entry points. The domain capability layer holds products, cart, orders, inventory, coupons, payment flows, and order state logs. The asynchronous consistency layer holds the local message table, the MQ relay, the consumer template, and compensation retry. And the governance layer holds Redis peak shaving, inventory reconciliation, payment reconciliation, the compensation back office, and the Spring Scheduled tasks.
The asynchronous consistency layer and the governance layer are the interesting additions. Most sample projects stop at the first three bands, which is why they look clean in a demo and fall over in production, because the gap between the database transaction and the message broker is exactly where the lost orders live.
The local message table is the answer to that gap, and the README describes the problem it solves as a business transaction committing successfully while the MQ delivery fails. The flow is: write the business data and the local message inside the transaction, have a relay scan messages in the INIT state, deliver to RabbitMQ or run a local handler, mark as SENT on success, retry with exponential backoff on failure, and move to FAILED once the maximum attempts are reached, after which a back office entry or a scheduled compensation handles it.
The support around it is explicit too. A LocalMessageService handles creation, lookup, success marking, and failure retry, a LocalMessageFailureClassifier classifies why a delivery failed, a LocalMessageCompensationLogService records failures, dead letters, and manual re-queues, and a TradeOpsController exposes interfaces for querying failed messages, viewing metrics, and manually re-queueing.
The dashboard statistics sit in the same family. A DashboardController serves the admin home page under the shop/dashboard path, covering core metrics, sales trends, payment channels, best selling products, inventory warnings, member trends, and recent activity, with the calculation logic centralised in a DashboardService and the front end doing display only, no hardcoded data.
The inventory model and its two states
The inventory design is the piece most likely to be copied from this project, because the two-state model is simple and the rules are written down.
The two fields are number for sellable stock and lockedStock for frozen stock, and the transitions are equally explicit. Placing an order subtracts n from number and adds n to lockedStock. Confirming payment subtracts n from lockedStock. A timeout cancellation adds n back to number and subtracts n from lockedStock. A refund restock adds n back to number.
The design principles behind those numbers matter more than the numbers. Redis Lua scripts only do entry peak shaving and act as the concurrency gate for hot SKUs. MySQL conditional updates are responsible for eventual consistency and for preventing overselling. The inventory flow table uses a unique flowKey so that a stock side effect is idempotent under local message retries. An InventoryReconciliationService can derive frozen stock from the flow log and identify and repair lockedStock discrepancies. And hot SKUs can use a Redis stock snapshot with bucketed keys to reduce contention on a single key.
The split of responsibility between the two stores is the design. Redis is fast but approximate, so it is used to shed load and to gate the entry. MySQL is the source of truth, and the conditional update there is what makes the final state correct. The Lua script and the conditional update are not redundant, they are two different guarantees.
The reconciliation service then closes the loop, because even with those rules, a crash between the two stores leaves a discrepancy. Deriving the expected frozen stock from the flow log and repairing it is what stops a drift from becoming permanent.
The order chain, and the seckill path that reuses it
The order flow is described as a chain of responsibility and domain support services. The steps are: the user places an order, parameters and the cart are validated, Redis Lua reserves stock, MySQL conditionally freezes stock, the order main table and the order detail rows are written, the local message table is written, and the post-actions run asynchronously through MQ or a local handler.
Order state is centralised in an OrderStateTransitionSupport, and every entry point, whether payment, cancellation, shipping, refund, or order confirmation, has to validate the transition, and updates are done with a condition on the current database state so concurrent writes cannot overwrite each other. That last detail is the one that matters under load, because a read-then-write without a condition is a lost update, and an order that goes from pending to paid while a cancellation is in flight is exactly the race that produces a paid and cancelled order at once.
The seckill, or flash sale, feature is where the interesting engineering shows. The design is a funnel filter, atomic Redis decrement, reliable local message delivery, asynchronous order creation over MQ, and a 60 second delayed close. Crucially, the orders still land in the same shop_order and shop_order_goods tables as ordinary orders, and the seckill activity is related to orders through shop_order_activity_relation at order item granularity, which avoids maintaining a separate seckill order table.
The flow runs through publishing the activity and warming the stock, having the mobile client claim a short lived seckill token, applying both SKU level and user level rate limits, validating the token, the duplicate purchase check, and the activity stock in a Redis Lua script, writing the local message table and reliably delivering the seckill order to MQ, creating the standard order asynchronously in the consumer, sending a 60 second delay message, closing the unpaid order automatically and releasing both the ordinary and the activity frozen stock, and confirming both on payment success.
The 60 second delayed close is the piece that makes a flash sale survivable, because a seckill order that is never paid has to release its stock reservation or the activity sells out to abandoned carts.
Payment callbacks, idempotency, and reconciliation
Payment callbacks are supported for WeChat Pay, Alipay, and the epay channel, and the processing rules are strict in a way that reflects experience with real payment providers.
The signature must be verified before any business processing happens. The order state update must carry a condition on the current state. The third party payment flow is protected by a unique constraint on shop_payment_flow.flow_key. A duplicate callback returns success idempotently. And the post-payment actions, such as confirming stock and updating virtual sales figures, are handled asynchronously through the local message.
That last point is where most payment integration bugs come from. A callback handler that confirms inventory inline is a callback handler that can time out, and a payment provider that retries a callback that timed out is a payment provider that will confirm the same order twice.
Reconciliation is presented as three separate comparisons. The payment flow against the order state and the order amount. The channel bill against the internal payment flow. And the refund flow against the order refund amount. A Spring Scheduled job provides the end of day reconciliation entry point.
The three-way split is deliberate. The first comparison catches a missing or wrong internal update, the second catches a transaction the provider has that you do not, and the third catches refunds that were never applied. Any one of them can be the only signal that something went wrong, which is why the project treats reconciliation as a first-class feature rather than an afterthought.
Running it locally, and the middleware it expects
The environment requirements are listed plainly: JDK 17, Maven 3.8 or newer, MySQL 8 or newer, Redis 6 or newer, RabbitMQ 3 or newer, and Elasticsearch 7.
The build and test commands are the standard Maven ones:
mvn clean package
mvn testAnd to use a local Maven repository directory instead of the default one:
mvn "-Dmaven.repo.local=.m2/repository" testThe services are started one module at a time, which is the practical part:
mvn -pl waynboot-admin-api spring-boot:run
mvn -pl waynboot-mobile-api spring-boot:run
mvn -pl waynboot-message/waynboot-message-consumer spring-boot:runOr you can start the corresponding Application classes from an IDE. The middleware comes up with Docker Compose:
docker-compose up -dAnd if you need Elasticsearch, RabbitMQ, Redis, and MySQL at the same time, the repository ships a specific compose file for that combination, docker-compose-es7-rabbitmq-redis-mysql.yml, alongside the base one.
The compose file itself reveals how the three applications are packaged, which is worth noting because it is not the usual jar-in-image approach. The mobile, admin, and message services all run the ibm-semeru-runtimes:open-17-jdk image with the built jar bind-mounted in from a jars/ directory rather than baked into the image, with the JVM options set on the command line including the timezone, the file encoding, and the active Spring profile, and with network_mode host so the services share the host network.
The database scripts live under db-init, with separate files for the base shop tables and seed data, the frozen stock fields and the stock flow table, the local message and compensation log tables, the order state log, the payment flow, channel bill, and refund flow tables, and the seckill activity, activity SKU, order activity relation, and admin menu permissions. The README notes that the SQL files are kept as the entry point for schema evolution and that the execution order and idempotency have to be confirmed against the target environment before committing.
The scheduled governance jobs and their knobs
The scheduled tasks run inside the waynboot-admin-api process using Spring's @Scheduled, sharing the same dependencies as the admin TradeOpsController, and the implementation is a single class, TradeGovernanceScheduledTask. The three jobs are refreshStockSnapshot, which refreshes the Redis stock snapshot with bucketing for hot SKUs and runs every 5 minutes by default; reconcileInventory, which reconciles the stock flow log and can auto-repair lockedStock under control and runs hourly on the hour by default; and reconcilePaymentDaily, which does end of day payment reconciliation at 02:00 daily by default.
The schedules, batch sizes, and whether automatic repair is enabled are all configuration, under a wayn.schedule.trade.* node in application.yml:
wayn:
schedule:
trade:
stock-snapshot:
cron: 0 */5 * * * *
limit: 500
bucket-count: 1
inventory-reconcile:
cron: 0 0 * * * *
limit: 500
repair: true
payment-reconcile:
cron: 0 0 2 * * *Two things are worth pulling out of that configuration. The repair flag is on for inventory reconciliation and off for the others, which reflects the different risk profile of each. Auto-repairing a stock count that was derived from a flow log is safe to repeat, because the derivation is deterministic. Auto-repairing a payment comparison is not, because a mismatch there is more likely to be a genuine discrepancy that a human should look at.
The bucket-count setting of 1 is the bucketing disabled, and the limit of 500 is the batch size, both of which are the kind of knob you turn after watching the first run rather than before.
The development conventions are also documented. All source, XML, and Markdown is UTF-8. Controllers only validate parameters, check permissions, log, and wrap the response in a VO, with no core business logic. API responses should prefer an explicit VO or ResVO rather than a bare Map of strings to objects, and transaction, inventory, payment, compensation, and idempotency logic needs class, method, and key branch comments. A note covers the detail that pure unit tests touching MyBatis-Plus lambda wrappers have to initialise the entity TableInfo cache.
Editorial conclusion
Adopt waynboot-mall if you want a runnable reference for a Chinese e-commerce backend, especially the inventory consistency, local message table, and reconciliation pieces, which are the parts most worth reading. Do not adopt it as a starting skeleton for a new product without reading the module boundaries first. Verify first that you can start the middleware stack with Docker Compose, because the build will not run without MySQL, Redis, RabbitMQ, and Elasticsearch.
Frequently asked questions
What is waynboot-mall and what is it built on?
It is a Maven multi-module mall backend on Spring Boot 3.1.4 and Java 17, with MyBatis-Plus and MySQL for data access, Redis and Lettuce with Lua scripts for caching, Elasticsearch 7 for search, RabbitMQ with delayed messages and a local message table for messaging, Spring Scheduled for tasks, and JUnit 5 with Mockito for testing.
How does waynboot-mall keep inventory consistent?
With a two-state model of sellable number and frozen lockedStock, where a Redis Lua script only does entry peak shaving and gates hot SKUs, MySQL conditional updates provide the final consistency and prevent overselling, the inventory flow table uses a unique flowKey for idempotency under retries, and InventoryReconciliationService derives and repairs frozen stock from the flow log.
What is the local message table for?
It solves the case where the business transaction commits but the MQ delivery fails. A relay scans messages in the INIT state, delivers to RabbitMQ or runs a local handler, marks SENT on success, retries with exponential backoff, moves to FAILED past the maximum attempts, and a back office entry or scheduled compensation handles it.
Which payment channels does waynboot-mall support?
WeChat Pay, Alipay, and the epay channel, through a waynboot-payment-channel module that adapts payment and refund operations. Callbacks must be signature-verified before business processing, the order update must carry a condition on the current state, and the third party flow is protected by a unique constraint on shop_payment_flow.flow_key so duplicate callbacks succeed idempotently.
How do I run waynboot-mall locally?
You need JDK 17, Maven 3.8 or newer, MySQL 8, Redis 6, RabbitMQ 3, and Elasticsearch 7. Build with mvn clean package and test with mvn test, bring the middleware up with docker-compose up -d, and start the services with the spring-boot:run command for waynboot-admin-api, waynboot-mobile-api, or the message consumer module.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wayn111-waynboot-mall)