使用Go通过服务账户下载Google Calendar事件附件遇登录重定向问题
问题:服务账户下载Google Calendar事件附件时遭遇登录重定向
我一直使用Google Calendar跟踪事件,需要获取这些事件并下载关联的附件,当前采用Go语言开发,也接受其他可行方案。
已尝试的操作步骤
使用账号A在网页版Google Calendar中创建带有Google Drive附件的事件

读取账号B(Google服务账户)的JSON凭证文件,并创建
google.HTTPClient
凭证文件内容:
{ "type": "service_account", "project_id": "GoogleProject", "private_key_id": "...", "private_key": "-----BEGIN PRIVATE KEY-----...\n-----END PRIVATE KEY-----\n", "client_email": "ServiceAccount@GoogleProject.iam.gserviceaccount.com", "client_id": "...", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ServiceAccount%40GoogleProject.iam.gserviceaccount.com", "universe_domain": "googleapis.com" }
创建客户端的Go代码:
import ( "context" "log" "os" "time" gCalendar "google.golang.org/api/calendar/v3" gDrive "google.golang.org/api/drive/v3" "google.golang.org/api/option" "golang.org/x/oauth2/jwt" ) var googleCalendarID = "..." // 目标日历ID func main() { googleCredentialsJson, err := os.ReadFile("google_application_credentials.json") if err != nil || len(googleCredentialsJson) == 0 { log.Fatalf("读取凭证文件失败: %v", err) } googleJWTConf, err := jwt.ConfigFromJSON(googleCredentialsJson, gCalendar.CalendarScope, gCalendar.CalendarEventsScope, gDrive.DriveScope, "https://www.googleapis.com/auth/cloud-platform", ) if err != nil { log.Fatalf("加载Google凭证失败: %+v", err) } googleCtx := context.Background() googleHTTPClient := googleJWTConf.Client(googleCtx) }
- 通过
google.HTTPClient获取日历事件:
googleCalendarService, err := gCalendar.NewService(googleCtx, option.WithHTTPClient(googleHTTPClient)) if err != nil { log.Fatalf("初始化Calendar服务失败: %+v", err) } calEvents, err := googleCalendarService.Events. List(googleCalendarID). TimeMin(time.Now().Format(time.RFC3339)). TimeMax(time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339)). Context(googleCtx).Do() if err != nil { log.Fatalf("获取日历事件失败: %v", err) }
- 尝试使用日历事件响应中的
FileUrl下载附件:
import ( "net/http" "net/http/httputil" ) if len(calEvents.Items) > 0 { calEvent := calEvents.Items[0] if len(calEvent.Attachments) > 0 { rFileUrl := calEvent.Attachments[0].FileUrl request, err := http.NewRequestWithContext(googleCtx, "GET", rFileUrl, nil) if err != nil { log.Fatalf("创建请求失败: %v", err) } response, err := googleHTTPClient.Do(request) if err != nil { log.Fatalf("请求文件失败: %v", err) } defer response.Body.Close() if response.StatusCode != http.StatusOK || response.Header.Get("X-Frame-Options") == "DENY" { tmpDump, _ := httputil.DumpResponse(response, false) log.Errorf("响应异常: %s", string(tmpDump)) log.Fatalf("响应状态码: %v", response.StatusCode) } } }
遇到的问题
每次请求都会被重定向到Google登录页面,即使文件设置为公开也是如此(但未登录Google的浏览器会话可以通过同一URL访问该文件)。现有资料大多围绕个人Drive文件下载,Calendar附件相关内容极少,也不清楚附件的具体存储机制,看起来文件加载前存在多次重定向。
解决方案
问题根源在于直接使用FileUrl请求——该链接是面向普通用户的网页地址,而非API调用端点,服务账户的OAuth凭证无法直接用于这类网页请求。正确的做法是通过Google Drive API,利用附件的FileId来下载文件:
修正后的下载代码
import "io" // 在获取到calEvents之后执行 if len(calEvents.Items) > 0 { calEvent := calEvents.Items[0] if len(calEvent.Attachments) > 0 { fileId := calEvent.Attachments[0].FileId // 提取文件ID,而非FileUrl // 初始化Drive服务 driveService, err := gDrive.NewService(googleCtx, option.WithHTTPClient(googleHTTPClient)) if err != nil { log.Fatalf("初始化Drive服务失败: %v", err) } // 请求下载文件 resp, err := driveService.Files.Get(fileId).Download() if err != nil { log.Fatalf("下载文件失败: %v", err) } defer resp.Body.Close() // 将文件内容保存到本地 outFile, err := os.Create("downloaded_attachment") // 根据文件类型修改后缀,如.pdf/.docx if err != nil { log.Fatalf("创建本地文件失败: %v", err) } defer outFile.Close() _, err = io.Copy(outFile, resp.Body) if err != nil { log.Fatalf("保存文件内容失败: %v", err) } log.Println("附件下载成功") } }
关键注意事项
- 权限配置:必须将目标Drive文件共享给服务账户的邮箱(即凭证中的
client_email字段值),权限设置为「查看者」即可。 - API权限校验:确保JWT配置中包含
gDrive.DriveScope(你的代码已包含此配置,无需修改)。 - 文件ID的可靠性:Calendar事件附件的
FileId直接对应Drive中的文件ID,是API调用的唯一标识,比FileUrl更稳定可靠。
内容的提问来源于stack exchange,提问作者The Kaese
相关产品推荐
相关产品推荐

