在App Engine Standard Java8中用服务账号创建Google Drive推送通知频道
嘿,看你已经把基础配置都搞定了,接下来咱们一步步把这个Spreadsheet推送通知的流程跑通,适配你的App Engine Java8环境:
一、先写创建推送频道的核心逻辑
咱们用Google Drive API来实现文件变更的推送订阅,因为Sheets属于Drive生态的文件,直接用Drive的watch接口就能监控编辑操作。
首先得在你的Maven或Gradle项目里加依赖,比如Maven的话,在pom.xml里加这些:
<dependencies> <dependency> <groupId>com.google.apis</groupId> <artifactId>google-api-services-drive</artifactId> <version>v3-rev20240425-2.0.0</version> </dependency> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> <version>1.20.0</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
然后写创建频道的工具类,用服务账号认证:
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.model.Channel; import java.io.InputStream; import java.util.Collections; import java.util.UUID; public class DrivePushChannelManager { // 替换成你的服务账号密钥文件路径(放在项目resources下用类路径读取) private static final String SERVICE_ACCOUNT_KEY_PATH = "/your-service-account-key.json"; // 替换成要监控的Spreadsheet的File ID(从Spreadsheet URL里能拿到) private static final String TARGET_SPREADSHEET_ID = "your-spreadsheet-file-id"; // 替换成你的App Engine域名对应的回调地址 private static final String WEBHOOK_URL = "https://yyyy.appspot.com/webhook"; public static Channel createOrRenewChannel() throws Exception { // 初始化服务账号认证 InputStream keyStream = DrivePushChannelManager.class.getResourceAsStream(SERVICE_ACCOUNT_KEY_PATH); GoogleCredential credential = GoogleCredential.fromStream(keyStream) .createScoped(Collections.singleton(DriveScopes.DRIVE_FILE)); // 构建Drive服务实例 Drive driveService = new Drive.Builder(GoogleNetHttpTransport.newTrustedTransport(), GsonFactory.getDefaultInstance(), credential) .setApplicationName("Spreadsheet Edit Notifier") .build(); // 生成唯一的频道ID(续期时可复用或生成新ID,Google会自动处理旧频道) String channelId = "spreadsheet-notify-channel-" + UUID.randomUUID(); // 配置频道:有效期24小时,回调地址,类型为webhook Channel channel = new Channel() .setId(channelId) .setType("web_hook") .setAddress(WEBHOOK_URL) .setExpiration(System.currentTimeMillis() + 86400000); // 24小时后过期 // 订阅目标Spreadsheet的变更 return driveService.files().watch(TARGET_SPREADSHEET_ID, channel).execute(); } }
二、实现Webhook回调端点
Google会把变更通知发送到咱们配置的回调地址,所以得写一个Servlet来处理这个请求,还要先完成订阅验证(Google会发一个GET请求带challenge参数,咱们要返回这个参数才能完成订阅):
import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/webhook") public class DriveWebhookServlet extends HttpServlet { // 处理订阅验证的GET请求 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String challenge = req.getParameter("challenge"); if (challenge != null && !challenge.isEmpty()) { resp.getWriter().write(challenge); resp.setStatus(HttpServletResponse.SC_OK); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().write("Missing challenge parameter"); } } // 处理实际的变更通知POST请求 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { // 解析请求体,拿到变更的文件信息 String payload = req.getReader().lines().reduce("", String::concat); // 这里可以实现自己的通知逻辑:比如发邮件、推送到内部消息系统等 System.out.println("Received Spreadsheet edit notification: " + payload); // 必须返回200 OK,否则Google会重试通知 resp.setStatus(HttpServletResponse.SC_OK); } }
三、配置Cron任务自动续期频道
因为频道最长只能活24小时,所以咱们用App Engine的Cron每日跑一次续期逻辑,替换掉即将过期的频道。
首先在WEB-INF目录下创建cron.xml:
<?xml version="1.0" encoding="UTF-8"?> <cronentries> <cron> <!-- 对应续期Servlet的路径 --> <url>/renew-channel</url> <description>Daily renewal of Spreadsheet push notification channel</description> <!-- 每天凌晨0点执行,时区根据你的需求调整 --> <schedule>every day 00:00</schedule> <timezone>Asia/Shanghai</timezone> </cron> </cronentries>
然后写续期的Servlet,直接调用咱们之前写的创建频道方法就行(重新创建频道会自动替换旧的过期频道):
import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/renew-channel") public class ChannelRenewServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { DrivePushChannelManager.createOrRenewChannel(); resp.getWriter().write("Push channel renewed successfully"); resp.setStatus(HttpServletResponse.SC_OK); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("Failed to renew channel: " + e.getMessage()); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } }
四、最后检查几个关键权限配置
- 服务账号权限:别忘给服务账号加目标Spreadsheet的共享权限,至少给「查看者」权限,要是需要更详细的变更信息,给「编辑者」权限更稳妥。直接在Spreadsheet的共享设置里输入服务账号邮箱就行。
- API启用:去Cloud Console的API库,确保「Google Drive API」已经启用,不然调用watch接口会报错。
- 域名验证:你已经在Webmaster Tools验证了
yyyy.appspot.com,确保这个域名和你回调URL用的域名完全一致,Google只会把通知发送到已验证的域名下的地址。
内容的提问来源于stack exchange,提问作者Deviling Master
相关产品推荐
相关产品推荐

