You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:23:08