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

> Explore the flash sale and promotion system in macrozheng/mall. Understand its three-tier hierarchy Promotion Session Product Relation and how it delivers time-bound discounts.

- Repository: [macro/mall](https://github.com/macrozheng/mall)
- Tags: deep-dive
- Published: 2026-02-28

---

**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`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/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`:

```java
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:

```java
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`:

```java
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:

```java
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`](https://github.com/macrozheng/mall/blob/main/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:

```java
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:

- **Models**: [`mall-mbg/src/main/java/com/macro/mall/model/SmsFlashPromotion.java`](https://github.com/macrozheng/mall/blob/main/mall-mbg/src/main/java/com/macro/mall/model/SmsFlashPromotion.java), [`SmsFlashPromotionSession.java`](https://github.com/macrozheng/mall/blob/main/SmsFlashPromotionSession.java), [`SmsFlashPromotionProductRelation.java`](https://github.com/macrozheng/mall/blob/main/SmsFlashPromotionProductRelation.java)
- **Admin Services**: [`mall-admin/src/main/java/com/macro/mall/service/impl/SmsFlashPromotionServiceImpl.java`](https://github.com/macrozheng/mall/blob/main/mall-admin/src/main/java/com/macro/mall/service/impl/SmsFlashPromotionServiceImpl.java), [`SmsFlashPromotionSessionServiceImpl.java`](https://github.com/macrozheng/mall/blob/main/SmsFlashPromotionSessionServiceImpl.java)
- **Portal Service**: [`mall-portal/src/main/java/com/macro/mall/portal/service/impl/HomeServiceImpl.java`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/java/com/macro/mall/portal/service/impl/HomeServiceImpl.java)
- **DAO**: [`mall-admin/src/main/java/com/macro/mall/dao/SmsFlashPromotionProductRelationDao.java`](https://github.com/macrozheng/mall/blob/main/mall-admin/src/main/java/com/macro/mall/dao/SmsFlashPromotionProductRelationDao.java)

## 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`](https://github.com/macrozheng/mall/blob/main/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`.