实现导入Markdown

This commit is contained in:
Minho
2018-03-24 17:24:02 +08:00
parent 7e9769dfbb
commit 4d1a03998a
81 changed files with 15083 additions and 459 deletions

View File

@@ -6,11 +6,12 @@ import (
"os"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/conf"
"github.com/lifei6671/mindoc/utils"
"strings"
"github.com/lifei6671/mindoc/utils/filetil"
)
// Attachment struct .
@@ -97,7 +98,7 @@ func (m *Attachment) FindToPager(pageIndex, pageSize int) (attachList []*Attachm
if err != nil {
return nil,0,err
return nil, 0, err
}
totalCount = int(total)
offset := (pageIndex - 1) * pageSize
@@ -113,7 +114,7 @@ func (m *Attachment) FindToPager(pageIndex, pageSize int) (attachList []*Attachm
for _, item := range list {
attach := &AttachmentResult{}
attach.Attachment = *item
attach.FileShortSize = utils.FormatBytes(int64(attach.FileSize))
attach.FileShortSize = filetil.FormatBytes(int64(attach.FileSize))
book := NewBook()

View File

@@ -1,9 +1,10 @@
package models
import (
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/utils"
"strings"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/utils/filetil"
)
type AttachmentResult struct {
@@ -54,7 +55,7 @@ func (m *AttachmentResult) Find(id int) (*AttachmentResult, error) {
m.Account = member.Account
}
}
m.FileShortSize = utils.FormatBytes(int64(attach.FileSize))
m.FileShortSize = filetil.FormatBytes(int64(attach.FileSize))
m.LocalHttpPath = strings.Replace(m.FilePath, "\\", "/", -1)
return m, nil

View File

@@ -11,6 +11,17 @@ import (
"os"
"path/filepath"
"strconv"
"crypto/md5"
"io"
"errors"
"github.com/lifei6671/mindoc/utils/filetil"
"github.com/lifei6671/mindoc/utils/ziptil"
"strings"
"regexp"
"io/ioutil"
"github.com/lifei6671/mindoc/utils/cryptil"
"github.com/lifei6671/mindoc/utils/requests"
"gopkg.in/russross/blackfriday.v2"
)
// Book struct .
@@ -77,6 +88,7 @@ func NewBook() *Book {
return &Book{}
}
//添加一个项目
func (m *Book) Insert() error {
o := orm.NewOrm()
// o.Begin()
@@ -124,7 +136,7 @@ func (m *Book) Find(id int) (*Book, error) {
return m, err
}
//更新一个项目
func (m *Book) Update(cols ...string) error {
o := orm.NewOrm()
@@ -164,6 +176,7 @@ func (m *Book) FindByFieldFirst(field string, value interface{}) (*Book, error)
}
//根据项目标识查询项目
func (m *Book) FindByIdentify(identify string) (*Book, error) {
o := orm.NewOrm()
@@ -375,3 +388,131 @@ func (m *Book) ResetDocumentNumber(bookId int) {
beego.Error(err)
}
}
func (book *Book)ImportBook(zipPath string) error {
if !filetil.FileExists(zipPath) {
return errors.New("文件不存在 => " + zipPath)
}
w := md5.New()
io.WriteString(w, zipPath) //将str写入到w中
io.WriteString(w, time.Now().String())
io.WriteString(w,book.BookName)
md5str := fmt.Sprintf("%x", w.Sum(nil)) //w.Sum(nil)将w的hash转成[]byte格式
tempPath := strings.Replace(filepath.Join(os.TempDir(), md5str),"\\","/",-1)
os.MkdirAll(tempPath, 0766)
//如果加压缩失败
if err := ziptil.Unzip(zipPath,tempPath);err != nil {
return err
}
err := filepath.Walk(tempPath, func(path string, info os.FileInfo, err error) error {
path = strings.Replace(path,"\\","/",-1)
if path == tempPath {
return nil
}
if !info.IsDir() {
ext := filepath.Ext(info.Name())
if strings.EqualFold(ext ,".md") || strings.EqualFold(ext , ".markdown" ) {
doc := NewDocument()
doc.BookId = book.BookId
docIdentify := strings.Replace(strings.TrimPrefix(path, tempPath+"/"), "/", "-", -1)
if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, docIdentify); !ok || err != nil {
docIdentify = "import-" + docIdentify
}
doc.Identify = docIdentify
re := regexp.MustCompile(`!\[(.*?)\]\((.*?)\)`)
markdown, err := ioutil.ReadFile(path);
if err != nil {
return err
}
doc.Markdown = re.ReplaceAllStringFunc(string(markdown), func(image string) string {
images := re.FindAllSubmatch([]byte(image), -1);
if len(images) <= 0 || len(images[0]) < 3 {
return image
}
originalImageUrl := string(images[0][2])
imageUrl := strings.Replace(string(originalImageUrl),"\\","/",-1)
//如果是本地路径,则需要将图片复制到项目目录
if !strings.HasPrefix(imageUrl, "http://") && !strings.HasPrefix(imageUrl, "https://") {
if strings.HasPrefix(imageUrl, "/") {
imageUrl = filepath.Join(tempPath, imageUrl)
} else if strings.HasPrefix(imageUrl, "./") {
imageUrl = filepath.Join(filepath.Dir(path), strings.TrimPrefix(imageUrl, "./"))
} else if strings.HasPrefix(imageUrl, "../") {
imageUrl = filepath.Join(filepath.Dir(path), imageUrl)
} else {
imageUrl = filepath.Join(filepath.Dir(path), imageUrl)
}
imageUrl = strings.Replace(imageUrl,"\\","/",-1)
dstFile := filepath.Join(conf.WorkingDirectory,"uploads",time.Now().Format("200601"),strings.TrimPrefix(imageUrl,tempPath))
if filetil.FileExists(imageUrl) {
filetil.CopyFile(imageUrl,dstFile)
imageUrl = strings.TrimPrefix(dstFile,conf.WorkingDirectory)
if !strings.HasPrefix(imageUrl,"/") && !strings.HasPrefix(imageUrl,"\\"){
imageUrl = "/" + imageUrl
}
}
}else{
imageExt := cryptil.Md5Crypt(imageUrl) + filepath.Ext(imageUrl)
dstFile := filepath.Join(conf.WorkingDirectory,"uploads",time.Now().Format("200601"),imageExt)
if err := requests.DownloadAndSaveFile(imageUrl,dstFile) ;err == nil {
imageUrl = strings.TrimPrefix(strings.Replace(dstFile,"\\","/",-1),strings.Replace(conf.WorkingDirectory,"\\","/",-1))
if !strings.HasPrefix(imageUrl,"/") && !strings.HasPrefix(imageUrl,"\\"){
imageUrl = "/" + imageUrl
}
}
}
imageUrl = strings.Replace(strings.TrimSuffix(image,originalImageUrl + ")") + imageUrl + ")","\\","/",-1)
beego.Info(imageUrl)
return imageUrl
})
doc.Content = string(blackfriday.Run([]byte(doc.Markdown)))
doc.Release = doc.Content
//beego.Info(content)
//images := re.FindAllSubmatch(markdown,-1);
//
//for _,image := range images {
// originalImageUrl := string(image[1])
// imageUrl := string(originalImageUrl)
//
// if !strings.HasPrefix(imageUrl,"http://") && !strings.HasPrefix(imageUrl,"https://") {
// if strings.HasPrefix(imageUrl, "/") {
// imageUrl = filepath.Join(tempPath, imageUrl)
// } else if strings.HasPrefix(imageUrl, "./") {
// imageUrl = filepath.Join(filepath.Dir(path), strings.TrimPrefix(imageUrl, "./"))
// } else if strings.HasPrefix(imageUrl, "../") {
// imageUrl = filepath.Join(filepath.Dir(path), imageUrl)
// }else{
// imageUrl = filepath.Join(filepath.Dir(path), imageUrl)
// }
//
// }
// beego.Info(imageUrl)
//}
}
}
return nil
})
return err
}

View File

@@ -2,24 +2,24 @@ package models
import (
"bytes"
"time"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"encoding/base64"
"github.com/PuerkitoBio/goquery"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/conf"
"github.com/lifei6671/mindoc/converter"
"github.com/lifei6671/mindoc/utils"
"gopkg.in/russross/blackfriday.v2"
"github.com/lifei6671/mindoc/utils/ziptil"
"github.com/lifei6671/mindoc/utils/filetil"
"github.com/lifei6671/mindoc/utils/ziptil"
"gopkg.in/russross/blackfriday.v2"
)
type BookResult struct {
@@ -36,7 +36,7 @@ type BookResult struct {
CommentCount int `json:"comment_count"`
CreateTime time.Time `json:"create_time"`
CreateName string `json:"create_name"`
RealName string `json:"real_name"`
RealName string `json:"real_name"`
ModifyTime time.Time `json:"modify_time"`
Cover string `json:"cover"`
Theme string `json:"theme"`
@@ -44,18 +44,18 @@ type BookResult struct {
MemberId int `json:"member_id"`
Editor string `json:"editor"`
AutoRelease bool `json:"auto_release"`
HistoryCount int `json:"history_count"`
HistoryCount int `json:"history_count"`
RelationshipId int `json:"relationship_id"`
RoleId int `json:"role_id"`
RoleName string `json:"role_name"`
Status int `json:"status"`
IsEnableShare bool `json:"is_enable_share"`
IsUseFirstDocument bool `json:"is_use_first_document"`
RelationshipId int `json:"relationship_id"`
RoleId int `json:"role_id"`
RoleName string `json:"role_name"`
Status int `json:"status"`
IsEnableShare bool `json:"is_enable_share"`
IsUseFirstDocument bool `json:"is_use_first_document"`
LastModifyText string `json:"last_modify_text"`
IsDisplayComment bool `json:"is_display_comment"`
IsDownload bool `json:"is_download"`
IsDownload bool `json:"is_download"`
}
func NewBookResult() *BookResult {
@@ -211,7 +211,7 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
convertBookResult := ConvertBookResult{}
outputPath := filepath.Join(conf.WorkingDirectory,"uploads","books", strconv.Itoa(m.BookId))
outputPath := filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(m.BookId))
viewPath := beego.BConfig.WebConfig.ViewsPath
pdfpath := filepath.Join(outputPath, "book.pdf")
@@ -220,7 +220,7 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
docxpath := filepath.Join(outputPath, "book.docx")
//先将转换的文件储存到临时目录
tempOutputPath := filepath.Join(os.TempDir(),sessionId,m.Identify) //filepath.Abs(filepath.Join("cache", sessionId))
tempOutputPath := filepath.Join(os.TempDir(), sessionId, m.Identify) //filepath.Abs(filepath.Join("cache", sessionId))
os.MkdirAll(outputPath, 0766)
os.MkdirAll(tempOutputPath, 0766)
@@ -229,7 +229,7 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
os.RemoveAll(p)
}(tempOutputPath)
if utils.FileExists(pdfpath) && utils.FileExists(epubpath) && utils.FileExists(mobipath) && utils.FileExists(docxpath) {
if filetil.FileExists(pdfpath) && filetil.FileExists(epubpath) && filetil.FileExists(mobipath) && filetil.FileExists(docxpath) {
convertBookResult.EpubPath = epubpath
convertBookResult.MobiPath = mobipath
convertBookResult.PDFPath = pdfpath
@@ -237,7 +237,6 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
return convertBookResult, nil
}
docs, err := NewDocument().FindListByBookId(m.BookId)
if err != nil {
return convertBookResult, err
@@ -293,7 +292,7 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
More: []string{},
}
if m.Publisher != "" {
ebookConfig.Footer = "<p style='color:#8E8E8E;font-size:12px;'>本文档由 <span style='text-decoration:none;color:#1abc9c;font-weight:bold;'>"+ m.Publisher +"</span> 生成<span style='float:right'>- _PAGENUM_ -</span></p>"
ebookConfig.Footer = "<p style='color:#8E8E8E;font-size:12px;'>本文档由 <span style='text-decoration:none;color:#1abc9c;font-weight:bold;'>" + m.Publisher + "</span> 生成<span style='float:right'>- _PAGENUM_ -</span></p>"
}
if m.RealName != "" {
ebookConfig.Creator = m.RealName
@@ -354,35 +353,31 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
f.Close()
}
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","css","kancloud.css"),filepath.Join(tempOutputPath,"styles","css","kancloud.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","css","export.css"),filepath.Join(tempOutputPath,"styles","css","export.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","editor.md","css","editormd.preview.css"),filepath.Join(tempOutputPath,"styles","editor.md","css","editormd.preview.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","prettify","themes","prettify.css"),filepath.Join(tempOutputPath,"styles","prettify","themes","prettify.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","css,","markdown.preview.css"),filepath.Join(tempOutputPath,"styles","css","markdown.preview.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","highlight","styles","vs.css"),filepath.Join(tempOutputPath,"styles","highlight","styles","vs.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory,"static","katex","katex.min.css"),filepath.Join(tempOutputPath,"styles","katex","katex.min.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "css", "kancloud.css"), filepath.Join(tempOutputPath, "styles", "css", "kancloud.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "css", "export.css"), filepath.Join(tempOutputPath, "styles", "css", "export.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "editor.md", "css", "editormd.preview.css"), filepath.Join(tempOutputPath, "styles", "editor.md", "css", "editormd.preview.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "prettify", "themes", "prettify.css"), filepath.Join(tempOutputPath, "styles", "prettify", "themes", "prettify.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "css,", "markdown.preview.css"), filepath.Join(tempOutputPath, "styles", "css", "markdown.preview.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "highlight", "styles", "vs.css"), filepath.Join(tempOutputPath, "styles", "highlight", "styles", "vs.css"))
filetil.CopyFile(filepath.Join(conf.WorkingDirectory, "static", "katex", "katex.min.css"), filepath.Join(tempOutputPath, "styles", "katex", "katex.min.css"))
eBookConverter := &converter.Converter{
BasePath: tempOutputPath,
OutputPath: strings.TrimSuffix(tempOutputPath,"sources"),
Config: ebookConfig,
Debug: true,
BasePath: tempOutputPath,
OutputPath: strings.TrimSuffix(tempOutputPath, "sources"),
Config: ebookConfig,
Debug: true,
}
if err := eBookConverter.Convert(); err != nil {
beego.Error("转换文件错误:" + m.BookName + " => " + err.Error())
return convertBookResult, err
}
beego.Info("文档转换完成:" + m.BookName)
utils.CopyFile(mobipath, filepath.Join(tempOutputPath, "output", "book.mobi"))
utils.CopyFile(pdfpath, filepath.Join(tempOutputPath, "output", "book.pdf"))
utils.CopyFile(epubpath, filepath.Join(tempOutputPath, "output", "book.epub"))
utils.CopyFile(docxpath, filepath.Join(tempOutputPath, "output", "book.docx"))
filetil.CopyFile(mobipath, filepath.Join(tempOutputPath, "output", "book.mobi"))
filetil.CopyFile(pdfpath, filepath.Join(tempOutputPath, "output", "book.pdf"))
filetil.CopyFile(epubpath, filepath.Join(tempOutputPath, "output", "book.epub"))
filetil.CopyFile(docxpath, filepath.Join(tempOutputPath, "output", "book.docx"))
convertBookResult.MobiPath = mobipath
convertBookResult.PDFPath = pdfpath
@@ -393,45 +388,45 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
}
//导出Markdown原始文件
func (m *BookResult) ExportMarkdown(sessionId string)(string, error){
outputPath := filepath.Join(conf.WorkingDirectory,"uploads","books", strconv.Itoa(m.BookId), "book.zip")
func (m *BookResult) ExportMarkdown(sessionId string) (string, error) {
outputPath := filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(m.BookId), "book.zip")
os.MkdirAll(filepath.Dir(outputPath),0644)
os.MkdirAll(filepath.Dir(outputPath), 0644)
tempOutputPath := filepath.Join(os.TempDir(),sessionId,"markdown")
tempOutputPath := filepath.Join(os.TempDir(), sessionId, "markdown")
defer os.RemoveAll(tempOutputPath)
err := exportMarkdown(tempOutputPath,0,m.BookId)
err := exportMarkdown(tempOutputPath, 0, m.BookId)
if err != nil {
return "",err
return "", err
}
if err := ziptil.Compress(outputPath,tempOutputPath);err != nil {
beego.Error("导出Markdown失败=>",err)
return "",err
if err := ziptil.Compress(outputPath, tempOutputPath); err != nil {
beego.Error("导出Markdown失败=>", err)
return "", err
}
return outputPath,nil
return outputPath, nil
}
func exportMarkdown(p string,parentId int,bookId int) (error){
func exportMarkdown(p string, parentId int, bookId int) error {
o := orm.NewOrm()
var docs []*Document
_,err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id",bookId).Filter("parent_id",parentId).All(&docs)
_, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", bookId).Filter("parent_id", parentId).All(&docs)
if err != nil {
beego.Error("导出Markdown失败=>",err)
beego.Error("导出Markdown失败=>", err)
return err
}
for _,doc := range docs {
for _, doc := range docs {
//获取当前文档的子文档数量如果数量不为0则将当前文档命名为READMD.md并设置成目录。
subDocCount,err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("parent_id",doc.DocumentId).Count()
subDocCount, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("parent_id", doc.DocumentId).Count()
if err != nil {
beego.Error("导出Markdown失败=>",err)
beego.Error("导出Markdown失败=>", err)
return err
}
@@ -439,27 +434,27 @@ func exportMarkdown(p string,parentId int,bookId int) (error){
if subDocCount > 0 {
if doc.Identify != "" {
docPath = filepath.Join(p, doc.Identify,"README.md")
docPath = filepath.Join(p, doc.Identify, "README.md")
} else {
docPath = filepath.Join(p, strconv.Itoa(doc.DocumentId),"README.md")
docPath = filepath.Join(p, strconv.Itoa(doc.DocumentId), "README.md")
}
}else{
} else {
if doc.Identify != "" {
docPath = filepath.Join(p, doc.Identify + ".md")
docPath = filepath.Join(p, doc.Identify+".md")
} else {
docPath = filepath.Join(p, doc.DocumentName + ".md")
docPath = filepath.Join(p, doc.DocumentName+".md")
}
}
dirPath := filepath.Dir(docPath);
dirPath := filepath.Dir(docPath)
os.MkdirAll(dirPath,0766)
os.MkdirAll(dirPath, 0766)
if err := ioutil.WriteFile(docPath,[]byte(doc.Markdown),0644);err != nil {
beego.Error("导出Markdown失败=>",err)
if err := ioutil.WriteFile(docPath, []byte(doc.Markdown), 0644); err != nil {
beego.Error("导出Markdown失败=>", err)
return err
}
if subDocCount > 0 {
if err = exportMarkdown(dirPath,doc.DocumentId,bookId);err != nil {
if err = exportMarkdown(dirPath, doc.DocumentId, bookId); err != nil {
return err
}
}
@@ -468,36 +463,13 @@ func exportMarkdown(p string,parentId int,bookId int) (error){
}
//查询项目的第一篇文档
func (m *BookResult) FindFirstDocumentByBookId(bookId int) (*Document,error) {
func (m *BookResult) FindFirstDocumentByBookId(bookId int) (*Document, error) {
o := orm.NewOrm()
doc := NewDocument()
err := o.QueryTable(doc.TableNameWithPrefix()).Filter("book_id", bookId).Filter("parent_id",0).OrderBy("order_sort").One(doc)
err := o.QueryTable(doc.TableNameWithPrefix()).Filter("book_id", bookId).Filter("parent_id", 0).OrderBy("order_sort").One(doc)
return doc,err
return doc, err
}

View File

@@ -39,6 +39,12 @@ type Document struct {
AttachList []*Attachment `orm:"-" json:"attach"`
}
// 多字段唯一键
func (m *Document) TableUnique() [][]string {
return [][]string{
[]string{"book_id", "identify"},
}
}
// TableName 获取对应数据库表名.
func (m *Document) TableName() string {
return "documents"
@@ -93,15 +99,6 @@ func (m *Document) InsertOrUpdate(cols ...string) error {
return nil
}
////根据指定字段查询一条文档.
//func (m *Document) FindByFieldFirst(field string, v interface{}) (*Document, error) {
//
// o := orm.NewOrm()
//
// err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, v).One(m)
//
// return m, err
//}
//根据文档识别编号和项目id获取一篇文档
func (m *Document) FindByIdentityFirst(identify string,bookId int) (*Document,error) {
o := orm.NewOrm()
@@ -120,11 +117,6 @@ func (m *Document) RecursiveDocument(docId int) error {
o.Delete(doc)
NewDocumentHistory().Clear(doc.DocumentId)
}
//
//var docs []*Document
//
//_, err := o.QueryTable(m.TableNameWithPrefix()).Filter("parent_id", doc_id).All(&docs)
var maps []orm.Params
_, err := o.Raw("SELECT document_id FROM " + m.TableNameWithPrefix() + " WHERE parent_id=" + strconv.Itoa(docId)).Values(&maps)
@@ -255,6 +247,7 @@ func (m *Document) FromCacheById(id int) (*Document,error) {
}()
return m.Find(id)
}
//根据文档标识从缓存中查询文档
func (m *Document) FromCacheByIdentify(identify string,bookId int) (*Document,error) {
b := cache.Get(fmt.Sprintf("Document.BookId.%d.Identify.%s",bookId , identify))
@@ -280,3 +273,4 @@ func (m *Document) FindListByBookId(bookId int) (docs []*Document, err error) {
return
}