基于JSP和Spring的Web应用如何实现FCM推送通知?
实现Spring+JSP Web应用的FCM推送通知完整流程
我帮你梳理一下从用户授权到发送推送的全流程,结合Spring后端和JSP前端的实现,其实和Facebook、Amazon这类网站的逻辑本质是一致的——都是前端处理浏览器授权、获取FCM令牌,后端存储令牌并调用FCM API发送推送。
一、前端(JSP):搞定用户授权与令牌获取
Web端的FCM推送依赖浏览器的通知API和Firebase的Web SDK,你需要在JSP页面里完成以下几步:
1. 引入Firebase SDK并初始化
先从Firebase控制台获取你的项目配置(apiKey、projectId这些),然后在JSP里引入SDK并初始化:
<!-- 放在JSP页面的<body>末尾,避免阻塞页面加载 --> <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js"></script> <script> // 替换成你自己的Firebase项目配置 const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID" }; // 初始化Firebase应用 firebase.initializeApp(firebaseConfig); const messaging = firebase.messaging(); </script>
2. 请求用户通知授权
浏览器会弹出授权弹窗,只有用户允许后才能获取FCM令牌。你可以在页面加载时触发,或者绑定到一个“开启通知”按钮:
async function requestNotificationPermission() { const permission = await Notification.requestPermission(); if (permission === 'granted') { console.log('用户同意接收通知'); // VAPID公钥从Firebase控制台的"云消息传递"页面生成 const token = await messaging.getToken({ vapidKey: "YOUR_VAPID_PUBLIC_KEY" }); // 把令牌传给Spring后端存储,关联当前登录用户 fetch('/save-fcm-token', { method: 'POST', headers: { 'Content-Type': 'application/json', // Spring CSRF防护需要的令牌,记得在JSP头部加<meta name="_csrf" content="${_csrf.token}"> 'X-CSRF-TOKEN': document.querySelector('meta[name="_csrf"]').content }, body: JSON.stringify({ userId: '${currentUser.id}', // 替换成当前登录用户的ID(从JSPEL获取) fcmToken: token }) }) .then(res => res.json()) .then(data => console.log('令牌已存储:', data)) .catch(err => console.error('存储令牌失败:', err)); } else { console.log('用户拒绝了通知授权'); } } // 页面加载时自动请求授权(也可绑定到按钮点击事件) window.addEventListener('load', requestNotificationPermission);
3. 处理令牌刷新
FCM令牌可能会过期或刷新,要监听刷新事件自动更新后端存储的令牌:
messaging.onTokenRefresh(() => { messaging.getToken({ vapidKey: "YOUR_VAPID_PUBLIC_KEY" }) .then(refreshedToken => { console.log('令牌已刷新,更新后端存储'); // 调用后端更新接口 fetch('/update-fcm-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="_csrf"]').content }, body: JSON.stringify({ userId: '${currentUser.id}', fcmToken: refreshedToken }) }); }) .catch(err => console.error('获取刷新令牌失败:', err)); });
4. 添加Service Worker处理后台推送
当用户不在你的页面时,需要Service Worker接收推送并显示通知。在项目根目录创建firebase-messaging-sw.js文件:
importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js'); importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js'); // 同样的Firebase配置 const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID" }; firebase.initializeApp(firebaseConfig); const messaging = firebase.messaging(); // 处理后台推送消息 messaging.onBackgroundMessage(payload => { const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: '/static/images/notification-icon.png' // 替换成你的通知图标路径 }; self.registration.showNotification(notificationTitle, notificationOptions); });
二、后端(Spring):存储令牌与发送推送
Spring后端核心是用Firebase Admin SDK发送推送,同时管理用户的FCM令牌:
1. 引入Firebase Admin依赖
在pom.xml中添加依赖:
<dependency> <groupId>com.google.firebase</groupId> <artifactId>firebase-admin</artifactId> <version>9.2.0</version> </dependency>
2. 初始化Firebase Admin SDK
从Firebase控制台下载服务账号密钥(JSON文件),放到src/main/resources目录下,写配置类初始化:
import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.io.FileInputStream; import java.io.IOException; @Configuration public class FirebaseConfig { @PostConstruct public void initFirebase() throws IOException { // 加载服务账号密钥 FileInputStream serviceAccount = new FileInputStream("src/main/resources/firebase-service-account.json"); FirebaseOptions options = FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .build(); // 避免重复初始化 if (FirebaseApp.getApps().isEmpty()) { FirebaseApp.initializeApp(options); } } }
3. 存储与管理FCM令牌
先创建实体类关联用户和令牌:
import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name = "user_fcm_tokens") public class UserFcmToken { @Id private String token; @ManyToOne private User user; // 假设你已有User实体类 // 构造函数、getter、setter省略 }
再创建JPA仓库接口:
import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserFcmTokenRepository extends JpaRepository<UserFcmToken, String> { Optional<UserFcmToken> findByUser(User user); }
4. 编写推送服务类
创建服务类处理令牌存储和推送发送:
import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class FcmNotificationService { private final FirebaseMessaging firebaseMessaging; private final UserFcmTokenRepository tokenRepository; private final UserRepository userRepository; // 假设你已有UserRepository @Autowired public FcmNotificationService(FirebaseMessaging firebaseMessaging, UserFcmTokenRepository tokenRepository, UserRepository userRepository) { this.firebaseMessaging = firebaseMessaging; this.tokenRepository = tokenRepository; this.userRepository = userRepository; } // 存储或更新用户的FCM令牌 public void saveOrUpdateToken(String userId, String fcmToken) { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("用户不存在")); UserFcmToken existingToken = tokenRepository.findByUser(user).orElse(null); if (existingToken != null) { existingToken.setToken(fcmToken); } else { UserFcmToken newToken = new UserFcmToken(); newToken.setToken(fcmToken); newToken.setUser(user); tokenRepository.save(newToken); } } // 向单个用户发送推送通知 public void sendNotificationToUser(String userId, String title, String body) throws Exception { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("用户不存在")); UserFcmToken token = tokenRepository.findByUser(user) .orElseThrow(() -> new RuntimeException("用户未注册FCM令牌")); // 构建通知内容 Notification notification = Notification.builder() .setTitle(title) .setBody(body) .build(); // 构建推送消息 Message message = Message.builder() .setToken(token.getToken()) .setNotification(notification) .build(); // 发送推送 firebaseMessaging.send(message); } }
5. 编写控制器接收前端令牌请求
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class FcmTokenController { private final FcmNotificationService fcmService; @Autowired public FcmTokenController(FcmNotificationService fcmService) { this.fcmService = fcmService; } @PostMapping("/save-fcm-token") public ResponseEntity<String> saveToken(@RequestBody FcmTokenRequest request) { try { fcmService.saveOrUpdateToken(request.getUserId(), request.getFcmToken()); return ResponseEntity.ok("令牌存储成功"); } catch (Exception e) { return ResponseEntity.badRequest().body("存储失败: " + e.getMessage()); } } @PostMapping("/update-fcm-token") public ResponseEntity<String> updateToken(@RequestBody FcmTokenRequest request) { return saveToken(request); // 逻辑和保存一致,直接复用 } // 内部类用于接收请求参数 static class FcmTokenRequest { private String userId; private String fcmToken; // getter、setter省略 } }
三、关键注意事项
- HTTPS要求:FCM Web推送仅支持HTTPS环境(localhost开发时浏览器会豁免这个限制),生产环境必须部署在HTTPS服务器上。
- VAPID密钥:必须在Firebase控制台的"云消息传递"页面生成公钥,前端获取令牌时要用到。
- Service Worker路径:
firebase-messaging-sw.js必须放在项目根目录,否则浏览器无法正确注册。 - 令牌清理:当用户注销账号或令牌失效时,要及时从数据库中移除无效令牌,避免发送失败。
内容的提问来源于stack exchange,提问作者Abhishek Tripathi
相关产品推荐
相关产品推荐

