@@ -20,6 +20,7 @@ import (
2020 "github.com/TruthHun/BookStack/models/store"
2121 "github.com/TruthHun/BookStack/utils"
2222 "github.com/TruthHun/BookStack/utils/html2md"
23+ "github.com/TruthHun/gotil/filetil"
2324 "github.com/astaxie/beego"
2425 "github.com/astaxie/beego/logs"
2526 "github.com/astaxie/beego/orm"
@@ -814,190 +815,165 @@ func (m *Book) Copy(sourceBookIdentify string) (err error) {
814815// Export2Markdown 将书籍导出markdown
815816func (m * Book ) Export2Markdown (identify string ) (path string , err error ) {
816817 var (
817- book * Book
818- baseDir = "uploads/export"
819- files []string
820- dirMap = make (map [string ]bool )
818+ book * Book
819+ exportDir = fmt .Sprintf ("uploads/export/%v" , identify )
820+ attachPrefix string
821+ exportAttachPrefix = "../attachments/"
822+ docs []Document
823+ ds []DocumentStore
824+ docIds []interface {}
825+ docMap = make (map [int ]Document )
826+ o = orm .NewOrm ()
827+ isOSSProject = utils .StoreType == utils .StoreOss
828+ cover = ""
829+ replaces []string
821830 )
822831
832+ path = fmt .Sprintf ("uploads/export/%v.zip" , identify )
823833 if book , err = m .FindByIdentify (identify ); err != nil {
824834 beego .Error (err )
825835 return
826836 }
827837
828- path = fmt .Sprintf (baseDir + "/%v.zip" , identify )
829-
830- // one := NewDocument()
831- // orm.NewOrm().QueryTable("md_documents").Filter("book_id", book.BookId).OrderBy("-ModifyTime").One(one, "ModifyTime")
832- // if info, errInfo := os.Stat(path); errInfo == nil {
833- // if info.ModTime().Unix() > one.ModifyTime.Unix() { //没有更新,则直接下载缓存文档
834- // return
835- // }
836- // }
837-
838- dir := fmt .Sprintf (baseDir + "/%v" , identify )
839- os .MkdirAll (dir , os .ModePerm )
838+ os .MkdirAll (filepath .Join (exportDir , "docs" ), os .ModePerm )
839+ // 最后删除导出目录
840+ defer func () {
841+ os .RemoveAll (exportDir )
842+ }()
840843
841- // cover
842- if book .Cover != "" {
843- file := filepath .Join (dir , "cover" + filepath .Ext (book .Cover ))
844- utils .CopyFile (file , strings .TrimLeft (book .Cover , "./" ))
845- files = append (files , file )
844+ attachPrefix = fmt .Sprintf ("/uploads/projects/%s/" , identify )
845+ if isOSSProject {
846+ attachPrefix = fmt .Sprintf ("/projects/%s/" , identify )
846847 }
847848
848- // chapter,并修正章节链接
849- var (
850- docs []Document
851- links = make (map [string ]string )
852- replaces []string
853- linkFmt = "/docs/%v/%v"
854- )
855- orm .NewOrm ().QueryTable (NewDocument ()).Filter ("book_id" , book .BookId ).Limit (10000 ).All (& docs )
856-
857- // 查找需要替换的链接
858- for _ , item := range docs {
859- filename := strconv .Itoa (item .DocumentId ) + ".md"
860- links [strconv .Itoa (item .DocumentId )] = filename
861- links [fmt .Sprintf (linkFmt , identify , item .DocumentId )] = filename
862- if item .Identify != "" {
863- filename = strings .TrimSuffix (item .Identify , ".md" ) + ".md"
864- links [fmt .Sprintf (linkFmt , identify , item .Identify )] = filename
865- links [item .Identify ] = filename
866- }
849+ o .QueryTable (NewDocument ()).Filter ("book_id" , book .BookId ).Limit (100000 ).All (& docs , "document_id" , "document_name" , "identify" )
850+ if len (docs ) == 0 {
851+ err = errors .New ("找不到书籍章节" )
852+ return
867853 }
868854
869- linkMDPrefix := "]("
870- for old , new := range links {
871- replaces = append (replaces , linkMDPrefix + old , linkMDPrefix + new )
855+ ext := ".md"
856+ replaces = append (replaces , attachPrefix , exportAttachPrefix )
857+ for _ , doc := range docs {
858+ docIds = append (docIds , doc .DocumentId )
859+ identify := doc .Identify
860+ id := strconv .Itoa (doc .DocumentId )
861+ if filepath .Ext (doc .Identify ) == "" {
862+ doc .Identify = doc .Identify + ext
863+ }
864+ docMap [doc .DocumentId ] = doc
865+ replaces = append (replaces ,
866+ "]($" + identify , "](" + doc .Identify ,
867+ "href=\" $" + identify , "href=\" " + doc .Identify ,
868+ "]($" + id , "](" + doc .Identify ,
869+ "href=\" $" + id , "href=\" " + doc .Identify ,
870+ )
872871 }
873872
874- replaces = append (replaces , "[TOC] " , "" ) // 从markdown中移除TOC
873+ replaces = append (replaces , "]($ " , "](" , "href= \" $" , "href= \" " )
875874
875+ // 图片等文件附件链接、URL链接等替换
876876 replacer := strings .NewReplacer (replaces ... )
877- gitbookReplacer := strings .NewReplacer (" " , "-" , "_" , "" , "&" , "" , "." , "" )
878-
879- for _ , item := range docs {
880- filename := strconv .Itoa (item .DocumentId ) + ".md"
881- if item .Identify != "" {
882- filename = strings .TrimSuffix (item .Identify , ".md" ) + ".md"
883- }
884877
885- if strings .ToLower (filename ) == "summary.md" {
878+ o .QueryTable (NewDocumentStore ()).Filter ("document_id__in" , docIds ... ).Limit (100000 ).All (& ds )
879+ for _ , item := range ds {
880+ doc , ok := docMap [item .DocumentId ]
881+ if ! ok {
886882 continue
887883 }
888884
889885 // 基本的链接替换
890886 md := replacer .Replace (item .Markdown )
891-
892- file := filepath .Join (dir , filename )
893- isDir := false
894- // 基础图片链接替换
895- if doc , _ := goquery .NewDocumentFromReader (strings .NewReader (item .Release )); doc != nil {
896- txt := strings .TrimSpace (doc .Find ("body" ).Text ())
897- if txt == "" || txt == "[TOC]" {
898- isDir = true
899- } else {
900- doc .Find ("img" ).Each (func (idx int , sel * goquery.Selection ) {
901- if src , ok := sel .Attr ("src" ); ok {
902- srcName := strings .TrimLeft (src , "./" )
903- imgFile := filepath .Join (dir , srcName )
904- utils .CopyFile (imgFile , srcName )
905- md = strings .ReplaceAll (md , linkMDPrefix + src , linkMDPrefix + srcName )
906- files = append (files , imgFile )
907- }
908- })
909- doc .Find ("a" ).Each (func (idx int , sel * goquery.Selection ) {
910- if href , ok := sel .Attr ("href" ); ok {
911- newHref := href
912- if ! strings .Contains (href , ".md" ) {
913- if strings .Contains (href , "#" ) {
914- newHref = strings .ReplaceAll (href , "#" , ".html#" )
915- } else {
916- newHref = href + ".html"
917- }
918- }
919- if slice := strings .Split (newHref , "#" ); len (slice ) > 1 {
920- newHref = slice [0 ] + "#" + gitbookReplacer .Replace (strings .Join (slice [1 :], "#" ))
921- }
922- md = strings .ReplaceAll (md , "](" + href , "](" + newHref )
923- }
924- })
925- }
926- }
927-
928- md = strings .ReplaceAll (md , "](/docs/" , "](../" )
929-
930- if isDir {
931- dirMap [filename ] = true
932- dirMap [strconv .Itoa (item .DocumentId )+ ".md" ] = true
933- } else {
934- ioutil .WriteFile (file , []byte (md ), os .ModePerm )
935- files = append (files , file )
936- }
887+ file := filepath .Join (exportDir , "docs" , doc .Identify )
888+ ioutil .WriteFile (file , []byte (md ), os .ModePerm )
937889 }
938890
939- // 生成新的 SUMMARY 文档
891+ // SUMMARY 文档内容
940892 docModel := NewDocument ()
941893 cont , _ := docModel .CreateDocumentTreeForHtml (book .BookId , 0 )
942- prefix := "/docs/" + book . Identify + "/"
943- if doc , _ := goquery .NewDocumentFromReader (strings .NewReader (cont )); doc != nil {
944- doc .Find ("a" ).Each (func (idx int , sel * goquery.Selection ) {
894+ // 把最后没有 .md 结尾的链接替换为 .md 结尾
895+ if gq , _ := goquery .NewDocumentFromReader (strings .NewReader (cont )); gq != nil {
896+ gq .Find ("a" ).Each (func (i int , sel * goquery.Selection ) {
945897 if href , ok := sel .Attr ("href" ); ok {
946- href = strings .TrimPrefix (href , prefix )
947- if ! strings .Contains (href , ".md" ) {
948- if strings .Contains (href , "#" ) {
949- href = strings .Replace (href , "#" , ".md#" , 1 )
950- } else {
951- href = href + ".md"
952- }
953- }
954- title := strings .ToLower (strings .TrimSpace (sel .Text ()))
955- if slice := strings .Split (href , "#" ); len (slice ) > 0 {
956- href = slice [0 ] + "#" + gitbookReplacer .Replace (title )
957- }
958- sel .SetAttr ("href" , href )
959- if _ , ok := dirMap [strings .Split (href , "#" )[0 ]]; ok {
960- sel .BeforeHtml (title )
961- sel .Remove ()
898+ if strings .ToLower (filepath .Ext (href )) != ".md" {
899+ href = href + ".md"
900+ sel .SetAttr ("href" , href )
962901 }
963902 }
964903 })
965- cont , _ = doc .Html ()
904+ cont , _ = gq .Html ()
966905 }
967-
968906 md := html2md .Convert (cont )
907+ md = strings .ReplaceAll (md , fmt .Sprintf ("](/read/%s/" , identify ), "](docs/" )
969908 md = fmt .Sprintf ("- [%v](README.md)\n " , book .BookName ) + md
970-
971- // 删除和覆盖已存在的summary文件
972- os .Remove (filepath .Join (dir , "summary.md" ))
973- summaryFile := filepath .Join (dir , "SUMMARY.md" )
909+ summaryFile := filepath .Join (exportDir , "SUMMARY.md" )
974910 ioutil .WriteFile (summaryFile , []byte (md ), os .ModePerm )
975911
912+ // 书籍封面处理
913+ if book .Cover != "" {
914+ cover = fmt .Sprintf ("" , strings .TrimPrefix (strings .TrimLeft (strings .ReplaceAll (book .Cover , "\\ " , "/" ), "./" ), strings .TrimLeft (attachPrefix , "./" )))
915+ }
916+
976917 // README
977- md = fmt .Sprintf ("# %+v \n \n " , book .BookName ) + md
978- readmeFile := filepath .Join (dir , "README.md" )
918+ md = fmt .Sprintf ("# %s \n \n %s \n \n %s \n \n \n \n ## 目录 \n \n %s " , book .BookName , cover , book . Description , md )
919+ readmeFile := filepath .Join (exportDir , "README.md" )
979920 ioutil .WriteFile (readmeFile , []byte (md ), os .ModePerm )
980921
981- files = append (files , summaryFile , readmeFile )
922+ dst := filepath .Join (exportDir , "attachments" )
923+ src := fmt .Sprintf ("projects/%s" , identify )
924+ if ! isOSSProject {
925+ src = "uploads/" + src
926+ }
927+
928+ if errDown := m .down2local (src , dst ); errDown != nil {
929+ beego .Error ("down2local error:" , errDown .Error ())
930+ }
931+
932+ err = m .zip (exportDir , path )
933+ if err != nil {
934+ beego .Error ("压缩失败:" , err .Error ())
935+ }
936+ return
937+ }
938+
939+ // 把书籍相关附件下载到本地
940+ func (m * Book ) down2local (srcDir , desDir string ) (err error ) {
941+ if utils .StoreType == utils .StoreLocal {
942+ return store .ModelStoreLocal .CopyDir (srcDir , desDir )
943+ }
944+ return store .ModelStoreOss .Down2local (srcDir , desDir )
945+ }
982946
947+ func (m * Book ) zip (dir , zipFile string ) (err error ) {
983948 // zip 压缩
984949
985950 var (
986951 d * os.File
987952 fw io.Writer
988953 fcont []byte
954+ fl []filetil.FileList
989955 )
990- os .Remove (path )
991- d , err = os .Create (path )
956+
957+ if fl , err = filetil .ScanFiles (dir ); err != nil {
958+ return
959+ }
960+
961+ os .Remove (zipFile )
962+ d , err = os .Create (zipFile )
992963 if err != nil {
993964 beego .Error (err )
994965 return
995966 }
996967 defer d .Close ()
997968 zipWriter := zip .NewWriter (d )
998969 defer zipWriter .Close ()
999- for _ , file := range files {
1000- info , errInfo := os .Stat (file )
970+
971+ for _ , file := range fl {
972+ if file .IsDir {
973+ continue
974+ }
975+
976+ info , errInfo := os .Stat (file .Path )
1001977 if errInfo != nil {
1002978 beego .Error (errInfo )
1003979 continue
@@ -1010,15 +986,15 @@ func (m *Book) Export2Markdown(identify string) (path string, err error) {
1010986 }
1011987
1012988 header .Method = zip .Deflate
1013- header .Name = strings .TrimLeft (strings .TrimPrefix (strings .ReplaceAll (file , "\\ " , "/" ), dir ), "./" )
989+ header .Name = strings .TrimLeft (strings .TrimPrefix (strings .ReplaceAll (file . Path , "\\ " , "/" ), dir ), "./" )
1014990
1015991 fw , err = zipWriter .CreateHeader (header )
1016992 if err != nil {
1017993 beego .Error (err )
1018994 return
1019995 }
1020996
1021- fcont , err = ioutil .ReadFile (file )
997+ fcont , err = ioutil .ReadFile (file . Path )
1022998 if err != nil {
1023999 return
10241000 }
@@ -1027,6 +1003,5 @@ func (m *Book) Export2Markdown(identify string) (path string, err error) {
10271003 return
10281004 }
10291005 }
1030- os .RemoveAll (dir )
10311006 return
10321007}
0 commit comments