From Zero to One: Build a Smart Table Reservation System with Triple‑Endpoint Architecture and Apple‑Style Design
This article walks through the end‑to‑end creation of a production‑grade table‑reservation platform, covering the three‑tier client architecture (WeChat Mini‑Program, H5/App via UniApp, and a Vue3 + Element Plus PC admin), a dual Spring Boot backend, Redis distributed locking combined with MySQL unique indexes for high‑concurrency seat booking, JWT‑based authentication, RBAC permission control, and an Apple‑inspired design system, all illustrated with concrete code snippets and deployment tips.
Project Background
The author needed a cost‑effective reservation system for a restaurant‑plus‑board‑game venue, as existing SaaS solutions were either prohibitively expensive or overly generic.
Overall Architecture
A three‑client separation (WeChat Mini‑Program, H5/App, PC admin) is backed by two independent Spring Boot services—one for the mobile side (port 8021) and one for the management side—both sharing the same MySQL 8.0 and Redis 7 instances. The table below summarizes the key dimensions:
Service Target : Mobile users vs. administrators
QPS Profile : High read‑many‑write‑few for mobile, low‑throughput heavy queries for admin
Security : Anonymous access for most mobile APIs, full authentication & RBAC for admin
Cache Strategy : Redis caches table status; core data lives in MySQL
Front‑End – UniApp + Apple Design System
UniApp compiles a single Vue 3 codebase to Mini‑Program, H5, and native App, providing the most economical way to support all three platforms. The design system mirrors Apple’s minimal aesthetic, using a palette of pure black (#000000), light gray (#f5f5f7), and Apple Blue (#0071e3). Design tokens are defined as CSS variables in :root:
:root {
--sk-black: #000000;
--sk-light-gray: #f5f5f7;
--sk-text-primary: #1d1d1f;
--sk-focus-color: #0071e3;
--sk-radius-pill: 980px;
--sk-radius-card: 8px;
--sk-shadow-card: rgba(0,0,0,0.22) 3px 5px 30px;
}Routing is shared; after login the role stored in user.roles determines which menu items are displayed, allowing a single Mini‑Program package for both customers and staff.
Front‑End – Vue3 + Element Plus Admin
The PC admin UI uses Vue 3, Vue‑Router, Pinia, and Element Plus. Core dependencies are listed in package.json:
{
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"pinia": "^2.1.7",
"element-plus": "^2.6.0",
"axios": "^1.6.7",
"@wangeditor/editor": "^5.1.23",
"vuedraggable": "^4.1.0",
"vite": "^5.2.8"
}Menu configuration is fetched from the backend and registered dynamically via router.addRoute(), so new menus require no front‑end redeployment.
Back‑End – Spring Boot 3 + MyBatis‑Plus
Both mobile and admin services share the same Maven parent ( spring-boot-starter-parent 3.x) and include the following core dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.x</version>
</dependency>The project follows a classic three‑layer structure (controller → service → mapper) plus a security layer (JWT utilities, filter, and UserDetailsService).
Concurrency Control for Seat Booking
The core challenge is preventing double‑booking of the same seat in the same time slot. Three candidate solutions are compared, and the final design combines a Redis distributed lock (SETNX with a 30 s TTL) and a MySQL unique index on (seat_id, reservation_date, start_time, end_time) as a safety net.
// Reservation flow
1. SETNX "reserve:lock:{seat}:{date}" 30s
2. If lock acquired, query DB for overlapping reservations
3. If none, start transaction and INSERT reservation
4. On success commit and DEL lock; on failure roll back and DEL lockThe overlapping‑interval SQL used in step 2 is:
WHERE seat_id = #{seatId}
AND reservation_date = #{date}
AND status IN (0,1,2)
AND start_time < #{endTime}
AND end_time > #{startTime}
LIMIT 1This “A start < end B && A end > B start” check guarantees no time‑slot clash.
Cache Strategy for Table Status
To render the daily seat‑availability map quickly, the system caches a hash in Redis keyed by app:table:daily:status:{date}. Cache miss triggers a DB aggregation; any reservation change invalidates the key.
Database Schema
The solution uses 13 tables. The most critical is reservation, which stores
user_id, shop_id, area_id, seat_id, reservation_date, start_time, end_time, duration, pricing_type, package_id, total_price, status, contact_name, phone, remark, created_at, updated_at. A composite unique index on (seat_id, reservation_date, start_time, end_time) provides the final safeguard against overselling.
Security – JWT + RBAC
Three roles are defined: ROLE_USER (customers), ROLE_MOBILE_ADMIN (staff using the Mini‑Program), and ROLE_PC_ADMIN (full‑stack administrators). Permissions are expressed at the menu‑button level, enabling fine‑grained control such as “order view + accept button” without “delete order”.
@Component
public class JwtUtils {
private static final String SECRET = "SECURITY";
private static final long EXPIRE_TIME = 86400000L; // 24h
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(Long userId) { ... }
public Long getUserIdFromToken(String token) { ... }
public boolean validateToken(String token) { ... }
}Login flow: WeChat code → openid + session_key → user lookup/creation → JWT issuance → client stores token for subsequent Authorization: Bearer requests.
WeChat Ecosystem Integration
Message subscription is handled via a WechatMessageService interface. Seven key notification points are implemented (reservation success, admin acceptance, rejection, one‑hour reminder, completion, user‑initiated cancellation, status change). The service respects the user’s subscription status (must call wx.requestSubscribeMessage()) and caches the WeChat access_token in Redis, refreshing 5 minutes before expiry.
Summary & Lessons Learned
Key successes include the dual‑backend isolation, Apple‑style design tokenization, UniApp code‑share for customer and staff, and the Redis‑lock + DB‑index double‑guard that eliminated overselling under 200+ concurrent requests. Pain points were UniApp CSS incompatibilities, MyBatis‑Plus LocalTime serialization quirks, and the need for a more robust lock renewal mechanism (Redisson watchdog).
Future roadmap items: integrate WeChat Pay, add membership & coupon engine, build visual floor‑plan editor, multi‑tenant SaaS refactor, and replace manual SETNX with Redisson distributed locks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
