事情是这样的,上周五我老婆突然发消息说,公司要她把一段培训视频上传到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门户注册一个应用:
- 登录 Azure Portal
- 搜索“App registrations”,点击“New registration”
- 名称随便填,VideoUploader”
- 重定向URL先不管,因为我们用客户端凭证
- 记下 Client ID(客户端ID)和 Tenant ID(租户ID)
- 在“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一块,既不会太碎,又能容忍网络波动。

核心逻辑很简单:
- 打开视频文件
- 按10MB读取一块
- 放到goroutine里并发上传
- 记录已上传的分片,万一失败能重试
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分钟。 - 流媒体优化:如果你上传的是视频用于流媒体播放,上传完成后可以调用
updateFileMetadataAPI设置Content-Type为video/mp4,这样在线播放会更流畅。
我后来把这套代码打包成一个命令行工具,老婆现在每周五自动上传视频文件到SharePoint,再也不用半夜问我“为什么又断了”,不过她不知道的是,我偷偷在代码里加了个彩蛋——每次上传成功都会打印一个🎉表情,她看到了说:“你写代码还挺浪漫的。”我心想,浪漫不重要,能把视频稳稳当当塞进Office 365才是真本事。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/nengyuan/933.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Golang把视频塞进Office 365?这事儿我琢磨了一整个周末》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:事情是这样的,上周五我老婆突然发消息说,公司要她把一段培训视频上传到Office365的SharePoint上,但她试了好几次都失败—...