An enterprise-grade meal ordering platform for company employees. Built with Java 17 + Spring Boot (backend) and React + TypeScript (frontend).
| Layer | Technology |
|---|---|
| Backend | Java 17, Spring Boot 3.3.4, Spring Security, Spring Data JPA |
| Frontend | React 18, TypeScript, Vite, Ant Design, Zustand |
| Database | MySQL 8.x (external, standalone) |
| Cache | Caffeine (in-memory) |
| Auth | JWT HS384 (access 1h + refresh 7d tokens) |
| Build | Maven (backend), npm (frontend) |
- Employee registration & login (JWT, bcrypt, email validation)
- Daily menu browsing with dish cards, images, prices
- Dish detail with customization options (required/optional, extra costs)
- Multi-select cuisine filter + keyword search
- Shopping cart with unique entries per customization
- Idempotent order placement (UUID-based)
- Order history & detail with cancellation
- Dietary Preferences: Allergen marking, cuisine preferences, spice/taste preferences, per-meal budget
- Allergen Warning: Popup when adding allergen-containing dishes to cart
- Ordering Rules: Max 5 items/order, cutoff times (10:00 lunch / 15:00 dinner), auto-switch meal slot, duplicate prevention
- Admin Console: Dish CRUD, on/off shelf toggle, daily menu setup with supply quantities
- Role-based Access: Admin-only routes and API endpoints
webox-xuhaodong-20260831/
├── backend/ # Spring Boot application
│ ├── pom.xml
│ └── src/main/java/com/webox/
│ ├── config/ # Security, Cache, Web, DataInitializer
│ ├── controller/ # REST endpoints (auth, dishes, orders, admin)
│ ├── dto/ # Request/Response DTOs
│ ├── entity/ # JPA entities
│ ├── enums/ # Domain enums
│ ├── exception/ # Global exception handling
│ ├── repository/ # Spring Data JPA repositories
│ ├── security/ # JWT provider & filter
│ └── service/ # Business logic
├── frontend/ # React SPA
│ └── src/
│ ├── api/ # Axios client & API modules
│ ├── components/ # Layout, dish, cart, admin components
│ ├── pages/ # Page components
│ ├── store/ # Zustand state management
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Money, cart, date utilities
├── docs/ # PRD and product images
├── ai-conversations/ # AI coding conversation logs (required deliverable)
├── API.md # Full API reference with request/response examples
├── ARCHITECTURE.md # System architecture, data flow, design decisions
└── README.md # This file
- JDK 17+ —
java -version - Maven 3.8+ —
mvn -version - Node.js 18+ —
node -v - MySQL 8.x — running and accessible
Create a MySQL database:
CREATE DATABASE webox CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Update credentials in backend/src/main/resources/application.yml if needed:
spring:
datasource:
url: jdbc:mysql://localhost:3306/webox?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: rootcd backend
mvn spring-boot:runThe backend starts on port 8080. On first run:
- Flyway automatically runs 4 migration scripts (users, dishes, orders, preferences)
- DataInitializer seeds:
- Admin account:
admin@webox.com/Admin123 - 9 dishes with customization options (burgers, pasta, salads, etc.)
- 20 product images copied from
docs/webox_product_images/product_images/→./uploads/ - Today's daily menu (all 9 dishes, 50 supply each)
- Tomorrow's daily menu (6 dishes, 30 supply each)
- Admin account:
cd frontend
npm install
npm run devThe frontend starts on port 5173 with a Vite proxy (/api → http://localhost:8080).
- Frontend: http://localhost:5173
- Backend API: http://localhost:8080/api
- Admin account:
admin@webox.com/Admin123
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
Public | Register new employee |
| POST | /api/auth/login |
Public | Login → JWT tokens |
| POST | /api/auth/refresh |
Public | Refresh access token |
| GET | /api/auth/me |
Bearer | Current user info |
| GET | /api/menu/today |
Bearer | Today's menu with inventory |
| GET | /api/menu/{date} |
Bearer | Menu for specific date |
| GET | /api/dishes |
Bearer | Search/filter dishes |
| GET | /api/dishes/{id} |
Bearer | Dish detail + customizations |
| GET | /api/orders/current-slot |
Bearer | Current meal slot + active order check |
| POST | /api/orders |
Bearer | Place order (idempotent) |
| GET | /api/orders |
Bearer | Order history (paginated) |
| GET | /api/orders/{orderNo} |
Bearer | Order detail |
| PUT | /api/orders/{orderNo}/cancel |
Bearer | Cancel pending order |
| GET | /api/preferences |
Bearer | Get dietary preferences |
| PUT | /api/preferences |
Bearer | Update dietary preferences |
| POST | /api/files/upload |
Admin | Upload dish image |
| GET | /api/files/{filename} |
Public | Serve uploaded file |
| GET | /api/admin/dishes |
Admin | List all dishes (incl. off-shelf) |
| POST | /api/admin/dishes |
Admin | Create dish |
| PUT | /api/admin/dishes/{id} |
Admin | Update dish |
| PATCH | /api/admin/dishes/{id}/status |
Admin | Toggle on/off shelf |
| GET | /api/admin/daily-menu/{date} |
Admin | View daily menu setup |
| PUT | /api/admin/daily-menu/{date} |
Admin | Setup daily menu |
Full API reference with request/response examples: see API.md.
┌─────────────────────────────────────────┐
│ React SPA (port 5173) │
│ Ant Design + Zustand + Axios │
│ JWT auto-refresh interceptor │
└──────────────────┬──────────────────────┘
│ HTTP (Vite proxy /api → :8080)
┌──────────────────▼──────────────────────┐
│ Spring Boot (port 8080) │
│ │
│ ┌─ Security ──────────────────────┐ │
│ │ JWT filter · BCrypt · RBAC │ │
│ └─────────────────────────────────┘ │
│ ┌─ Controllers ───────────────────┐ │
│ │ Auth · Dish · Order · Prefs │ │
│ │ AdminDish · AdminDailyMenu │ │
│ └─────────────────────────────────┘ │
│ ┌─ Services ──────────────────────┐ │
│ │ @Transactional(rollbackFor) │ │
│ │ @Cacheable / @CacheEvict │ │
│ └─────────────────────────────────┘ │
│ ┌─ Repositories ──────────────────┐ │
│ │ Spring Data JPA │ │
│ │ Pessimistic locks (inventory) │ │
│ └─────────────────────────────────┘ │
│ ┌─ Cache (Caffeine) ──────────────┐ │
│ │ dishes: 10 min · menu: 5 min │ │
│ └─────────────────────────────────┘ │
└──────────────────┬──────────────────────┘
│ JDBC (Flyway migrations)
┌──────────────────▼──────────────────────┐
│ MySQL 8.x (standalone) │
│ 10 tables · utf8mb4 │
└─────────────────────────────────────────┘
See ARCHITECTURE.md for detailed data flow, module breakdown, and design decisions.
| Decision | Approach |
|---|---|
| Money | All prices stored as INT in cents (¥22.50 = 2250). No floating-point arithmetic anywhere. |
| Idempotency | Client generates crypto.randomUUID() per order. DB UNIQUE constraint on idempotency_key. |
| Inventory safety | UPDATE ... WHERE supply - sold >= qty atomic row-level operation. No overselling under concurrency. |
| Order snapshots | order_items store dish_name and unit_price_cents at order time. Historical orders survive dish edits. |
| Cache | Caffeine in-memory. Dishes: 10 min, daily menus: 5 min. Evicted on admin CRUD or order changes. |
| Cutoff logic | < 10:00 → Lunch today · < 15:00 → Dinner today · ≥ 15:00 → Lunch tomorrow |
| Transactions | All @Transactional declare rollbackFor = Exception.class for full rollback coverage. |
| Lazy loading | Read methods use @Transactional(readOnly = true) to keep session open during DTO mapping. |
| Cart uniqueness | `dishId::groupName:optionName |
| Product images | Auto-copied from docs/webox_product_images/ → ./uploads/ on first startup. Served at /api/files/. |
users ─────────< orders ─────────< order_items ─────────< order_item_customizations
│
│ delivery_date + meal_slot (unique active per user)
│
dishes ─────────< daily_menu_items >───────── daily_menus
│
├── customization_groups ────< customization_options
│
dietary_preferences ──── users (1:1)
4 Flyway migrations: V1__create_users, V2__create_dishes_and_menu, V3__create_orders, V4__create_dietary_preferences.
cd backend
mvn test24 unit tests (JUnit 5 + Mockito) covering:
- AuthService (5 tests): register, login, duplicate email, wrong password, user not found
- OrderService (8 tests): place order, idempotency, 5-item limit, duplicate order, insufficient inventory, cancel, wrong user
- InventoryService (5 tests): deduct, insufficient stock, restore, remaining calculation, low-stock threshold
- DietaryPreferenceService (4 tests): get existing, get defaults, create, update
- CutoffTime (2 tests): delivery date, meal slot determination
| Item | Status |
|---|---|
| Runnable full-stack code | ✅ |
| README | ✅ |
| API documentation (API.md) | ✅ |
| Architecture documentation (ARCHITECTURE.md) | ✅ |
| Test cases (24 unit tests, all passing) | ✅ |
AI coding conversation logs (ai-conversations/) |
✅ |
| English UI (all user-facing content) | ✅ |
| Independent MySQL (not embedded) | ✅ |
| Seed data (dishes + images + admin) | ✅ |