实现导入Markdown

pull/244/head
Minho 2018-03-25 14:59:42 +08:00
parent 74df816f38
commit 6a49b8a42f
6 changed files with 210 additions and 170 deletions

View File

@ -473,7 +473,7 @@ func (c *BookController) Create() {
}
}
if books, _ := book.FindByField("identify", identify); len(books) > 0 {
if books, _ := book.FindByField("identify", identify,"book_id"); len(books) > 0 {
c.JsonResult(6006, "项目标识已存在")
}
@ -543,6 +543,10 @@ func (c *BookController) Import() {
c.JsonResult(6004, "不支持的文件类型")
}
if books, _ := models.NewBook().FindByField("identify", identify,"book_id"); len(books) > 0 {
c.JsonResult(6006, "项目标识已存在")
}
tempPath := filepath.Join(os.TempDir(), c.CruSession.SessionID())
os.MkdirAll(tempPath, 0766)
@ -676,7 +680,7 @@ func (c *BookController) Release() {
bookId = book.BookId
}
go func(identify string) {
models.NewDocument().ReleaseContent(bookId)
models.NewBook().ReleaseContent(bookId)
//当文档发布后,需要删除已缓存的转换项目
outputPath := filepath.Join(beego.AppConfig.DefaultString("book_output_path", "cache"), strconv.Itoa(bookId))

View File

@ -766,7 +766,7 @@ func (c *DocumentController) Content() {
//如果启用了自动发布
if autoRelease {
go func(identify string) {
models.NewDocument().ReleaseContent(bookId)
models.NewBook().ReleaseContent(bookId)
}(identify)
}

View File

@ -1,26 +1,28 @@
package models
import (
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/conf"
"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/filetil"
"github.com/lifei6671/mindoc/utils/requests"
"github.com/lifei6671/mindoc/utils/ziptil"
"gopkg.in/russross/blackfriday.v2"
)
@ -136,6 +138,7 @@ func (book *Book) Find(id int) (*Book, error) {
return book, err
}
//更新一个项目
func (book *Book) Update(cols ...string) error {
o := orm.NewOrm()
@ -157,11 +160,11 @@ func (book *Book) Update(cols ...string) error {
}
//根据指定字段查询结果集.
func (book *Book) FindByField(field string, value interface{}) ([]*Book, error) {
func (book *Book) FindByField(field string, value interface{},cols ...string) ([]*Book, error) {
o := orm.NewOrm()
var books []*Book
_, err := o.QueryTable(book.TableNameWithPrefix()).Filter(field, value).All(&books)
_, err := o.QueryTable(book.TableNameWithPrefix()).Filter(field, value).All(&books,cols...)
return books, err
}
@ -248,7 +251,7 @@ func (book *Book) ThoroughDeleteBook(id int) error {
}
o := orm.NewOrm()
book,err := book.Find(id);
book, err := book.Find(id)
if err != nil {
return err
}
@ -377,6 +380,72 @@ func (book *Book) FindForLabelToPager(keyword string, pageIndex, pageSize, membe
}
}
//发布文档
func (book *Book) ReleaseContent(bookId int) {
o := orm.NewOrm()
var docs []*Document
_, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", bookId).All(&docs, "document_id", "identify", "content")
if err != nil {
beego.Error("发布失败 => ", err)
return
}
for _, item := range docs {
if item.Content != "" {
item.Release = item.Content
bufio := bytes.NewReader([]byte(item.Content))
//解析文档中非本站的链接,并设置为新窗口打开
if content, err := goquery.NewDocumentFromReader(bufio); err == nil {
content.Find("a").Each(func(i int, contentSelection *goquery.Selection) {
if src, ok := contentSelection.Attr("href"); ok {
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
//beego.Info(src,conf.BaseUrl,strings.HasPrefix(src,conf.BaseUrl))
if conf.BaseUrl != "" && !strings.HasPrefix(src, conf.BaseUrl) {
contentSelection.SetAttr("target", "_blank")
if html, err := content.Html(); err == nil {
item.Release = html
}
}
}
}
})
}
}
attachList, err := NewAttachment().FindListByDocumentId(item.DocumentId)
if err == nil && len(attachList) > 0 {
content := bytes.NewBufferString("<div class=\"attach-list\"><strong>附件</strong><ul>")
for _, attach := range attachList {
if strings.HasPrefix(attach.HttpPath, "/") {
attach.HttpPath = strings.TrimSuffix(conf.BaseUrl, "/") + attach.HttpPath
}
li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>", attach.HttpPath, attach.FileName, attach.FileName)
content.WriteString(li)
}
content.WriteString("</ul></div>")
item.Release += content.String()
}
_, err = o.Update(item, "release")
if err != nil {
beego.Error(fmt.Sprintf("发布失败 => %+v", item), err)
} else {
//当文档发布后,需要清除已缓存的转换文档和文档缓存
if doc, err := NewDocument().Find(item.DocumentId); err == nil {
doc.PutToCache()
} else {
doc.RemoveCache()
}
os.RemoveAll(filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookId)))
}
}
}
//重置文档数量
func (book *Book) ResetDocumentNumber(bookId int) {
o := orm.NewOrm()
@ -394,24 +463,48 @@ func (book *Book)ImportBook(zipPath string) error {
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)
tempPath := filepath.Join(os.TempDir(), md5str)
os.MkdirAll(tempPath, 0766)
//如果加压缩失败
if err := ziptil.Unzip(zipPath, tempPath); err != nil {
return err
}
//当导入结束后,删除临时文件
defer os.RemoveAll(tempPath)
for {
//如果当前目录下只有一个目录,则重置根目录
if entries, err := ioutil.ReadDir(tempPath); err == nil && len(entries) == 1 {
dir := entries[0]
if dir.IsDir() && dir.Name() != "." && dir.Name() != ".." {
tempPath = filepath.Join(tempPath, dir.Name())
break
}
} else {
break
}
}
tempPath = strings.Replace(tempPath, "\\", "/", -1)
docMap := make(map[string]int, 0)
book.Insert()
o := orm.NewOrm()
o.Insert(book)
relationship := NewRelationship()
relationship.BookId = book.BookId
relationship.RoleId = 0
relationship.MemberId = book.MemberId
relationship.Insert()
err := filepath.Walk(tempPath, func(path string, info os.FileInfo, err error) error {
path = strings.Replace(path, "\\", "/", -1)
@ -422,6 +515,7 @@ func (book *Book)ImportBook(zipPath string) error {
ext := filepath.Ext(info.Name())
//如果是Markdown文件
if strings.EqualFold(ext, ".md") || strings.EqualFold(ext, ".markdown") {
beego.Info("正在处理 =>",path,info.Name())
doc := NewDocument()
doc.BookId = book.BookId
doc.MemberId = book.MemberId
@ -434,14 +528,14 @@ func (book *Book)ImportBook(zipPath string) error {
doc.Identify = docIdentify
//匹配图片,如果图片语法是在代码块中,这里同样会处理
re := regexp.MustCompile(`!\[(.*?)\]\((.*?)\)`)
markdown, err := ioutil.ReadFile(path);
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);
images := re.FindAllSubmatch([]byte(image), -1)
if len(images) <= 0 || len(images[0]) < 3 {
return image
}
@ -493,7 +587,7 @@ func (book *Book)ImportBook(zipPath string) error {
doc.Markdown = linkRegexp.ReplaceAllStringFunc(doc.Markdown, func(link string) string {
links := linkRegexp.FindAllStringSubmatch(link, -1)
originalLink := links[0][2];
originalLink := links[0][2]
linkPath, err := filepath.Abs(filepath.Join(filepath.Dir(path), originalLink))
if err == nil {
@ -502,14 +596,23 @@ func (book *Book)ImportBook(zipPath string) error {
ext := filepath.Ext(linkPath)
//如果链接是Markdown文件则生成文档标识,否则,将目标文件复制到项目目录
if strings.EqualFold(ext, ".md") || strings.EqualFold(ext, ".markdown") {
docIdentify := strings.Replace(strings.TrimPrefix(linkPath, tempPath+"/"), "/", "-", -1)
docIdentify := strings.Replace(strings.TrimPrefix(strings.Replace(linkPath, "\\", "/", -1), tempPath+"/"), "/", "-", -1)
//beego.Info(originalLink, "|", linkPath, "|", docIdentify)
if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, docIdentify); !ok || err != nil {
docIdentify = "import-" + docIdentify
}
docIdentify = strings.TrimSuffix(docIdentify, "-README.md")
link = strings.TrimSuffix(link, originalLink+")") + conf.URLFor("DocumentController.Read", ":key", book.Identify, ":id", docIdentify) + ")"
} else {
//filetil.CopyFile(linkPath,)
dstPath := filepath.Join(conf.WorkingDirectory, "uploads", time.Now().Format("200601"), originalLink)
filetil.CopyFile(linkPath, dstPath)
link = strings.TrimPrefix(strings.Replace(dstPath, "\\", "/", -1), strings.Replace(conf.WorkingDirectory, "\\", "/", -1))
link = strings.TrimSuffix(link, originalLink+")") + link + ")"
}
}
}
@ -517,9 +620,8 @@ func (book *Book)ImportBook(zipPath string) error {
return link
})
doc.Content = string(blackfriday.Run([]byte(doc.Markdown)))
doc.Release = doc.Content
doc.Version = time.Now().Unix()
//解析文档名称默认使用第一个h标签为标题
@ -561,7 +663,7 @@ func (book *Book)ImportBook(zipPath string) error {
doc.ParentId = parentId
isInsert = true
}
if err := doc.InsertOrUpdate("document_name","markdown","release","content");err != nil {
if err := doc.InsertOrUpdate("document_name", "markdown", "content"); err != nil {
beego.Error(doc.DocumentId, err)
}
if isInsert {
@ -571,7 +673,7 @@ func (book *Book)ImportBook(zipPath string) error {
} else {
//如果当前目录下存在Markdown文件则需要创建此节点
if filetil.HasFileOfExt(path, []string{".md", ".markdown"}) {
beego.Info("正在处理 =>",path,info.Name())
identify := strings.Replace(strings.Trim(strings.TrimPrefix(path, tempPath), "/"), "/", "-", -1)
if ok, err := regexp.MatchString(`[a-z]+[a-zA-Z0-9_.\-]*$`, identify); !ok || err != nil {
identify = "import-" + identify
@ -595,19 +697,22 @@ func (book *Book)ImportBook(zipPath string) error {
parentDoc.ParentId = parentId
if err := parentDoc.InsertOrUpdate(); err != nil {
beego.Error(err)
}
docMap[identify] = parentDoc.DocumentId
beego.Info(path,"|",parentDoc.DocumentId,"|",identify,"|",info.Name(),"|",parentIdentify)
//beego.Info(path,"|",parentDoc.DocumentId,"|",identify,"|",info.Name(),"|",parentIdentify)
}
}
return nil
})
if err != nil {
beego.Error("导入项目异常 => ", err)
book.Description = "【项目导入存在错误:" + err.Error() + "】"
}
book.ReleaseContent(book.BookId)
return err
}

View File

@ -2,18 +2,15 @@ package models
import (
"time"
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/conf"
"strings"
"os"
"path/filepath"
"strconv"
"github.com/PuerkitoBio/goquery"
"github.com/lifei6671/mindoc/cache"
"encoding/json"
"github.com/lifei6671/mindoc/conf"
)
// Document struct.
@ -45,6 +42,7 @@ func (m *Document) TableUnique() [][]string {
[]string{"book_id", "identify"},
}
}
// TableName 获取对应数据库表名.
func (m *Document) TableName() string {
return "documents"
@ -136,72 +134,6 @@ func (m *Document) RecursiveDocument(docId int) error {
return nil
}
//发布文档
func (m *Document) ReleaseContent(bookId int) {
o := orm.NewOrm()
var docs []*Document
_, err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", bookId).All(&docs, "document_id","identify", "content")
if err != nil {
beego.Error("发布失败 => ", err)
return
}
for _, item := range docs {
if item.Content != "" {
item.Release = item.Content
bufio := bytes.NewReader([]byte(item.Content))
//解析文档中非本站的链接,并设置为新窗口打开
if content, err := goquery.NewDocumentFromReader(bufio);err == nil {
content.Find("a").Each(func(i int, contentSelection *goquery.Selection) {
if src, ok := contentSelection.Attr("href"); ok{
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src,"https://") {
//beego.Info(src,conf.BaseUrl,strings.HasPrefix(src,conf.BaseUrl))
if conf.BaseUrl != "" && !strings.HasPrefix(src,conf.BaseUrl) {
contentSelection.SetAttr("target", "_blank")
if html, err := content.Html();err == nil {
item.Release = html
}
}
}
}
})
}
}
attachList, err := NewAttachment().FindListByDocumentId(item.DocumentId)
if err == nil && len(attachList) > 0 {
content := bytes.NewBufferString("<div class=\"attach-list\"><strong>附件</strong><ul>")
for _, attach := range attachList {
if strings.HasPrefix(attach.HttpPath, "/") {
attach.HttpPath = strings.TrimSuffix(conf.BaseUrl, "/") + attach.HttpPath
}
li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>", attach.HttpPath, attach.FileName, attach.FileName)
content.WriteString(li)
}
content.WriteString("</ul></div>")
item.Release += content.String()
}
_, err = o.Update(item, "release")
if err != nil {
beego.Error(fmt.Sprintf("发布失败 => %+v", item), err)
}else {
//当文档发布后,需要清除已缓存的转换文档和文档缓存
if doc,err := NewDocument().Find(item.DocumentId); err == nil {
doc.PutToCache()
}else{
doc.RemoveCache()
}
os.RemoveAll(filepath.Join(conf.WorkingDirectory,"uploads","books",strconv.Itoa(bookId)))
}
}
}
//将文档写入缓存
func (m *Document) PutToCache() {
go func(m Document) {
@ -219,13 +151,14 @@ func (m *Document) PutToCache(){
}
}(*m)
}
//清除缓存
func (m *Document) RemoveCache() {
go func(m Document) {
cache.Put("Document.Id." + strconv.Itoa(m.DocumentId), m, time.Second*3600);
cache.Put("Document.Id."+strconv.Itoa(m.DocumentId), m, time.Second*3600)
if m.Identify != "" {
cache.Put(fmt.Sprintf("Document.BookId.%d.Identify.%s",m.BookId , m.Identify), m, time.Second*3600);
cache.Put(fmt.Sprintf("Document.BookId.%d.Identify.%s", m.BookId, m.Identify), m, time.Second*3600)
}
}(*m)
}
@ -273,4 +206,3 @@ func (m *Document) FindListByBookId(bookId int) (docs []*Document, err error) {
return
}

View File

@ -151,12 +151,11 @@ func AbsolutePath(p string) (string, error) {
// FileExists reports whether the named file or directory exists.
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
}else{
return true
}
}
func FormatBytes(size int64) string {
units := []string{" B", " KB", " MB", " GB", " TB"}

View File

@ -194,11 +194,11 @@
<div class="form-group">
<div class="form-group required">
<label class="text-label">项目标题</label>
<input type="text" class="form-control" placeholder="项目标题(不超过100字)" name="book_name" maxlength="100" value="导入测试">
<input type="text" class="form-control" placeholder="项目标题(不超过100字)" name="book_name" maxlength="100" value="">
</div>
<div class="form-group required">
<label class="text-label">项目标识</label>
<input type="text" class="form-control" placeholder="项目唯一标识(不超过50字)" name="identify" value="import">
<input type="text" class="form-control" placeholder="项目唯一标识(不超过50字)" name="identify" value="">
<div class="clearfix"></div>
<p class="text" style="font-size: 12px;color: #999;margin-top: 6px;">文档标识只能包含小写字母、数字,以及“-”、“.”和“_”符号.</p>
</div>