基于Angular 4、Node.js的实时通知实现示例及项目适配请求
Hey there! Let's tackle your real-time notification needs for both your current Symfony 3 + Angular 4 neighborhood sharing project and the Angular 4 + Node.js + MySQL setup you're curious about.
We'll use Socket.io as the real-time layer (with a lightweight Node.js relay server, since Symfony doesn't have native WebSocket support out of the box) to send instant alerts when a user clicks the "Borrow" button.
Architecture Overview
- Angular frontend clicks "Borrow" → sends request to Symfony backend
- Symfony processes the borrow request, then triggers a real-time alert via the Node.js Socket.io server
- Node.js server pushes the alert to the item owner's Angular client (if online)
- If the owner is offline, we store the notification in Symfony's database for later retrieval
Step 1: Set Up the Node.js Socket.io Relay Server
First, create a simple Node.js server to handle WebSocket connections:
const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: "http://your-angular-frontend-url", // Replace with your frontend URL methods: ["GET", "POST"] } }); // Map user IDs to their active socket connections const userSocketMap = new Map(); // Handle client connections io.on('connection', (socket) => { // Register user when they log in socket.on('register-user', (userId) => { userSocketMap.set(userId, socket); console.log(`User ${userId} connected`); }); // Clean up on disconnect socket.on('disconnect', () => { for (const [userId, sock] of userSocketMap.entries()) { if (sock === socket) { userSocketMap.delete(userId); console.log(`User ${userId} disconnected`); break; } } }); }); // Endpoint for Symfony to trigger notifications app.use(express.json()); app.post('/send-alert', (req, res) => { const { recipientId, message, data } = req.body; const targetSocket = userSocketMap.get(recipientId); if (targetSocket) { targetSocket.emit('borrow-request', { message, data: { ...data, timestamp: new Date().toISOString() } }); res.status(200).json({ success: true }); } else { res.status(404).json({ success: false, message: 'Recipient not online' }); } }); const PORT = 3001; server.listen(PORT, () => console.log(`Socket.io relay running on port ${PORT}`));
Step 2: Symfony Backend Integration
Update your Symfony controller to handle borrow requests and trigger notifications:
// src/AppBundle/Controller/ItemController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use GuzzleHttp\Client; use AppBundle\Entity\Notification; class ItemController extends Controller { public function borrowAction(Request $request) { // 1. Extract request data $itemId = $request->request->get('itemId'); $borrower = $this->getUser(); // 2. Fetch item and its owner $em = $this->getDoctrine()->getManager(); $item = $em->getRepository('AppBundle:Item')->find($itemId); $owner = $item->getOwner(); // 3. Save borrow request to database (customize this logic) $borrowRequest = new \AppBundle\Entity\BorrowRequest(); $borrowRequest->setItem($item); $borrowRequest->setBorrower($borrower); $borrowRequest->setStatus('pending'); $em->persist($borrowRequest); // 4. Send real-time alert (fallback to database if offline) $client = new Client(); try { $client->post('http://your-node-server-url:3001/send-alert', [ 'json' => [ 'recipientId' => $owner->getId(), 'message' => "Someone wants to borrow your item: {$item->getName()}", 'data' => ['itemId' => $itemId, 'borrowerName' => $borrower->getName()] ] ]); } catch (\Exception $e) { // Save notification to database for later retrieval $notification = new Notification(); $notification->setRecipient($owner); $notification->setMessage("Someone wants to borrow your item: {$item->getName()}"); $notification->setData(['itemId' => $itemId, 'borrowerName' => $borrower->getName()]); $em->persist($notification); $this->get('logger')->error('Failed to send real-time alert: ' . $e->getMessage()); } $em->flush(); return new JsonResponse(['success' => true]); } }
Step 3: Angular Frontend Implementation
First install the Socket.io client:
npm install socket.io-client@2.x --save
Create a reusable Socket service:
// src/app/services/socket.service.ts import { Injectable } from '@angular/core'; import * as io from 'socket.io-client'; import { Observable } from 'rxjs/Observable'; @Injectable() export class SocketService { private socket; private serverUrl = 'http://your-node-server-url:3001'; constructor() { this.socket = io(this.serverUrl); } // Register user with the Socket server registerUser(userId: number): void { this.socket.emit('register-user', userId); } // Listen for borrow request alerts onBorrowRequest(): Observable<any> { return new Observable(observer => { this.socket.on('borrow-request', (alert) => { observer.next(alert); }); }); } disconnect(): void { this.socket.disconnect(); } }
Update your navbar component to register the user and listen for alerts:
// src/app/components/navbar/navbar.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { SocketService } from '../../services/socket.service'; import { AuthService } from '../../services/auth.service'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html' }) export class NavbarComponent implements OnInit, OnDestroy { private currentUserId: number; constructor(private socketService: SocketService, private authService: AuthService) { this.currentUserId = this.authService.getCurrentUser().id; } ngOnInit() { this.socketService.registerUser(this.currentUserId); // Listen for new borrow requests this.socketService.onBorrowRequest().subscribe(alert => { // Show alert (use a toast library like ngx-toastr for better UX) alert(`Alert: ${alert.message}\nBorrower: ${alert.data.borrowerName}`); }); } ngOnDestroy() { this.socketService.disconnect(); } }
Finally, add the "Borrow" button logic to your item component:
// src/app/components/item/item.component.ts import { Component, Input } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-item', templateUrl: './item.component.html' }) export class ItemComponent { @Input() item: any; constructor(private http: HttpClient) {} onBorrowClick() { this.http.post('/api/items/borrow', { itemId: this.item.id }) .subscribe(() => { alert('Borrow request submitted!'); }, (error) => { alert('Failed to submit request. Please try again.'); }); } }
For this setup, Node.js acts as both the API backend and WebSocket server, with MySQL storing users, items, and notification records (including unread alerts).
Step 1: Node.js Backend Setup
First, install dependencies:
npm install express socket.io mysql2 cors body-parser --save
Create a database connection:
// db.js const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: 'localhost', user: 'your-db-user', password: 'your-db-password', database: 'neighborhood_share', connectionLimit: 10 }); module.exports = pool;
Create the combined API + Socket server:
// server.js const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const cors = require('cors'); const bodyParser = require('body-parser'); const pool = require('./db'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: "http://your-angular-frontend-url" } }); app.use(cors()); app.use(bodyParser.json()); const userSocketMap = new Map(); // Handle Socket connections io.on('connection', async (socket) => { socket.on('register-user', async (userId) => { userSocketMap.set(userId, socket); // Push unread notifications to the user const [unreadAlerts] = await pool.execute( 'SELECT * FROM notifications WHERE recipient_id = ? AND is_read = 0 ORDER BY created_at DESC', [userId] ); if (unreadAlerts.length > 0) { socket.emit('unread-notifications', unreadAlerts); } }); // Mark notification as read socket.on('mark-read', async (notificationId) => { await pool.execute('UPDATE notifications SET is_read = 1 WHERE id = ?', [notificationId]); }); socket.on('disconnect', () => { for (const [userId, sock] of userSocketMap.entries()) { if (sock === socket) { userSocketMap.delete(userId); break; } } }); }); // API: Submit borrow request app.post('/api/borrow', async (req, res) => { const { itemId, borrowerId } = req.body; try { // Get item owner const [itemRows] = await pool.execute('SELECT owner_id, name FROM items WHERE id = ?', [itemId]); if (itemRows.length === 0) return res.status(404).json({ success: false }); const ownerId = itemRows[0].owner_id; const itemName = itemRows[0].name; // Save borrow request await pool.execute( 'INSERT INTO borrow_requests (item_id, borrower_id, status) VALUES (?, ?, ?)', [itemId, borrowerId, 'pending'] ); // Create notification record const [notificationResult] = await pool.execute( 'INSERT INTO notifications (recipient_id, message, data, is_read) VALUES (?, ?, ?, ?)', [ ownerId, `Someone wants to borrow your item: ${itemName}`, JSON.stringify({ itemId, borrowerId }), 0 ] ); // Push real-time alert const targetSocket = userSocketMap.get(ownerId); if (targetSocket) { targetSocket.emit('borrow-request', { id: notificationResult.insertId, message: `Someone wants to borrow your item: ${itemName}`, data: { itemId, borrowerId }, is_read: 0, created_at: new Date().toISOString() }); } res.status(200).json({ success: true }); } catch (error) { console.error(error); res.status(500).json({ success: false }); } }); // API: Get all user notifications app.get('/api/notifications/:userId', async (req, res) => { const userId = req.params.userId; const [notifications] = await pool.execute( 'SELECT * FROM notifications WHERE recipient_id = ? ORDER BY created_at DESC', [userId] ); res.status(200).json(notifications); }); const PORT = 3000; server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Step 2: Angular Frontend Integration
The Angular setup is similar to the previous example, but update the Socket service to include API calls:
// src/app/services/socket.service.ts import { Injectable } from '@angular/core'; import * as io from 'socket.io-client'; import { Observable } from 'rxjs/Observable'; import { HttpClient } from '@angular/common/http'; @Injectable() export class SocketService { private socket; private serverUrl = 'http://your-node-server-url:3000'; constructor(private http: HttpClient) { this.socket = io(this.serverUrl); } registerUser(userId: number): void { this.socket.emit('register-user', userId); } onBorrowRequest(): Observable<any> { return new Observable(observer => { this.socket.on('borrow-request', (alert) => observer.next(alert)); }); } onUnreadNotifications(): Observable<any> { return new Observable(observer => { this.socket.on('unread-notifications', (alerts) => observer.next(alerts)); }); } markNotificationRead(notificationId: number): void { this.socket.emit('mark-read', notificationId); } getUserNotifications(userId: number): Observable<any> { return this.http.get(`${this.serverUrl}/api/notifications/${userId}`); } submitBorrowRequest(itemId: number, borrowerId: number): Observable<any> { return this.http.post(`${this.serverUrl}/api/borrow`, { itemId, borrowerId }); } }
You can then create a notifications component to display all alerts and mark them as read, following similar patterns to the previous example.
内容的提问来源于stack exchange,提问作者Tayssir ben salem

