How the Flash Sale and Promotion System Works in macrozheng/mall: A Technical Deep Dive

The flash sale and promotion system implements a three-tier hierarchy of Promotion → Session → Product Relation, enabling time-bound discounts through administrative CRUD APIs and real-time portal rendering via HomeServiceImpl#getHomeFlashPromotion().

The flash sale and promotion system in the macrozheng/mall repository provides a complete infrastructure for time-sensitive discount campaigns. Built on a MySQL-backed layered architecture, it separates administrative configuration from customer-facing presentation, allowing merchants to schedule precise inventory-limited flash sales across specific time windows.

Three-Tier Architecture Overview

The system organizes flash sales into three distinct layers, each managed through dedicated models, services, and controllers in the mall-admin module.

Promotion Definition Layer

The top-level SmsFlashPromotion entity defines the overall campaign with date boundaries and status controls. Stored in mall-mbg/src/main/java/com/macro/mall/model/SmsFlashPromotion.java, this model tracks startDate, endDate, and an status flag (1 for enabled, 0 for disabled). The SmsFlashPromotionServiceImpl handles business logic, while SmsFlashPromotionController exposes REST endpoints under /flash.

Promotion Session Layer

Within each promotion, SmsFlashPromotionSession defines daily time windows (e.g., 10:00–12:00). Located in mall-mbg/src/main/java/com/macro/mall/model/SmsFlashPromotionSession.java, sessions store startTime, endTime, and independent status flags. This allows granular control over specific time slots without modifying the parent promotion’s date range.

Product-Session Relation Layer

The SmsFlashPromotionProductRelation entity links actual products to specific promotion sessions. Defined in mall-mbg/src/main/java/com/macro/mall/model/SmsFlashPromotionProductRelation.java, it stores flashPromotionPrice, flashPromotionCount (total stock), and flashPromotionLimit (per-user purchase limits). This bridge table connects sms_flash_promotion, sms_flash_promotion_session, and product catalogs.

Admin Workflow and API Endpoints

Administrators configure flash sales through sequential REST API calls managed by the mall-admin module.

Creating Flash Promotions

To initiate a campaign, admins POST to /flash/create, handled by SmsFlashPromotionController#create:

SmsFlashPromotion flash = new SmsFlashPromotion();
flash.setTitle("双11限时购");
flash.setStartDate(DateUtil.parse("2024-11-11"));
flash.setEndDate(DateUtil.parse("2024-11-12"));
flash.setStatus(1);

int count = flashPromotionService.create(flash);

The service layer sets createTime and persists via SmsFlashPromotionMapper.

Managing Time Sessions

After creating a promotion, admins define time windows via /flashSession/create. The SmsFlashPromotionSessionServiceImpl processes these requests:

SmsFlashPromotionSession session = new SmsFlashPromotionSession();
session.setTitle("上午场");
session.setStartTime(DateUtil.parseTime("10:00:00"));
session.setEndTime(DateUtil.parseTime("12:00:00"));
session.setStatus(1);
flashPromotionSessionService.create(session);

Linking Products to Sessions

Finally, products are associated through /flashProductRelation/create in SmsFlashPromotionProductRelationController:

SmsFlashPromotionProductRelation rel = new SmsFlashPromotionProductRelation();
rel.setFlashPromotionId(flashId);
rel.setFlashPromotionSessionId(sessionId);
rel.setProductId(productId);
rel.setFlashPromotionPrice(new BigDecimal("99.99"));
rel.setFlashPromotionCount(200);
rel.setFlashPromotionLimit(2);
relationService.create(Collections.singletonList(rel));

Status management occurs through /update/status/{id} endpoints that trigger updateByPrimaryKeySelective on the respective mapper.

Portal Implementation: Rendering Active Flash Sales

The mall-portal module renders current flash sales through HomeServiceImpl#getHomeFlashPromotion(), which orchestrates the three-tier hierarchy into a customer-facing display.

Query Logic in HomeServiceImpl

The core method chains temporal queries to identify active promotions:

private HomeFlashPromotion getHomeFlashPromotion() {
    HomeFlashPromotion homeFlashPromotion = new HomeFlashPromotion();
    Date now = new Date();

    // ① Find promotion active on today's date
    SmsFlashPromotion flashPromotion = getFlashPromotion(now);
    if (flashPromotion != null) {
        // ② Find session covering current time
        SmsFlashPromotionSession flashPromotionSession = getFlashPromotionSession(now);
        if (flashPromotionSession != null) {
            homeFlashPromotion.setStartTime(flashPromotionSession.getStartTime());
            homeFlashPromotion.setEndTime(flashPromotionSession.getEndTime());

            // ③ Fetch next session for "即将开始" display
            SmsFlashPromotionSession nextSession = getNextFlashPromotionSession(
                homeFlashPromotion.getStartTime());
            if (nextSession != null) {
                homeFlashPromotion.setNextStartTime(nextSession.getStartTime());
                homeFlashPromotion.setNextEndTime(nextSession.getEndTime());
            }

            // ④ Load products for this promotion-session
            List<FlashPromotionProduct> flashProductList =
                homeDao.getFlashProductList(
                    flashPromotion.getId(), 
                    flashPromotionSession.getId());
            homeFlashPromotion.setProductList(flashProductList);
        }
    }
    return homeFlashPromotion;
}

Temporal Query Implementation

  • getFlashPromotion(Date): Queries SmsFlashPromotion where status = 1 and now falls between startDate and endDate
  • getFlashPromotionSession(Date): Checks SmsFlashPromotionSession where the current time (extracted via DateUtil.getTime) lies within startTime and endTime
  • getNextFlashPromotionSession(Date): Retrieves the earliest future session using start_time > date ordered ascending

The homeDao.getFlashProductList method (implemented in SmsFlashPromotionProductRelationDao.java) performs a three-table join to return FlashPromotionProduct DTOs containing product details and flash-specific pricing.

Data Model Relationships

The database schema follows a strict one-to-many hierarchy:


SmsFlashPromotion (1) ── (*) SmsFlashPromotionSession (1) ── (*) SmsFlashPromotionProductRelation

  • SmsFlashPromotion: Defines campaign duration via start_date and end_date columns
  • SmsFlashPromotionSession: References flashPromotionId and defines daily start_time/end_time boundaries
  • SmsFlashPromotionProductRelation: Contains foreign keys to both parent tables plus pricing and inventory fields (flash_promotion_price, flash_promotion_count)

The portal’s FlashPromotionProduct DTO extends PmsProduct to combine catalog data with flash sale attributes, creating a ready-to-render payload for the frontend home page.

Practical Code Examples

Retrieving Current Flash Sales (Portal)

Client applications access flash data through the content service:

HomeContentResult home = homeService.content();
HomeFlashPromotion flashBlock = home.getHomeFlashPromotion();

if (flashBlock != null) {
    System.out.println("Current flash sale ends at " + flashBlock.getEndTime());
    flashBlock.getProductList().forEach(p ->
        System.out.println(p.getName() + " – " + p.getFlashPromotionPrice()));
}

Key Source Files

Critical implementation files include:

Summary

  • Three-tier architecture: Separates campaign definition (SmsFlashPromotion), time windows (SmsFlashPromotionSession), and product assignments (SmsFlashPromotionProductRelation)
  • Admin control: Full CRUD operations via REST endpoints in mall-admin with status management and pagination via PageHelper
  • Portal rendering: HomeServiceImpl#getHomeFlashPromotion() queries active promotions by date and time, then loads associated products through custom DAO joins
  • Inventory protection: Product relations include both total stock (flashPromotionCount) and per-user limits (flashPromotionLimit) to prevent overselling
  • Temporal precision: The system distinguishes between promotion dates (calendar days) and session times (clock times) to support recurring daily flash sales within multi-day campaigns

Frequently Asked Questions

How does the system prevent users from purchasing flash sale items outside designated time windows?

The portal validates temporal eligibility in HomeServiceImpl#getHomeFlashPromotion(), which queries SmsFlashPromotionSession where the current time falls between startTime and endTime. Only products linked to currently active sessions are returned to the frontend, effectively blocking premature or expired purchases at the data retrieval layer.

What is the difference between flashPromotionCount and flashPromotionLimit?

According to the SmsFlashPromotionProductRelation model, flashPromotionCount defines the total inventory available for the flash sale session (system-wide stock), while flashPromotionLimit restricts how many units a single user can purchase. This dual-threshold approach prevents individual users from depleting inventory while maintaining scarcity controls.

How does the portal handle multiple concurrent flash sale promotions?

The getFlashPromotion(Date) method in HomeServiceImpl selects the active promotion based solely on date range and status = 1. If multiple promotions overlap on the same date, the implementation returns the first match found. For session conflicts within a promotion, getFlashPromotionSession(Date) identifies the specific time window containing the current moment, ensuring only one session’s products display at any given time.

Where does the aggregation of flash sale product data occur?

The SmsFlashPromotionProductRelationDao.java interface defines getFlashProductList(Long promotionId, Long sessionId), which executes a SQL join across sms_flash_promotion, sms_flash_promotion_session, and product tables. This aggregation happens at the database level via MyBatis mappers, returning FlashPromotionProduct DTOs that combine relational data with base product attributes from PmsProduct.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →