Design a Scalable Bus & Subway Ticketing System from Scratch
This article walks through the full architecture of a modern bus and subway ticketing platform, covering functional and non‑functional requirements, core components, interaction flows, database schema, shortest‑path routing with Dijkstra, QR‑code generation in Go, and high‑concurrency strategies.
1. Introduction
The author sets the scene of a typical commuter rushing to catch a bus or subway, using a mobile QR code as the "key" to board without buying a ticket on the spot.
2. Requirements Design
2.1 Functional Requirements
User registration and login via mobile app or mini‑program.
Route query for subway lines, stations, departure times and fares.
Generation of a travel QR code linked to the user’s account and payment method.
Real‑time location of subway trains.
Scan‑to‑pay at entry and exit, with automatic fare calculation.
Transaction history query.
2.2 Non‑Functional Requirements
Uneven user distribution: millions of daily users in first‑tier cities.
Peak‑time traffic: far higher load during morning and evening rush hours.
High concurrency handling.
Low latency for queries and payments.
Scalability to accommodate growth.
24/7 availability.
Security and privacy for personal and payment data.
3. High‑Level Architecture
3.1 Core Components
Frontend application (mobile app / mini‑program).
Backend services for user management, route query, QR‑code handling, order processing and payment.
Relational database cluster (MySQL) for persisting user, route and transaction data.
Push system to deliver payment results to the client.
Load balancer and message queue to improve performance.
3.2 Ticketing Flow
3.2.1 Interaction Between Mobile App and Backend
The user registers and logs in, then the backend generates a QR code that can be displayed at any time.
3.2.2 Interaction Between Mobile App and Vehicle
User scans the QR code at the entry gate; the gate forwards the code to the backend.
The backend validates the code, checks entry records and records the time and location.
After travel, the user scans the QR code at the exit gate.
The backend validates the exit, matches it with an entry record and records the exit time and location.
3.2.3 Backend Processing
Fare calculation based on entry/exit stations and city‑specific rules.
Recording the fare and deducting it from the user’s payment method (e.g., Alipay or WeChat Pay).
Storing the trip record for user review and settlement.
Sending a notification to the user when the fare is deducted.
Database interactions for all read/write operations.
4. Detailed Design
4.1 Database Schema
User table (User): user ID, phone, password, payment method, creation time.
QRCode table (QRCode): QR code ID, user ID, city ID, generation time, expiry, QR data.
Vehicle table (Vehicle): vehicle ID, plate or train number, type (bus/subway), scanner device ID.
TripRecord table (TripRecord): record ID, user ID, vehicle ID, board/alight times, start/end stations.
PaymentRecord table (PaymentRecord): payment ID, trip record ID, transaction time, amount, method, status.
4.2 Shortest‑Path Query
The system stores station coordinates and uses Dijkstra’s algorithm to find the shortest route. The step‑by‑step traversal is presented as an ordered list:
Select A (distance 0). Adjacent nodes B (6) and C (3). Choose C.
Select C (distance 3). Update distances: B remains 6, D becomes 7 (A‑C‑D).
Select B (distance 6). No shorter path to D.
Select D (distance 7). Adjacent E (2) and F (3). Choose E.
Select E (distance 9). Update F via D‑F = 10.
Select F (distance 10). All nodes visited; final distances: A=0, C=3, B=6, D=7, E=9, F=10.
Station coordinates are encoded as strings and stored using Redis GeoHash for fast nearby‑station lookup.
4.3 QR Code Management
QR codes are generated with the Go library github.com/skip2/go-qrcode:
import "github.com/skip2/go-qrcode
func main() {
qr, err := qrcode.New("https://mp.weixin.qq.com", qrcode.Medium)
if err != nil {
log.Fatal(err)
} else {
qr.BackgroundColor = color.RGBA{50, 205, 50, 255} // background
qr.ForegroundColor = color.White // foreground
qr.WriteFile(256, "./wechatgzh_qrcode.png")
}
}The generated QR data is stored in a dedicated table with fields: QRCodeID, UserID, QRData, GenerationTime, ExpiryTime. High‑concurrency handling includes:
Load balancing across multiple servers.
Caching generated QR codes in Redis to avoid repeated generation.
Rate limiting (e.g., max 5 QR codes per minute per user) to prevent abuse.
Security measures such as encrypted storage protect user privacy and payment information.
5. Future Directions
Smart boarding with facial recognition and automatic fare deduction.
Big‑data analytics on travel data to improve services.
Continuous focus on user experience, performance and security as demand grows.
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.
LouZai
10 years of front‑line experience at leading firms (Xiaomi, Baidu, Meituan) in development, architecture, and management; discusses technology and life.
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.
