本文记录了一些 GoSIP 获取 SharePoint 的示例,相对来说重点是获取历史版本,虽说 GoSIP 尽量做到了开箱即用,但是对开发者来说,本身对 SharePoint 服务也需要有所了解,并熟悉其 REST API 风格。
如果你需要通过 Golang 程序读写 SharePoint 上的文件,相信本篇笔记会对你有所帮助。
环境说明
SharePoint Root Site
在「文档」类别的根目录下有一个 test.txt 文件
SharePoint 子网站
在 Root Site 的「网站内容」类别下,点击「新建 - 子网站」进行创建
标题为「文档中心」,路径为 subtest
同样,创建一个测试文件,名为 subsite_test.txt
获取文件的路径
勾选文件,在右侧面板可以找到文件的路径
得到文件的路径
https://mycompany.sharepoint.com/sites/appsite/Shared%20Documents/test.txt
SubSite 同理,获取 subsite_test.txt 的路径为
https://mycompany.sharepoint.com/sites/appsite/subsite/Documents/subsite_test.txt
这里可以看到一些区别,根站点的「文档」应为对应 “Shared Documents”,子站点则为 “Documents”
Golang GoSIP 库简介
GoSIP 地址:GoSIP - SharePoint SDK for Go
微软的 REST API 很难用现代化的接口调用方式去理解,看 SharePoint 分散在不同地方的文档糟心的很,还好有 GoSIP 库,它对 SharePoint API 做了封装,使用体验良好。
官方文档清晰易懂,以下众多示例也是 GoSIP 使用的 Examples,GoSIP 支持多种认证方式,以下示例均使用的用户名/密码认证方式进行的验证(saml)
代码示例(1)用户名密码认证
认证并获取网站标题
auth.go
package main
import (
"fmt"
"github.com/koltyakov/gosip"
"github.com/koltyakov/gosip/api"
strategy "github.com/koltyakov/gosip/auth/saml"
"log"
)
func main() {
authCfg := &strategy.AuthCnfg{
SiteURL: "https://mycompany.sharepoint.com/sites/appsite",
Username: "<user-user-email>",
Password: "<user-user-password>",
}
client := &gosip.SPClient{AuthCnfg: authCfg}
sp := api.NewSP(client)
resp, err := sp.Web().Get()
if err != nil {
log.Fatal(err)
}
fmt.Printf("网站标题:%s\n", resp.Data().Title)
}
输出
AppSite
示例代码(2)获取文件信息
package main
import (
"fmt"
"github.com/koltyakov/gosip"
"github.com/koltyakov/gosip/api"
strategy "github.com/koltyakov/gosip/auth/saml"
"log"
)
func main() {
authCfg := &strategy.AuthCnfg{
SiteURL: "https://mycompany.sharepoint.com/sites/appsite",
Username: "<user-user-email>",
Password: "<user-user-password>",
}
client := &gosip.SPClient{AuthCnfg: authCfg}
sp := api.NewSP(client)
fileURL := "Shared Documents/test.txt"
// 获取文件信息
fileResp, err := sp.Web().GetFileByPath(fileURL).Get()
if err != nil {
log.Fatal(err)
}
fmt.Printf("FileName: %v\n", fileResp.Data().Name)
fmt.Printf("FileSize: %d\n", fileResp.Data().Length)
fmt.Printf("UniqueID: %s\n", fileResp.Data().UniqueID)
fmt.Printf("MajorVersion: %d\n", fileResp.Data().MajorVersion)
fmt.Printf("MinorVersion: %d\n", fileResp.Data().MinorVersion)
fmt.Printf("ServerRelativeURL: %s\n", fileResp.Data().ServerRelativeURL)
fmt.Printf("TimeCreated: %s\n", fileResp.Data().TimeCreated)
fmt.Printf("TimeLastModified: %s\n", fileResp.Data().TimeLastModified)
}
输出
FileName: test.txt
FileSize: 15
UniqueID: da16b19f-ff22-4646-9ed2-818dda806d9f
MajorVersion: 3
MinorVersion: 0
ServerRelativeURL: /sites/appsite/Shared Documents/test.txt
TimeCreated: 2024-01-24 08:19:07 +0000 UTC
TimeLastModified: 2024-01-30 07:55:06 +0000 UTC
从输出的结果可以看到,传入的路径是 Shared Documents/test.txt,文件的 ServerRelativeURL 是 /sites/appsite/Shared Documents/test.txt,SDK 对请求时的路径做了自动补全
另外对于子站点的文件请求,将 fileURL 变量修改为如下地址,子站点的请求均需携带。
fileURL := "subsite/Documents/subsite_test.txt"
示例代码(3)下载文件
package main
import (
"github.com/koltyakov/gosip"
"github.com/koltyakov/gosip/api"
"log"
"os"
strategy "github.com/koltyakov/gosip/auth/saml"
)
func main() {
authCfg := &strategy.AuthCnfg{
SiteURL: "https://mycompany.sharepoint.com/sites/appsite",
Username: "<user-user-email>",
Password: "<user-user-password>",
}
client := &gosip.SPClient{AuthCnfg: authCfg}
sp := api.NewSP(client)
fileURL := "Shared Documents/test.txt"
data, err := sp.Web().GetFile(fileURL).Download()
if err != nil {
log.Fatal(err)
}
file, err := os.Create("./test.txt")
if err != nil {
log.Fatalf("unable to create a file: %v\n", err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
log.Fatalf("unable to write to file: %v\n", err)
}
file.Sync()
}
示例代码(4)下载文件的历史版本
文件每次编辑,都会产生历史版本
download-version.go
package main
import (
"fmt"
"github.com/koltyakov/gosip"
"github.com/koltyakov/gosip/api"
strategy "github.com/koltyakov/gosip/auth/saml"
"log"
)
func majorVersionToVersionId(major int) int {
return 512 * major
}
func main() {
authCfg := &strategy.AuthCnfg{
SiteURL: "https://mycompany.sharepoint.com/sites/appsite",
Username: "<user-user-email>",
Password: "<user-user-password>",
}
client := &gosip.SPClient{AuthCnfg: authCfg}
spClient := api.NewHTTPClient(client)
fileURL := "test.txt"
versionId := majorVersionToVersionId(2)
endpoint := fmt.Sprintf("%s/_api/web/GetFileByServerRelativeUrl('/sites/appsite/Shared Documents/%s')/Versions/GetById(%d)/$value", authCfg.GetSiteURL(), fileURL, versionId)
data, err := spClient.Get(endpoint, nil)
if err != nil {
log.Fatal()
}
fmt.Println(string(data))
}
这里是用的是 GoSIP 提供的更加底层的 HTTPClient,请求自定义接口路径,如果想要用文件 ID 定位问文件,可以修改代码片段
fileId := "da16b19f-ff22-4646-9ed2-818dda806d9f"
versionId := majorVersionToVersionId(2)
endpoint := fmt.Sprintf("%s/_api/web/GetFileById('%s')/Versions/GetById(%d)/$value", authCfg.GetSiteURL(), fileId, versionId)
另外 REST API 也支持通过原始路径请求历史文件,上示例的 endpoint 替换即可
endpoint = fmt.Sprintf("%s/_vti_history/%d/Shared Documents/test.txt", authCfg.GetSiteURL(), versionId)
总结一下,就是以下 API 地址从获取历史文件内容来看都是等价的:
# 通过相对路径
https://mycompany.sharepoint.com/sites/appsite/_api/web/GetFileByServerRelativeUrl('/sites/appsite/Shared Documents/test.txt')/Versions/GetById(1024)/$value
# 通过文件ID
https://mycompany.sharepoint.com/sites/appsite/_api/web/GetFileById('da16b19f-ff22-4646-9ed2-818dda806d9f')/Versions/GetById(1024)/$value
# 通过历史版本位置
https://mycompany.sharepoint.com/sites/appsite/_vti_history/1024/Shared Documents/test.txt
示例代码(5)获取文件及文件夹列表
package main
import (
"fmt"
"github.com/koltyakov/gosip"
"github.com/koltyakov/gosip/api"
strategy "github.com/koltyakov/gosip/auth/saml"
"log"
)
func main() {
authCfg := &strategy.AuthCnfg{
SiteURL: "https://mycompany.sharepoint.com/sites/appsite",
Username: "<user-user-email>",
Password: "<user-user-password>",
}
client := &gosip.SPClient{AuthCnfg: authCfg}
sp := api.NewSP(client)
// 获取并遍历所有文件
folderURL := "Shared Documents/"
items, err := sp.Web().GetFolder(folderURL).Files().Get()
if err != nil {
log.Fatalf("无法获取文件: %v\n", err)
}
for _, file := range items.Data() {
fmt.Printf(file.Data().Name)
}
// 获取并遍历所有子文件夹
folders, err := sp.Web().GetFolder(folderURL).Folders().Get()
if err != nil {
log.Fatalf("无法获取子文件夹: %v\n", err)
}
for _, folder := range folders.Data() {
fmt.Printf(folder.Data().Name)
}
}
版本号规则的补充
文件既可以有用整数(12.0)表示的主要版本(Major Version),也可以有用十进制数(12.3)表示的次要版本(Minor Version)。
如果将库配置为支持「签入/签出」,则用户对签出文档执行的每次更改都将创建次要版本。
如果直接在线编辑文件,那么将创建主要版本。
获取历史文件时的文件 ID 计算公式:VersionID = MajorVersion * 512 + MinorVersion
不建议使用 SharePoint 的 SubSite
推荐使用 Hub Site 替代 SubSite 的组织方式,因为 Hub Site 组织更加的清晰,易于扩展。
这不是本文的重点,故不再发散介绍。
SharePoint 请求 Proxy
推荐这个库:https://www.npmjs.com/package/sp-rest-proxy
如果需要适配较多的 SharePoint 原始 API,建议使用这个工具提供的页面便于调试,前端开发的时候也会更加的便捷。
其它补充
自己的网页嵌入 SharePoint 的文件地址,txt 文件能够展示,xlsx 则不展示
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SharePoint XLSX 文件嵌入示例</title>
</head>
<body>
<h1>SharePoint XLSX 文件嵌入示例</h1>
<iframe src="https://mycompany.sharepoint.com/sites/appsite/_layouts/15/embed.aspx?UniqueId=d09c1141-5111-4a00-97de-1cc6c16c501b" width="640" height="360" frameborder="0" scrolling="no" allowfullscreen title="test-xlsx.xlsx"></iframe>
<hr/>
<br/>
<iframe src="https://mycompany.sharepoint.com/sites/appsite/_layouts/15/embed.aspx?UniqueId=da16b19f-ff22-4646-9ed2-818dda806d9f" width="640" height="360" frameborder="0" scrolling="no" allowfullscreen title="test.txt"></iframe>
</body>
</html>
效果
看控制台报错是因为找不到 “https://res-1.cdn.office.net/files/odsp-web-prod_2024-01-19.010/monaco-worker.js” 这个文件,看起来是编辑器没有加载完全的原因,这倒不是重点,先这样。
参考
- GoSIP SharePoint SDK
- GetFolderByServerRelativeUrl Rest API return Server relative urls must start with SPWeb.ServerRelativeUrl
- Sharepoint REST API - GetFileByServerRelativeUrl issue
- Token doesn't allow to access version history of files
- Working with SharePoint site resources
- Versioning in SharePoint