Loading... ## 介绍 接收到一个业务需求,也就是当我在写 Confluence 文档的时候,经常遇到直接复制内容到其他平台的情况。那么这个时候如果我当前的内容里面带有图片,并且页面还有权限,那么就会出现在我其他页面访问的时候,这个图片内容粘贴过去的时候就是裂图,因为没有权限可以看 ## 先了解下 Confluence 的附件设计 Confluence 的附件有一个数字用来定义属性:文件自己的内容 id 和 文件所在页面中的内容 id 。这个意思是文件在逻辑上是属于内容的,通常内容又是属于空间(不是所有的内容都属于空间)。Confluence 中的空间文件,目录结构通常有 8 个级别,每一个目录级别的名字通常基于下面的算法。 | 层级 | 详解 | | ------- | ----------------------------------------------------------- | | 1 (top) | 总是为 'ver003' 这个定义为 Confluence 版本 3 的文件存储格式 | | 2 | 最小的 3 个数字,这个数字为 空间 id 取模 250 | | 3 | 下一个最小的 3 个数字,这个数字为 空间 id 取模 250 | | 4 | 完整的 空间 id | | 5 | 附件所附加在页面的 ID 取模 250 后的最小 3 个数字 | | 6 | 附件所附加在页面的 ID 取模 250 后的下一个最小 3 个数字 | | 7 | 附件所在页面的完整的 content id | | 8 | 附件所在完整的 content id | | 9 | 这个是文件,这个文件是按照版本号进行命名的,例如:1, 2, 6。 | ## 开始撸代码 ### 逻辑图 ![](https://minio-api.open.yantao.wiki:16666/blog-image/2024/10/05/6700d5bb58533.png) ## 前置条件 在计算路径前,需要知道一下信息 * 页面 PageID * 空间的 SpaceID * 附件的 ContentID ### 附件路径计算方法 ```go func confluencePath(pageId string, spaceId string, contentId string) string { attachmentPath := "ver003" for i := len(spaceId); i >= 0; i -= 3 { start := i - 3 if start < 0 { start = 0 } substr := spaceId[start:i] result := modulo(substr, 250) attachmentPath = attachmentPath + "/" + fmt.Sprintf("%d", result) if start == len(spaceId)-6 { attachmentPath = attachmentPath + "/" + spaceId break } } for i := len(pageId); i >= 0; i -= 3 { start := i - 3 if start < 0 { start = 0 } substr := pageId[start:i] result := modulo(substr, 250) attachmentPath = attachmentPath + "/" + fmt.Sprintf("%d", result) if start == len(pageId)-6 { attachmentPath = attachmentPath + "/" + pageId break } } return attachmentPath + "/" + contentId + "/1" } func modulo(substr string, module int) int { num := 0 for _, ch := range substr { num = num*10 + int(ch-'0') num %= module } return num } ``` ### 处理复制出来的格式 由于每个人复制的方式不同,可能造成附件的地址不同,因此需要做成可配置项,目前已知的是这几种 * pages/viewpage\\.action\\?pageId=(\\d+)&preview=/(\\d+)/(\\d+)/([^&]+) * download/attachments/(\\d+)/([^?]+) * download/thumbnails/(\\d+)/([^?]+) ``` for _, value := range config.URLPatterns { // 编译正则表达式 re := regexp.MustCompile(value.Info.Pattern) // 匹配正则表达式 matches := re.FindStringSubmatch(urlPath) var confluenceContent ConfluenceContent if len(matches) > value.Info.Fields.ContentId && len(matches) > value.Info.Fields.PageId { confluenceContent = query(value.Info.Fields.Type, matches[value.Info.Fields.ContentId], matches[value.Info.Fields.PageId]) confluenceAttachmentPath := confluencePath(confluenceContent.PAGEID, confluenceContent.SPACEID, confluenceContent.CONTENTID) if len(confluenceAttachmentPath) < 20 { http.Error(w, "地址不正确", http.StatusBadRequest) return } else { imageRender(w, r, config.SourceBegin+confluenceAttachmentPath) return } } } ``` ### 通过 ContentId 和附件名称获取计算需要的信息 ``` func query(values ...interface{}) ConfluenceContent { if len(values) < 2 { return ConfluenceContent{} } var runSql string if values[0] == "title" && values[1] != nil && values[2] != nil { runSql = fmt.Sprintf("SELECT `CONTENTID`,`TITLE`,`PAGEID`,`SPACEID` FROM CONTENT WHERE `CONTENTTYPE` = 'ATTACHMENT' AND `TITLE` = '%s' AND `PAGEID` = '%s'", values[1], values[2]) } if values[0] == "contentId" && values[1] != nil && values[2] != nil { runSql = fmt.Sprintf("SELECT `CONTENTID`,`TITLE`,`PAGEID`,`SPACEID` FROM CONTENT WHERE `CONTENTTYPE` = 'ATTACHMENT' AND `CONTENTID` = '%s' AND `PAGEID` = '%s'", values[1], values[2]) } if len(runSql) > 0 { rows, err := database.DB.Query(runSql) if err != nil { panic(err.Error()) } defer rows.Close() // Iterate through the results for rows.Next() { var confluenceContent ConfluenceContent if err := rows.Scan(&confluenceContent.CONTENTID, &confluenceContent.TITLE, &confluenceContent.PAGEID, &confluenceContent.SPACEID); err != nil { panic(err.Error()) } return confluenceContent } if err = rows.Err(); err != nil { panic(err.Error()) } } return ConfluenceContent{} } ``` ## 完结 有了这信息,后面的就是 HTTP 的处理了,完整的案例代码在下方: GitHub: [点击直达]([https://github.com/almightyYantao/Confluence-Proxy-Image](https://github.com/almightyYantao/Confluence-Proxy-Image)) 最后修改:2024 年 10 月 05 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏