前言
写这篇文章是因为最近项目中一个需求,因为业务是海外公司的,他们的文件存储是基于微软旗下的SharePoint,国内貌似用的很少 所以文档也是比较难找,在摸索了几小时后才成功完成这个需求,
操作是需要注意一下几点:
1.需要使用有管理权限的账号登录
2.添加权限后需要点击 代表 xxx 授予管理员权限同意 按钮
3.在使用GraphAPI 获取权限时可能会查询数据为空,需要检测url和权限是否已经正确配置
1. 前置条件
可访问 https://portal.azure.com 与 https://xxxx.sharepoint.com(sharepoint的访问url)。
管理员可授予 Graph 权限,并在需要时在 SharePoint 管理中心放行应用。
2. 在 Entra ID 注册应用
Azure Portal → Microsoft Entra ID → 应用注册 → 新注册。
记录 Application (client) ID、Directory (tenant) ID。
证书和密码 → 新建客户端密码,保存 client secret。
3. 配置 Graph 权限
API 权限 → 添加权限 → Microsoft Graph → 应用程序权限。
勾选至少 Sites.ReadWrite.All(可附加 Files.ReadWrite.All)。
点击 Grant admin consent 让管理员同意。(代表xxx授予管理员同意)



4. 获取 access_token
POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token Header: Content-Type: application/x-www-form-urlencoded Body:
grant_type=client_credentials
client_id=<CLIENT_ID>
client_secret=<CLIENT_SECRET>
scope=https://graph.microsoft.com/.default返回的 access_token 用于后续所有 Graph 请求。
5. 查 Site ID 与 Projects Drive ID(执行一次即可)
Graph Explorer 登录用户账号后调用:
GET https://graph.microsoft.com/v1.0/sites/rmkcivilconstruction.sharepoint.com:/sites/RMK-Portal?$select=id,webUrl记录完整 id → SITE_ID。
查 Projects 列表:
GET https://graph.microsoft.com/v1.0/sites/{SITE_ID}/lists?$filter=displayName eq 'Projects'&$select=id,displayName取 id → LIST_ID。
查 Projects 的 drive:
GET https://graph.microsoft.com/v1.0/sites/{SITE_ID}/lists/{LIST_ID}/drive返回的 id 即 DRIVE_ID,后续上传都使用它。
如 lists 查询不到,可改用 /drives 全量列出;若仍为空,检查 SharePoint 应用访问策略或权限。
6. Go 上传示例(PDF 小于 4MB)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type tokenResp struct {
AccessToken string `json:"access_token"`
}
func main() {
tenantID := os.Getenv("AZ_TENANT_ID")
clientID := os.Getenv("AZ_CLIENT_ID")
clientSecret := os.Getenv("AZ_CLIENT_SECRET")
driveID := os.Getenv("SP_DRIVE_ID")
localFile := "report-2025-11-26.pdf"
remotePath := "2025/11/report-2025-11-26.pdf"
token, err := fetchToken(tenantID, clientID, clientSecret)
if err != nil {
panic(err)
}
if err := uploadToDrive(driveID, remotePath, localFile, token); err != nil {
panic(err)
}
fmt.Println("上传成功")
}
func fetchToken(tenant, client, secret string) (string, error) {
url := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenant)
body := []byte(
"grant_type=client_credentials" +
"&client_id=" + client +
"&client_secret=" + secret +
"&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default",
)
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 300 {
return "", fmt.Errorf("token err %d: %s", resp.StatusCode, string(data))
}
var tr tokenResp
if err := json.Unmarshal(data, &tr); err != nil {
return "", err
}
return tr.AccessToken, nil
}
func uploadToDrive(driveID, remotePath, localPath, token string) error {
fileBytes, err := os.ReadFile(localPath)
if err != nil {
return err
}
url := fmt.Sprintf("https://graph.microsoft.com/v1.0/drives/%s/root:/%s:/content", driveID, remotePath)
req, _ := http.NewRequest("PUT", url, bytes.NewReader(fileBytes))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/pdf")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 300 {
return fmt.Errorf("upload err %d: %s", resp.StatusCode, string(data))
}
return nil
}7. 运行所需环境变量
AZ_TENANT_ID=<TENANT_ID>
AZ_CLIENT_ID=<CLIENT_ID>
AZ_CLIENT_SECRET=<CLIENT_SECRET>
SP_DRIVE_ID=<Projects drive id>8. 常见排查
/sites 或 /drives 返回空:检查 Graph 权限是否已 Admin consent,以及 SharePoint 是否对应用开放。
itemNotFound:Site/Drive ID 不完整或指向子站点。
大于 4MB 的文件:改用 createUploadSession 分片上传。