7.0 KiB
7.0 KiB
3D Virtual Tour Map - Architecture Diagram
Technology Stack
Backend
- Runtime: Node.js
- Framework: Express.js
- Database: MongoDB (Mongoose ODM)
- Authentication: JWT (jsonwebtoken) + bcrypt
- Image Processing: Sharp (resize), exifr (read EXIF), piexifjs (write EXIF)
- File Upload: Multer
- Security: CORS, referer verification, no-cache headers
Frontend
- Map: Leaflet.js (OpenStreetMap tiles)
- 3D Viewer: Pannellum.js (360° panorama viewer)
- UI: Vanilla HTML/CSS/JavaScript
- State: localStorage for JWT tokens
Architecture Diagram
graph TB
subgraph "Client (Browser)"
UI[HTML/CSS UI]
MAP[Leaflet Map]
VIEWER[Pannellum 3D Viewer]
AUTH[Auth Panel]
MODAL[Scene Creation Modal]
end
subgraph "Backend (Node.js/Express)"
SERVER[server.js]
ROUTES[API Routes]
MIDDLEWARES[Security & Auth Middlewares]
UTILS[Image & EXIF Utils]
MODELS[Mongoose Models]
end
subgraph "Database (MongoDB)"
USERS[Users Collection]
SCENES[Scenes Collection]
ASSETS[Assets Collection]
end
subgraph "File System"
UPLOADS[uploads/ Directory]
TEMP[temp/ Directory]
end
UI --> MAP
UI --> AUTH
UI --> MODAL
UI --> VIEWER
MAP -->|Right-click| MODAL
MAP -->|Load Scenes| ROUTES
MAP -->|Click Marker| VIEWER
AUTH -->|Login/Register| ROUTES
AUTH -->|Store JWT| UI
MODAL -->|Upload Image| ROUTES
VIEWER -->|Request Image| ROUTES
ROUTES --> MIDDLEWARES
MIDDLEWARES -->|Verify JWT| AUTH
MIDDLEWARES -->|Verify Referer| UI
MIDDLEWARES -->|Privacy Check| SCENES
ROUTES --> MODELS
MODELS --> USERS
MODELS --> SCENES
MODELS --> ASSETS
ROUTES --> UTILS
UTILS -->|Resize| UPLOADS
UTILS -->|Read/Write EXIF| UPLOADS
UTILS -->|Temp Storage| TEMP
SCENES --> ASSETS
ASSETS --> UPLOADS
SERVER --> ROUTES
SERVER -->|Serve Static| UI
Data Flow
1. User Registration/Login
Client → POST /api/auth/register or /api/auth/login
→ authRoutes.js
→ User model (bcrypt hash/compare)
→ JWT generation
→ Response with token
→ Client stores token in localStorage
2. Scene Creation (Upload 360° Image)
Client → Right-click on map → Open modal with lat/lng
→ POST /api/scenes (with multipart/form-data)
→ authMiddleware.protect (verify JWT)
→ Multer saves to temp/
→ imageHelper.resizeTo8K (resize to 8192x4096)
→ exifHelper.getGPSCoordinates (read original GPS)
→ exifHelper.injectGPSCoordinates (inject map lat/lng)
→ Asset model saved to DB
→ Scene model saved to DB (with privacy settings)
→ Delete temp file
→ Response with scene data
3. Load Scenes on Map
Client → GET /api/scenes (with optional JWT)
→ authMiddleware.optionalAuth
→ Scene.find() with privacy filter:
- Guests: public + shared scenes
- Logged-in: public + member + owned + shared-with-me
→ Populate owner and asset data
→ Response with scene list
→ Client adds markers to Leaflet map
4. View 3D Panorama
Client → Click marker → GET /api/scenes/:id (with token if shared)
→ authMiddleware.optionalAuth
→ Privacy verification
→ Response with scene details
→ Client constructs secure image URL: /api/assets/view/:assetId?token=...
→ GET /api/assets/view/:assetId
→ securityMiddleware.verifyReferer (anti-hotlinking)
→ securityMiddleware.setNoCacheHeaders
→ Privacy verification again
→ Stream image file from disk
→ Pannellum viewer displays 360° panorama
→ Client-side security: block right-click, drag, keyboard shortcuts
Security Layers
Backend Security
- JWT Authentication: Required for creating scenes, optional for viewing
- Privacy Model: Four levels (public, private, member, shared)
- Referer Verification: Prevents direct URL access to images
- No-Cache Headers: Prevents browser caching of protected images
- Share Tokens: For shared scenes, token required for access
Frontend Security
- Right-click Blocking: Prevents image saving in viewer
- Drag Prevention: Blocks drag-and-drop of images
- Keyboard Restrictions: Blocks F12, Ctrl+S, Ctrl+U, Ctrl+Shift+I
- Token-based Access: Share tokens passed in URLs for shared content
Database Schema
User Model
{
username: String (unique, required),
password: String (bcrypt hashed),
role: Enum ['Chủ sở hữu', 'Thành viên'],
timestamps: true
}
Scene Model
{
title: String (required),
assetId: ObjectId (ref: Asset),
lat: Number (required),
lng: Number (required),
owner: ObjectId (ref: User),
privacy: Enum ['public', 'private', 'shared', 'member'],
shareToken: String (unique, sparse),
sharedWith: [ObjectId] (ref: User),
timestamps: true
}
Asset Model
{
filePath: String (required),
uploadedBy: ObjectId (ref: User),
coordinates: { lat: Number, lng: Number },
timestamps: true
}
File Structure
3dtours/
├── backend/
│ ├── config/
│ │ └── db.js # MongoDB connection
│ ├── middlewares/
│ │ ├── authMiddleware.js # JWT verification
│ │ └── securityMiddleware.js # Referer check, cache control
│ ├── models/
│ │ ├── User.js # User schema
│ │ ├── Scene.js # Scene schema
│ │ └── Asset.js # Asset schema
│ ├── routes/
│ │ ├── authRoutes.js # Login/register endpoints
│ │ └── apiRoutes.js # Scenes/assets endpoints
│ ├── utils/
│ │ ├── imageHelper.js # Sharp resize to 8K
│ │ └── exifHelper.js # GPS read/write
│ ├── uploads/ # Processed images
│ │ └── temp/ # Temporary upload storage
│ ├── server.js # Express app entry point
│ ├── package.json
│ └── .env # Environment variables
└── frontend/
├── css/
│ └── style.css # UI styling
├── js/
│ ├── main_map.js # Map logic, auth, scene loading
│ └── viewer360.js # Pannellum viewer + security
└── index.html # Main UI
Key Features
- Interactive Map: Leaflet-based map with scene markers
- 3D Panorama Viewer: Pannellum for 360° image viewing
- User Authentication: Registration, login with role-based access
- Privacy Controls: Public, private, member-only, and shared scenes
- Image Processing: Automatic resize to 8K (8192x4096) for consistency
- GPS Handling: Extract original EXIF GPS, inject map coordinates
- Security: Multi-layer protection against unauthorized access
- Share Links: Token-based sharing for restricted content