用Golang把视频塞进Office 365?这事儿我琢磨了一整个周末

事情是这样的,上周五我老婆突然发消息说,公司要她把一段培训视频上传到Office365的SharePoint上,但她试了好几次都失败—...

事情是这样的,上周五我老婆突然发消息说,公司要她把一段培训视频上传到Office 365的SharePoint上,但她试了好几次都失败——要么文件太大,要么上传到一半就断了,她气呼呼地说:“你不是写代码的吗?搞不定这个?”于是我硬着头皮接下了这个活儿,结果发现,这事儿如果用Go语言来做,不仅能搞定,还能批量处理、自动重试、甚至加进度条,今天就把我踩过的坑和最终方案写下来。

为什么选Go?因为我另外几个语言试了更坑

一开始我想用Python写,毕竟requests库谁都会,但实际跑起来问题不少:内存占用高、大文件上传动不动就OOM(Out of Memory,内存溢出),而且Office 365的认证流程在Python里折腾起来像个迷宫,后来换Go,两小时就通了,Go的并发模型天然适合分片上传,编译出来的二进制文件丢到服务器上就能跑,连Python环境都不用装。

你可能觉得:“我直接用浏览器上传不就行了?”可现实是,企业级的Office 365环境经常限制单个文件大小(比如默认250MB),而且网络不稳定时浏览器上传就是一场噩梦,用Go写一个上传工具,可以切片、断点续传、还能搭配定时任务自动处理。

第一步:搞定认证——这步卡了我最久

Office 365的API用的是Microsoft Graph,认证方式推荐OAuth 2.0客户端凭证模式(适合后台服务),你需要先去Azure门户注册一个应用:

  1. 登录 Azure Portal
  2. 搜索“App registrations”,点击“New registration”
  3. 名称随便填,VideoUploader”
  4. 重定向URL先不管,因为我们用客户端凭证
  5. 记下 Client ID(客户端ID)和 Tenant ID(租户ID)
  6. 在“Certificates & secrets”里生成一个 Client Secret(客户端密码)

拿到这些信息后,Go代码里就可以这样获取Access Token(访问令牌):

package main
import (
    "context"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
)
func getAccessToken(clientID, clientSecret, tenantID string) (string, error) {
    tokenURL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID)
    data := url.Values{
        "client_id":     {clientID},
        "scope":         {"https://graph.microsoft.com/.default"},
        "client_secret": {clientSecret},
        "grant_type":    {"client_credentials"},
    }
    resp, err := http.PostForm(tokenURL, data)
    if err != nil {
        return "", fmt.Errorf("请求token失败: %v", err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    // 简单解析,生产环境建议用encoding/json
    // 这里假设你会解析JSON获取access_token字段
    return parseToken(body), nil
}

小提示:Office 365的Access Token有效期通常是一小时,所以记得在代码里做缓存,别每次上传都重新请求——我第一版就没注意,结果每上传一个分片就刷新一次Token,白白浪费了十几分钟。

第二步:创建上传会话——Office 365的大招

Microsoft Graph提供了一个叫 “创建上传会话” 的API,专门处理大文件,原理是先告诉服务器“我要上传一个视频”,服务器返回一个上传URL,然后你把视频切成一块块往上怼。

这里的关键是API端点,假设你要把视频上传到当前用户的OneDrive根目录:

POST https://graph.microsoft.com/v1.0/me/drive/root:/文件名.xlsx:/createUploadSession

或者上传到SharePoint的特定文档库:

POST https://graph.microsoft.com/v1.0/sites/{site-id}/drive/items/{parent-id}:/文件名.mp4:/createUploadSession

请求头里需要带上 Authorization: Bearer {你的AccessToken},请求体是JSON,指定文件大小和名称:

{
    "@microsoft.graph.conflictBehavior": "rename",
    "fileSize": 123456789,
    "name": "培训视频.mp4"
}

Go代码实现:

func createUploadSession(accessToken, fileName string, fileSize int64) (string, error) {
    url := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/drive/root:/%s:/createUploadSession", fileName)
    payload := fmt.Sprintf(`{
        "@microsoft.graph.conflictBehavior": "rename",
        "fileSize": %d,
        "name": "%s"
    }`, fileSize, fileName)
    req, _ := http.NewRequest("POST", url, strings.NewReader(payload))
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    // 解析响应获取uploadUrl
    return extractUploadURL(body), nil
}

踩坑记录:我第一次写的时候没设置 @microsoft.graph.conflictBehavior,结果同名文件直接报错,设置成 "rename" 后,服务器会自动把新文件改成“文件名 1.mp4”,虽然丑但不至于失败。

第三步:切片上传——Go的goroutine派上用场了

Office 365要求每个分片大小至少327680字节(320KB),最大60MB,我推荐切成10MB一块,既不会太碎,又能容忍网络波动。

用Golang把视频塞进Office 365?这事儿我琢磨了一整个周末

核心逻辑很简单:

  1. 打开视频文件
  2. 按10MB读取一块
  3. 放到goroutine里并发上传
  4. 记录已上传的分片,万一失败能重试
func uploadFragments(uploadURL string, filePath string, fragmentSize int64) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()
    fileInfo, _ := file.Stat()
    totalSize := fileInfo.Size()
    var fragmentIndex int64 = 0
    for fragmentIndex * fragmentSize < totalSize {
        start := fragmentIndex * fragmentSize
        end := start + fragmentSize - 1
        if end >= totalSize {
            end = totalSize - 1
        }
        fragment := make([]byte, fragmentSize)
        n, _ := file.ReadAt(fragment, start)
        fragment = fragment[:n]
        // 并发上传每个分片
        go uploadFragment(uploadURL, fragment, start, end, totalSize)
        fragmentIndex++
    }
    return nil
}

每个分片上传说白了就是一个PUT请求,但注意要在请求头里指定 Content-Range

Content-Range: bytes 0-10485759/104857600

完整的Go实现:

func uploadFragment(uploadURL string, data []byte, start, end, totalSize int64) error {
    req, _ := http.NewRequest("PUT", uploadURL, bytes.NewReader(data))
    contentRange := fmt.Sprintf("bytes %d-%d/%d", start, end, totalSize)
    req.Header.Set("Content-Range", contentRange)
    req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data)))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("分片 %d-%d 上传失败: %v", start, end, err)
    }
    defer resp.Body.Close()
    // 201表示成功,其他状态码需要处理
    if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
        body, _ := ioutil.ReadAll(resp.Body)
        return fmt.Errorf("分片上传返回异常状态码 %d: %s", resp.StatusCode, string(body))
    }
    return nil
}

真实感言:我第一次跑的时候,100个并发把公司网络搞崩了,后来加了个semaphore(信号量)限制一下,比如同时最多上传5个分片,Go的chan做这个特别顺手:

sem := make(chan struct{}, 5) // 同时最多5个并发
// 上传时:
sem <- struct{}{}
go func() {
    defer func() { <-sem }()
    // 执行上传
}()

第四步:处理大文件——别让内存爆炸

视频文件动不动就几个GB,你不可能一次性读到内存里,上面用的是 ReadAt 随机读取文件,但更好的方式是直接用 io.SectionReader,Go标准库自带的,特别好用:

func uploadLargeFile(uploadURL, filePath string) error {
    file, _ := os.Open(filePath)
    stat, _ := file.Stat()
    totalSize := stat.Size()
    const fragmentSize = 10 * 1024 * 1024 // 10MB
    for start := int64(0); start < totalSize; start += fragmentSize {
        end := start + fragmentSize - 1
        if end >= totalSize {
            end = totalSize - 1
        }
        reader := io.NewSectionReader(file, start, fragmentSize)
        // 直接用reader作为请求体
        req, _ := http.NewRequest("PUT", uploadURL, reader)
        // ... 设置Content-Range, 然后发送
    }
    return nil
}

这样Go的HTTP客户端会自动读取那一段文件内容,不会在内存里保持完整拷贝。

第五步:断点续传和错误处理——现实世界不是实验室

网络上传必然会有失败,所以上传会话API支持查询已上传的分片列表,在上传前(或者上传失败后),先发一个GET请求到uploadURL,服务器会返回一个JSON,里面有个 nextExpectedRanges 字段,告诉你哪些分片还没收到。

func getMissingRanges(uploadURL string) ([]string, error) {
    resp, _ := http.Get(uploadURL)
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    // 解析nextExpectedRanges
    return parseRanges(body), nil
}

有了这个,你就可以只补传缺失的部分,不用从头再来,我老婆每次上传到99%断了,就是因为没做断点续传,用Go写好这个逻辑后,再大的文件也能慢慢啃完。

第六步:最终调用——完整示例

把这些拼起来,main函数大概长这样:

func main() {
    clientID := "你的ClientID"
    clientSecret := "你的ClientSecret"
    tenantID := "你的TenantID"
    filePath := "C:\\视频\\培训.mp4"
    token, err := getAccessToken(clientID, clientSecret, tenantID)
    if err != nil {
        log.Fatalf("获取Token失败: %v", err)
    }
    uploadURL, err := createUploadSession(token, "培训视频.mp4", getFileSize(filePath))
    if err != nil {
        log.Fatalf("创建上传会话失败: %v", err)
    }
    err = uploadLargeFile(uploadURL, filePath)
    if err != nil {
        log.Fatalf("上传失败: %v", err)
    }
    fmt.Println("上传完成!去Office 365看看吧。")
}

还有几个没说的小问题

  • 文件名编码:中文文件名在Office 365里可能出现乱码,建议上传前用 url.PathEscape 处理。
  • 视频太长超时:设置HTTP客户端的Timeout要足够大,Timeout: 0 表示不限时,但最好设个30分钟。
  • 流媒体优化:如果你上传的是视频用于流媒体播放,上传完成后可以调用 updateFileMetadata API设置Content-Type为 video/mp4,这样在线播放会更流畅。

我后来把这套代码打包成一个命令行工具,老婆现在每周五自动上传视频文件到SharePoint,再也不用半夜问我“为什么又断了”,不过她不知道的是,我偷偷在代码里加了个彩蛋——每次上传成功都会打印一个🎉表情,她看到了说:“你写代码还挺浪漫的。”我心想,浪漫不重要,能把视频稳稳当当塞进Office 365才是真本事。

本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/nengyuan/933.html

(3)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-07-01

    我是be365的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-07-01

    希望本篇文章《用Golang把视频塞进Office 365?这事儿我琢磨了一整个周末》能对你有所帮助!

  • kyadmin
    kyadmin 2026-07-01

    本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名

  • kyadmin
    kyadmin 2026-07-01

    本文概览:事情是这样的,上周五我老婆突然发消息说,公司要她把一段培训视频上传到Office365的SharePoint上,但她试了好几次都失败—...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们