2017-04-22 17:24:17 +08:00
package models
import (
2018-01-25 19:18:59 +08:00
"bytes"
2018-01-26 17:17:38 +08:00
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
2018-03-24 17:24:02 +08:00
"time"
2017-04-30 22:13:12 +08:00
2021-03-23 15:09:17 +08:00
"encoding/json"
"net/http"
"regexp"
2018-01-26 17:17:38 +08:00
"github.com/PuerkitoBio/goquery"
"github.com/astaxie/beego"
2017-04-24 18:25:17 +08:00
"github.com/astaxie/beego/logs"
2018-01-26 17:17:38 +08:00
"github.com/astaxie/beego/orm"
2021-03-23 15:09:17 +08:00
"github.com/mindoc-org/mindoc/conf"
"github.com/mindoc-org/mindoc/converter"
"github.com/mindoc-org/mindoc/utils/cryptil"
"github.com/mindoc-org/mindoc/utils/filetil"
"github.com/mindoc-org/mindoc/utils/gopool"
"github.com/mindoc-org/mindoc/utils/requests"
"github.com/mindoc-org/mindoc/utils/ziptil"
2018-03-24 17:24:02 +08:00
"gopkg.in/russross/blackfriday.v2"
2018-07-10 16:26:25 +08:00
)
2018-08-17 17:33:45 +08:00
var (
exportLimitWorkerChannel = gopool . NewChannelPool ( conf . GetExportLimitNum ( ) , conf . GetExportQueueLimitNum ( ) )
2017-04-22 17:24:17 +08:00
)
type BookResult struct {
2018-01-26 17:17:38 +08:00
BookId int ` json:"book_id" `
BookName string ` json:"book_name" `
2018-11-20 20:36:14 +08:00
ItemId int ` json:"item_id" `
ItemName string ` json:"item_name" `
2018-01-26 17:17:38 +08:00
Identify string ` json:"identify" `
OrderIndex int ` json:"order_index" `
Description string ` json:"description" `
Publisher string ` json:"publisher" `
PrivatelyOwned int ` json:"privately_owned" `
PrivateToken string ` json:"private_token" `
2018-11-14 15:57:55 +08:00
BookPassword string ` json:"book_password" `
2018-01-26 17:17:38 +08:00
DocCount int ` json:"doc_count" `
CommentStatus string ` json:"comment_status" `
CommentCount int ` json:"comment_count" `
CreateTime time . Time ` json:"create_time" `
CreateName string ` json:"create_name" `
2018-03-24 17:24:02 +08:00
RealName string ` json:"real_name" `
2018-01-26 17:17:38 +08:00
ModifyTime time . Time ` json:"modify_time" `
Cover string ` json:"cover" `
Theme string ` json:"theme" `
Label string ` json:"label" `
MemberId int ` json:"member_id" `
Editor string ` json:"editor" `
AutoRelease bool ` json:"auto_release" `
2018-03-24 17:24:02 +08:00
HistoryCount int ` json:"history_count" `
2018-01-26 17:17:38 +08:00
2018-11-13 17:33:13 +08:00
//RelationshipId int `json:"relationship_id"`
//TeamRelationshipId int `json:"team_relationship_id"`
2018-11-05 18:50:01 +08:00
RoleId conf . BookRole ` 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" `
2018-01-26 17:17:38 +08:00
LastModifyText string ` json:"last_modify_text" `
IsDisplayComment bool ` json:"is_display_comment" `
2018-03-24 17:24:02 +08:00
IsDownload bool ` json:"is_download" `
2018-08-17 17:33:45 +08:00
AutoSave bool ` json:"auto_save" `
2017-04-22 17:24:17 +08:00
}
func NewBookResult ( ) * BookResult {
return & BookResult { }
}
2018-11-12 21:01:59 +08:00
func ( m * BookResult ) String ( ) string {
ret , err := json . Marshal ( * m )
2018-08-13 19:05:49 +08:00
if err != nil {
return ""
}
return string ( ret )
}
2017-04-24 18:25:17 +08:00
// 根据项目标识查询项目以及指定用户权限的信息.
2018-11-13 17:33:13 +08:00
func ( m * BookResult ) FindByIdentify ( identify string , memberId int ) ( * BookResult , error ) {
2018-03-08 16:36:13 +08:00
if identify == "" || memberId <= 0 {
2018-01-26 17:17:38 +08:00
return m , ErrInvalidParameter
2017-04-28 18:08:01 +08:00
}
2017-04-22 17:24:17 +08:00
o := orm . NewOrm ( )
2018-11-12 21:01:59 +08:00
var book Book
2017-04-22 17:24:17 +08:00
2018-11-12 21:01:59 +08:00
err := NewBook ( ) . QueryTable ( ) . Filter ( "identify" , identify ) . One ( & book )
2017-04-22 17:24:17 +08:00
if err != nil {
2018-11-14 15:57:55 +08:00
beego . Error ( "获取项目失败 ->" , err )
2017-04-22 17:24:17 +08:00
return m , err
}
2018-11-14 15:57:55 +08:00
roleId , err := NewBook ( ) . FindForRoleId ( book . BookId , memberId )
2017-04-22 17:24:17 +08:00
if err != nil {
2018-11-13 17:33:13 +08:00
return m , ErrPermissionDenied
2017-04-22 17:24:17 +08:00
}
var relationship2 Relationship
2018-11-12 21:01:59 +08:00
//查找项目创始人
2018-11-13 17:33:13 +08:00
err = NewRelationship ( ) . QueryTable ( ) . Filter ( "book_id" , book . BookId ) . Filter ( "role_id" , 0 ) . One ( & relationship2 )
2017-04-22 17:24:17 +08:00
if err != nil {
2018-08-17 17:33:45 +08:00
logs . Error ( "根据项目标识查询项目以及指定用户权限的信息 -> " , err )
2017-04-26 18:17:38 +08:00
return m , ErrPermissionDenied
2017-04-22 17:24:17 +08:00
}
2017-05-03 14:22:05 +08:00
member , err := NewMember ( ) . Find ( relationship2 . MemberId )
2017-04-22 17:24:17 +08:00
if err != nil {
2017-04-26 18:17:38 +08:00
return m , err
2017-04-22 17:24:17 +08:00
}
2018-11-12 21:01:59 +08:00
m . ToBookResult ( book )
2018-11-13 17:33:13 +08:00
m . RoleId = roleId
2018-11-12 21:01:59 +08:00
m . MemberId = memberId
2018-01-26 17:17:38 +08:00
m . CreateName = member . Account
2018-11-12 21:01:59 +08:00
2018-02-02 23:12:29 +08:00
if member . RealName != "" {
m . RealName = member . RealName
}
2018-11-13 17:33:13 +08:00
2017-04-25 20:05:59 +08:00
if m . RoleId == conf . BookFounder {
2017-04-22 17:24:17 +08:00
m . RoleName = "创始人"
2017-04-26 18:17:38 +08:00
} else if m . RoleId == conf . BookAdmin {
2017-04-22 17:24:17 +08:00
m . RoleName = "管理员"
2017-04-26 18:17:38 +08:00
} else if m . RoleId == conf . BookEditor {
2017-04-22 17:24:17 +08:00
m . RoleName = "编辑者"
2017-04-26 18:17:38 +08:00
} else if m . RoleId == conf . BookObserver {
2017-04-22 17:24:17 +08:00
m . RoleName = "观察者"
}
doc := NewDocument ( )
2017-04-26 18:17:38 +08:00
err = o . QueryTable ( doc . TableNameWithPrefix ( ) ) . Filter ( "book_id" , book . BookId ) . OrderBy ( "modify_time" ) . One ( doc )
2017-04-22 17:24:17 +08:00
if err == nil {
member2 := NewMember ( )
member2 . Find ( doc . ModifyAt )
2018-03-22 20:45:50 +08:00
m . LastModifyText = member2 . Account + " 于 " + doc . ModifyTime . Local ( ) . Format ( "2006-01-02 15:04:05" )
2017-04-22 17:24:17 +08:00
}
2017-04-26 18:17:38 +08:00
return m , nil
2017-04-22 17:24:17 +08:00
}
2017-04-24 18:25:17 +08:00
2018-01-26 17:17:38 +08:00
func ( m * BookResult ) FindToPager ( pageIndex , pageSize int ) ( books [ ] * BookResult , totalCount int , err error ) {
2017-04-24 18:25:17 +08:00
o := orm . NewOrm ( )
2018-01-26 17:17:38 +08:00
count , err := o . QueryTable ( NewBook ( ) . TableNameWithPrefix ( ) ) . Count ( )
2017-04-24 18:25:17 +08:00
if err != nil {
return
}
totalCount = int ( count )
2017-05-12 12:27:34 +08:00
sql := ` SELECT
2018-02-02 23:12:29 +08:00
book . * , rel . relationship_id , rel . role_id , m . account AS create_name , m . real_name
2017-05-12 12:27:34 +08:00
FROM md_books AS book
LEFT JOIN md_relationship AS rel ON rel . book_id = book . book_id AND rel . role_id = 0
LEFT JOIN md_members AS m ON rel . member_id = m . member_id
ORDER BY book . order_index DESC , book . book_id DESC LIMIT ? , ? `
2018-01-26 17:17:38 +08:00
offset := ( pageIndex - 1 ) * pageSize
2017-04-24 18:25:17 +08:00
2018-01-26 17:17:38 +08:00
_ , err = o . Raw ( sql , offset , pageSize ) . QueryRows ( & books )
2017-04-24 18:25:17 +08:00
return
}
2018-01-25 19:18:59 +08:00
//实体转换
func ( m * BookResult ) ToBookResult ( book Book ) * BookResult {
m . BookId = book . BookId
m . BookName = book . BookName
m . Identify = book . Identify
m . OrderIndex = book . OrderIndex
m . Description = strings . Replace ( book . Description , "\r\n" , "<br/>" , - 1 )
m . PrivatelyOwned = book . PrivatelyOwned
m . PrivateToken = book . PrivateToken
2018-11-14 15:57:55 +08:00
m . BookPassword = book . BookPassword
2018-01-25 19:18:59 +08:00
m . DocCount = book . DocCount
m . CommentStatus = book . CommentStatus
m . CommentCount = book . CommentCount
2018-03-23 10:00:36 +08:00
m . CreateTime = book . CreateTime
m . ModifyTime = book . ModifyTime
2018-01-25 19:18:59 +08:00
m . Cover = book . Cover
m . Label = book . Label
m . Status = book . Status
m . Editor = book . Editor
m . Theme = book . Theme
m . AutoRelease = book . AutoRelease == 1
2018-03-13 19:20:50 +08:00
m . IsEnableShare = book . IsEnableShare == 0
2018-03-23 10:00:36 +08:00
m . IsUseFirstDocument = book . IsUseFirstDocument == 1
2018-01-25 19:18:59 +08:00
m . Publisher = book . Publisher
2018-02-02 23:12:29 +08:00
m . HistoryCount = book . HistoryCount
2018-02-28 15:47:00 +08:00
m . IsDownload = book . IsDownload == 0
2018-08-17 17:33:45 +08:00
m . AutoSave = book . AutoSave == 1
2018-11-20 20:36:14 +08:00
m . ItemId = book . ItemId
2018-01-25 19:18:59 +08:00
if book . Theme == "" {
m . Theme = "default"
}
if book . Editor == "" {
m . Editor = "markdown"
}
2018-03-22 20:45:50 +08:00
doc := NewDocument ( )
o := orm . NewOrm ( )
err := o . QueryTable ( doc . TableNameWithPrefix ( ) ) . Filter ( "book_id" , book . BookId ) . OrderBy ( "modify_time" ) . One ( doc )
if err == nil {
member2 := NewMember ( )
member2 . Find ( doc . ModifyAt )
m . LastModifyText = member2 . Account + " 于 " + doc . ModifyTime . Local ( ) . Format ( "2006-01-02 15:04:05" )
}
2018-11-20 20:36:14 +08:00
if m . ItemId > 0 {
if item , err := NewItemsets ( ) . First ( m . ItemId ) ; err == nil {
m . ItemName = item . ItemName
}
}
2018-01-25 19:18:59 +08:00
return m
}
2018-07-10 16:26:25 +08:00
//后台转换
2018-11-12 21:01:59 +08:00
func BackgroundConvert ( sessionId string , bookResult * BookResult ) error {
2018-07-11 14:18:27 +08:00
if err := converter . CheckConvertCommand ( ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "检查转换程序失败 -> " , err )
2018-07-11 14:18:27 +08:00
return err
}
2018-07-11 11:30:48 +08:00
err := exportLimitWorkerChannel . LoadOrStore ( bookResult . Identify , func ( ) {
2018-07-10 16:26:25 +08:00
bookResult . Converter ( sessionId )
} )
2018-07-11 18:30:28 +08:00
2018-07-11 11:30:48 +08:00
if err != nil {
2018-07-11 18:30:28 +08:00
2018-08-17 17:33:45 +08:00
beego . Error ( "将导出任务加入任务队列失败 -> " , err )
2018-07-11 14:18:27 +08:00
return err
2018-07-11 11:30:48 +08:00
}
2018-07-10 16:26:25 +08:00
exportLimitWorkerChannel . Start ( )
2018-07-11 14:18:27 +08:00
return nil
2018-07-10 16:26:25 +08:00
}
2018-03-22 14:27:23 +08:00
//导出PDF、word等格式
2018-01-26 17:17:38 +08:00
func ( m * BookResult ) Converter ( sessionId string ) ( ConvertBookResult , error ) {
2018-01-25 19:18:59 +08:00
convertBookResult := ConvertBookResult { }
2018-07-11 11:30:48 +08:00
outputPath := filepath . Join ( conf . GetExportOutputPath ( ) , strconv . Itoa ( m . BookId ) )
2018-01-26 17:17:38 +08:00
viewPath := beego . BConfig . WebConfig . ViewsPath
2018-01-25 19:18:59 +08:00
2018-01-26 17:17:38 +08:00
pdfpath := filepath . Join ( outputPath , "book.pdf" )
epubpath := filepath . Join ( outputPath , "book.epub" )
mobipath := filepath . Join ( outputPath , "book.mobi" )
docxpath := filepath . Join ( outputPath , "book.docx" )
2018-01-25 19:18:59 +08:00
2018-01-26 17:17:38 +08:00
//先将转换的文件储存到临时目录
2018-08-17 17:33:45 +08:00
tempOutputPath := filepath . Join ( os . TempDir ( ) , sessionId , m . Identify , "source" ) //filepath.Abs(filepath.Join("cache", sessionId))
2018-07-12 15:15:24 +08:00
2018-08-17 17:33:45 +08:00
sourceDir := strings . TrimSuffix ( tempOutputPath , "source" ) ;
2018-07-12 15:15:24 +08:00
if filetil . FileExists ( sourceDir ) {
if err := os . RemoveAll ( sourceDir ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "删除临时目录失败 ->" , sourceDir , err )
2018-07-12 15:15:24 +08:00
}
}
2018-04-02 09:23:56 +08:00
if err := os . MkdirAll ( outputPath , 0766 ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "创建目录失败 -> " , outputPath , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := os . MkdirAll ( tempOutputPath , 0766 ) ; err != nil {
beego . Error ( "创建目录失败 -> " , tempOutputPath , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
os . MkdirAll ( filepath . Join ( tempOutputPath , "Images" ) , 0755 )
2018-01-26 17:17:38 +08:00
2018-07-10 16:26:25 +08:00
//defer os.RemoveAll(strings.TrimSuffix(tempOutputPath,"source"))
2018-03-24 17:24:02 +08:00
if filetil . FileExists ( pdfpath ) && filetil . FileExists ( epubpath ) && filetil . FileExists ( mobipath ) && filetil . FileExists ( docxpath ) {
2018-01-26 17:17:38 +08:00
convertBookResult . EpubPath = epubpath
convertBookResult . MobiPath = mobipath
convertBookResult . PDFPath = pdfpath
convertBookResult . WordPath = docxpath
return convertBookResult , nil
2018-01-25 19:18:59 +08:00
}
2018-01-26 17:17:38 +08:00
2018-01-25 19:18:59 +08:00
docs , err := NewDocument ( ) . FindListByBookId ( m . BookId )
if err != nil {
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
2018-01-26 17:17:38 +08:00
tocList := make ( [ ] converter . Toc , 0 )
2018-01-25 19:18:59 +08:00
for _ , item := range docs {
if item . ParentId == 0 {
toc := converter . Toc {
2018-01-26 17:17:38 +08:00
Id : item . DocumentId ,
Link : strconv . Itoa ( item . DocumentId ) + ".html" ,
Pid : item . ParentId ,
2018-01-25 19:18:59 +08:00
Title : item . DocumentName ,
}
2018-01-26 17:17:38 +08:00
tocList = append ( tocList , toc )
2018-01-25 19:18:59 +08:00
}
}
for _ , item := range docs {
if item . ParentId != 0 {
toc := converter . Toc {
2018-01-26 17:17:38 +08:00
Id : item . DocumentId ,
Link : strconv . Itoa ( item . DocumentId ) + ".html" ,
Pid : item . ParentId ,
2018-01-25 19:18:59 +08:00
Title : item . DocumentName ,
}
2018-01-26 17:17:38 +08:00
tocList = append ( tocList , toc )
2018-01-25 19:18:59 +08:00
}
}
ebookConfig := converter . Config {
2018-01-26 17:17:38 +08:00
Charset : "utf-8" ,
Cover : m . Cover ,
Timestamp : time . Now ( ) . Format ( "2006-01-02 15:04:05" ) ,
2018-03-06 13:47:51 +08:00
Description : string ( blackfriday . Run ( [ ] byte ( m . Description ) ) ) ,
2018-01-30 13:48:07 +08:00
Footer : "<p style='color:#8E8E8E;font-size:12px;'>本文档使用 <a href='https://www.iminho.me' style='text-decoration:none;color:#1abc9c;font-weight:bold;'>MinDoc</a> 构建 <span style='float:right'>- _PAGENUM_ -</span></p>" ,
Header : "<p style='color:#8E8E8E;font-size:12px;'>_SECTION_</p>" ,
2018-01-26 17:17:38 +08:00
Identifier : "" ,
Language : "zh-CN" ,
Creator : m . CreateName ,
Publisher : m . Publisher ,
Contributor : m . Publisher ,
Title : m . BookName ,
Format : [ ] string { "epub" , "mobi" , "pdf" , "docx" } ,
FontSize : "14" ,
PaperSize : "a4" ,
MarginLeft : "72" ,
MarginRight : "72" ,
MarginTop : "72" ,
MarginBottom : "72" ,
Toc : tocList ,
More : [ ] string { } ,
2018-01-25 19:18:59 +08:00
}
2018-01-30 13:21:27 +08:00
if m . Publisher != "" {
2018-03-24 17:24:02 +08:00
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>"
2018-01-30 13:21:27 +08:00
}
2018-02-02 23:12:29 +08:00
if m . RealName != "" {
ebookConfig . Creator = m . RealName
}
2018-01-25 19:18:59 +08:00
2018-01-26 17:17:38 +08:00
if tempOutputPath , err = filepath . Abs ( tempOutputPath ) ; err != nil {
2018-01-25 19:18:59 +08:00
beego . Error ( "导出目录配置错误:" + err . Error ( ) )
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
2018-01-26 17:17:38 +08:00
for _ , item := range docs {
2018-01-25 19:18:59 +08:00
name := strconv . Itoa ( item . DocumentId )
2018-01-26 17:17:38 +08:00
fpath := filepath . Join ( tempOutputPath , name + ".html" )
2018-01-25 19:18:59 +08:00
2018-09-25 18:52:18 +08:00
f , err := os . OpenFile ( fpath , os . O_CREATE | os . O_RDWR , 0755 )
2018-01-25 19:18:59 +08:00
if err != nil {
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
var buf bytes . Buffer
2018-01-26 17:17:38 +08:00
if err := beego . ExecuteViewPathTemplate ( & buf , "document/export.tpl" , viewPath , map [ string ] interface { } { "Model" : m , "Lists" : item , "BaseUrl" : conf . BaseUrl } ) ; err != nil {
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
html := buf . String ( )
if err != nil {
f . Close ( )
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
bufio := bytes . NewReader ( buf . Bytes ( ) )
doc , err := goquery . NewDocumentFromReader ( bufio )
doc . Find ( "img" ) . Each ( func ( i int , contentSelection * goquery . Selection ) {
2018-07-10 16:26:25 +08:00
if src , ok := contentSelection . Attr ( "src" ) ; ok {
2018-07-12 15:15:24 +08:00
//var encodeString string
2018-08-17 17:33:45 +08:00
dstSrcString := "Images/" + filepath . Base ( src )
2018-01-26 17:17:38 +08:00
2018-07-10 16:26:25 +08:00
//如果是本地路径则直接读取文件内容
if strings . HasPrefix ( src , "/" ) {
spath := filepath . Join ( conf . WorkingDirectory , src )
2018-08-17 17:33:45 +08:00
if filetil . CopyFile ( spath , filepath . Join ( tempOutputPath , dstSrcString ) ) ; err != nil {
beego . Error ( "复制图片失败 -> " , err , src )
2018-07-12 15:15:24 +08:00
return
2018-07-10 16:26:25 +08:00
}
2018-07-12 15:15:24 +08:00
2018-08-17 17:33:45 +08:00
} else {
2018-07-10 16:26:25 +08:00
client := & http . Client { }
2018-08-17 17:33:45 +08:00
if req , err := http . NewRequest ( "GET" , src , nil ) ; err == nil {
req . Header . Set ( "User-Agent" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36" )
req . Header . Set ( "Referer" , src )
2018-07-10 16:26:25 +08:00
//10秒连接超时时间
2018-07-12 15:15:24 +08:00
client . Timeout = time . Second * 100
2018-07-10 16:26:25 +08:00
2018-08-17 17:33:45 +08:00
if resp , err := client . Do ( req ) ; err == nil {
2018-07-10 16:26:25 +08:00
defer resp . Body . Close ( )
2018-08-17 17:33:45 +08:00
if body , err := ioutil . ReadAll ( resp . Body ) ; err == nil {
2018-07-12 15:15:24 +08:00
//encodeString = base64.StdEncoding.EncodeToString(body)
2018-08-17 17:33:45 +08:00
if err := ioutil . WriteFile ( filepath . Join ( tempOutputPath , dstSrcString ) , body , 0755 ) ; err != nil {
beego . Error ( "下载图片失败 -> " , err , src )
2018-07-12 15:15:24 +08:00
return
}
2018-08-17 17:33:45 +08:00
} else {
beego . Error ( "下载图片失败 -> " , err , src )
2018-07-12 15:15:24 +08:00
return
2018-07-10 16:26:25 +08:00
}
2018-08-17 17:33:45 +08:00
} else {
beego . Error ( "下载图片失败 -> " , err , src )
2018-07-12 15:15:24 +08:00
return
2018-07-10 16:26:25 +08:00
}
}
}
2018-07-12 15:15:24 +08:00
contentSelection . SetAttr ( "src" , dstSrcString )
2018-01-25 19:18:59 +08:00
}
} )
2018-09-25 18:52:18 +08:00
//移除文档底部的更新信息
if selection := doc . Find ( "div.wiki-bottom" ) . First ( ) ; selection . Size ( ) > 0 {
selection . Remove ( )
}
2018-01-25 19:18:59 +08:00
html , err = doc . Html ( )
if err != nil {
f . Close ( )
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
f . WriteString ( html )
f . Close ( )
}
2018-03-22 14:27:23 +08:00
2018-04-02 09:23:56 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "css" , "kancloud.css" ) , filepath . Join ( tempOutputPath , "styles" , "css" , "kancloud.css" ) ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "复制CSS样式出错 -> static/css/kancloud.css" , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "css" , "export.css" ) , filepath . Join ( tempOutputPath , "styles" , "css" , "export.css" ) ) ; err != nil {
beego . Error ( "复制CSS样式出错 -> static/css/export.css" , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "editor.md" , "css" , "editormd.preview.css" ) , filepath . Join ( tempOutputPath , "styles" , "editor.md" , "css" , "editormd.preview.css" ) ) ; err != nil {
beego . Error ( "复制CSS样式出错 -> static/editor.md/css/editormd.preview.css" , err )
2018-04-02 09:23:56 +08:00
}
2018-09-25 18:52:18 +08:00
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "css" , "markdown.preview.css" ) , filepath . Join ( tempOutputPath , "styles" , "css" , "markdown.preview.css" ) ) ; err != nil {
beego . Error ( "复制CSS样式出错 -> static/css/markdown.preview.css" , err )
2018-04-02 09:23:56 +08:00
}
2018-11-05 18:50:01 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "editor.md" , "lib" , "highlight" , "styles" , "github.css" ) , filepath . Join ( tempOutputPath , "styles" , "css" , "github.css" ) ) ; err != nil {
2018-09-25 18:52:18 +08:00
beego . Error ( "复制CSS样式出错 -> static/editor.md/lib/highlight/styles/github.css" , err )
}
2018-11-05 18:50:01 +08:00
if err := filetil . CopyDir ( filepath . Join ( conf . WorkingDirectory , "static" , "font-awesome" ) , filepath . Join ( tempOutputPath , "styles" , "font-awesome" ) ) ; err != nil {
2018-09-25 18:52:18 +08:00
beego . Error ( "复制CSS样式出错 -> static/font-awesome" , err )
2018-04-02 09:23:56 +08:00
}
2018-11-05 18:50:01 +08:00
if err := filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , "static" , "editor.md" , "lib" , "mermaid" , "mermaid.css" ) , filepath . Join ( tempOutputPath , "styles" , "css" , "mermaid.css" ) ) ; err != nil {
2018-09-25 18:52:18 +08:00
beego . Error ( "复制CSS样式出错 -> static/editor.md/lib/mermaid/mermaid.css" , err )
2018-04-02 09:23:56 +08:00
}
2018-03-22 14:27:23 +08:00
2018-01-25 19:18:59 +08:00
eBookConverter := & converter . Converter {
2018-03-24 17:24:02 +08:00
BasePath : tempOutputPath ,
2018-08-17 17:33:45 +08:00
OutputPath : filepath . Join ( strings . TrimSuffix ( tempOutputPath , "source" ) , "output" ) ,
2018-03-24 17:24:02 +08:00
Config : ebookConfig ,
Debug : true ,
2018-07-10 16:26:25 +08:00
ProcessNum : conf . GetExportProcessNum ( ) ,
2018-01-25 19:18:59 +08:00
}
2018-08-17 17:33:45 +08:00
os . MkdirAll ( eBookConverter . OutputPath , 0766 )
2018-03-30 17:21:16 +08:00
2018-01-26 17:17:38 +08:00
if err := eBookConverter . Convert ( ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "转换文件错误:" + m . BookName + " -> " + err . Error ( ) )
2018-01-26 17:17:38 +08:00
return convertBookResult , err
2018-01-25 19:18:59 +08:00
}
2018-01-26 17:17:38 +08:00
beego . Info ( "文档转换完成:" + m . BookName )
2018-01-29 13:10:05 +08:00
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( eBookConverter . OutputPath , "output" , "book.mobi" ) , mobipath , ) ; err != nil {
beego . Error ( "复制文档失败 -> " , filepath . Join ( eBookConverter . OutputPath , "output" , "book.mobi" ) , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( eBookConverter . OutputPath , "output" , "book.pdf" ) , pdfpath ) ; err != nil {
beego . Error ( "复制文档失败 -> " , filepath . Join ( eBookConverter . OutputPath , "output" , "book.pdf" ) , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( eBookConverter . OutputPath , "output" , "book.epub" ) , epubpath ) ; err != nil {
beego . Error ( "复制文档失败 -> " , filepath . Join ( eBookConverter . OutputPath , "output" , "book.epub" ) , err )
2018-04-02 09:23:56 +08:00
}
2018-08-17 17:33:45 +08:00
if err := filetil . CopyFile ( filepath . Join ( eBookConverter . OutputPath , "output" , "book.docx" ) , docxpath ) ; err != nil {
beego . Error ( "复制文档失败 -> " , filepath . Join ( eBookConverter . OutputPath , "output" , "book.docx" ) , err )
2018-04-02 09:23:56 +08:00
}
2018-01-26 17:17:38 +08:00
convertBookResult . MobiPath = mobipath
convertBookResult . PDFPath = pdfpath
convertBookResult . EpubPath = epubpath
convertBookResult . WordPath = docxpath
return convertBookResult , nil
2018-01-25 19:18:59 +08:00
}
2018-03-12 18:24:58 +08:00
//导出Markdown原始文件
2018-03-24 17:24:02 +08:00
func ( m * BookResult ) ExportMarkdown ( sessionId string ) ( string , error ) {
outputPath := filepath . Join ( conf . WorkingDirectory , "uploads" , "books" , strconv . Itoa ( m . BookId ) , "book.zip" )
2018-03-12 18:24:58 +08:00
2018-03-24 17:24:02 +08:00
os . MkdirAll ( filepath . Dir ( outputPath ) , 0644 )
2018-03-12 18:24:58 +08:00
2018-03-24 17:24:02 +08:00
tempOutputPath := filepath . Join ( os . TempDir ( ) , sessionId , "markdown" )
2018-03-12 18:24:58 +08:00
defer os . RemoveAll ( tempOutputPath )
2018-08-17 17:33:45 +08:00
bookUrl := conf . URLFor ( "DocumentController.Index" , ":key" , m . Identify ) + "/"
2018-03-27 01:45:34 +08:00
2018-08-17 17:33:45 +08:00
err := exportMarkdown ( tempOutputPath , 0 , m . BookId , tempOutputPath , bookUrl )
2018-03-12 18:24:58 +08:00
if err != nil {
2018-03-24 17:24:02 +08:00
return "" , err
2018-03-12 18:24:58 +08:00
}
2018-03-24 17:24:02 +08:00
if err := ziptil . Compress ( outputPath , tempOutputPath ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "导出Markdown失败->" , err )
2018-03-24 17:24:02 +08:00
return "" , err
2018-03-12 18:24:58 +08:00
}
2018-03-24 17:24:02 +08:00
return outputPath , nil
2018-03-12 18:24:58 +08:00
}
2018-03-27 01:45:34 +08:00
//递归导出Markdown文档
2018-08-17 17:33:45 +08:00
func exportMarkdown ( p string , parentId int , bookId int , baseDir string , bookUrl string ) error {
2018-03-12 18:24:58 +08:00
o := orm . NewOrm ( )
var docs [ ] * Document
2018-03-24 17:24:02 +08:00
_ , err := o . QueryTable ( NewDocument ( ) . TableNameWithPrefix ( ) ) . Filter ( "book_id" , bookId ) . Filter ( "parent_id" , parentId ) . All ( & docs )
2018-03-12 18:24:58 +08:00
if err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "导出Markdown失败->" , err )
2018-03-12 18:24:58 +08:00
return err
}
2018-03-24 17:24:02 +08:00
for _ , doc := range docs {
2018-03-12 18:24:58 +08:00
//获取当前文档的子文档数量, 如果数量不为0, 则将当前文档命名为READMD.md并设置成目录。
2018-03-24 17:24:02 +08:00
subDocCount , err := o . QueryTable ( NewDocument ( ) . TableNameWithPrefix ( ) ) . Filter ( "parent_id" , doc . DocumentId ) . Count ( )
2018-03-12 18:24:58 +08:00
if err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "导出Markdown失败->" , err )
2018-03-12 18:24:58 +08:00
return err
}
var docPath string
if subDocCount > 0 {
if doc . Identify != "" {
2018-03-24 17:24:02 +08:00
docPath = filepath . Join ( p , doc . Identify , "README.md" )
2018-03-12 18:24:58 +08:00
} else {
2018-03-24 17:24:02 +08:00
docPath = filepath . Join ( p , strconv . Itoa ( doc . DocumentId ) , "README.md" )
2018-03-12 18:24:58 +08:00
}
2018-03-24 17:24:02 +08:00
} else {
2018-03-12 18:24:58 +08:00
if doc . Identify != "" {
2018-08-17 17:33:45 +08:00
if strings . HasSuffix ( doc . Identify , ".md" ) || strings . HasSuffix ( doc . Identify , ".markdown" ) {
2018-03-27 01:45:34 +08:00
docPath = filepath . Join ( p , doc . Identify )
2018-08-17 17:33:45 +08:00
} else {
2018-03-27 01:45:34 +08:00
docPath = filepath . Join ( p , doc . Identify + ".md" )
}
2018-03-12 18:24:58 +08:00
} else {
2018-03-27 01:45:34 +08:00
docPath = filepath . Join ( p , strings . TrimSpace ( doc . DocumentName ) + ".md" )
2018-03-12 18:24:58 +08:00
}
}
2018-03-24 17:24:02 +08:00
dirPath := filepath . Dir ( docPath )
2018-03-12 18:24:58 +08:00
2018-03-24 17:24:02 +08:00
os . MkdirAll ( dirPath , 0766 )
2018-03-27 00:55:11 +08:00
markdown := doc . Markdown
//如果当前文档不为空
if strings . TrimSpace ( doc . Markdown ) != "" {
re := regexp . MustCompile ( ` !\[(.*?)\]\((.*?)\) ` )
//处理文档中图片
markdown = re . ReplaceAllStringFunc ( doc . 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 )
2018-03-12 18:24:58 +08:00
2018-03-27 00:55:11 +08:00
//如果是本地路径,则需要将图片复制到项目目录
if strings . HasPrefix ( imageUrl , "http://" ) || strings . HasPrefix ( imageUrl , "https://" ) {
imageExt := cryptil . Md5Crypt ( imageUrl ) + filepath . Ext ( imageUrl )
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
dstFile := filepath . Join ( baseDir , "uploads" , time . Now ( ) . Format ( "200601" ) , imageExt )
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
if err := requests . DownloadAndSaveFile ( imageUrl , dstFile ) ; err == nil {
imageUrl = strings . TrimPrefix ( strings . Replace ( dstFile , "\\" , "/" , - 1 ) , strings . Replace ( baseDir , "\\" , "/" , - 1 ) )
if ! strings . HasPrefix ( imageUrl , "/" ) && ! strings . HasPrefix ( imageUrl , "\\" ) {
imageUrl = "/" + imageUrl
}
2018-03-26 22:39:16 +08:00
}
2018-03-27 00:55:11 +08:00
} else if strings . HasPrefix ( imageUrl , "/" ) {
filetil . CopyFile ( filepath . Join ( conf . WorkingDirectory , imageUrl ) , filepath . Join ( baseDir , imageUrl ) )
2018-03-26 22:39:16 +08:00
}
2018-03-27 00:55:11 +08:00
imageUrl = strings . Replace ( strings . TrimSuffix ( image , originalImageUrl + ")" ) + imageUrl + ")" , "\\" , "/" , - 1 )
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
return imageUrl
} )
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
linkRe := regexp . MustCompile ( ` \[(.*?)\]\((.*?)\) ` )
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
markdown = linkRe . ReplaceAllStringFunc ( markdown , func ( link string ) string {
links := linkRe . FindAllStringSubmatch ( link , - 1 )
if len ( links ) > 0 && len ( links [ 0 ] ) >= 3 {
originalLink := links [ 0 ] [ 2 ]
2018-03-26 22:39:16 +08:00
2018-03-27 01:45:34 +08:00
//如果当前链接位于当前项目内
2018-08-17 17:33:45 +08:00
if strings . HasPrefix ( originalLink , bookUrl ) {
2018-03-27 01:45:34 +08:00
docIdentify := strings . TrimSpace ( strings . TrimPrefix ( originalLink , bookUrl ) )
tempDoc := NewDocument ( )
2018-08-17 17:33:45 +08:00
if id , err := strconv . Atoi ( docIdentify ) ; err == nil && id > 0 {
err := o . QueryTable ( NewDocument ( ) . TableNameWithPrefix ( ) ) . Filter ( "document_id" , id ) . One ( tempDoc , "identify" , "parent_id" , "document_id" )
2018-03-27 01:45:34 +08:00
if err != nil {
beego . Error ( err )
return link
}
2018-08-17 17:33:45 +08:00
} else {
err := o . QueryTable ( NewDocument ( ) . TableNameWithPrefix ( ) ) . Filter ( "identify" , docIdentify ) . One ( tempDoc , "identify" , "parent_id" , "document_id" )
2018-03-27 01:45:34 +08:00
if err != nil {
beego . Error ( err )
return link
}
}
2018-08-17 17:33:45 +08:00
tempLink := recursiveJoinDocumentIdentify ( tempDoc . ParentId , "" ) + strings . TrimPrefix ( originalLink , bookUrl )
2018-03-27 01:45:34 +08:00
2018-08-17 17:33:45 +08:00
if ! strings . HasSuffix ( tempLink , ".md" ) && ! strings . HasSuffix ( doc . Identify , ".markdown" ) {
2018-03-27 01:45:34 +08:00
tempLink = tempLink + ".md"
}
2018-08-17 17:33:45 +08:00
relative := strings . TrimPrefix ( strings . Replace ( p , "\\" , "/" , - 1 ) , strings . Replace ( baseDir , "\\" , "/" , - 1 ) )
2018-03-27 02:59:04 +08:00
repeat := 0
if relative != "" {
2018-08-17 17:33:45 +08:00
relative = strings . TrimSuffix ( strings . TrimPrefix ( relative , "/" ) , "/" )
repeat = strings . Count ( relative , "/" ) + 1
2018-03-27 02:59:04 +08:00
}
2018-08-17 17:33:45 +08:00
beego . Info ( repeat , "|" , relative , "|" , p , "|" , baseDir )
tempLink = strings . Repeat ( "../" , repeat ) + tempLink
2018-03-27 01:45:34 +08:00
link = strings . TrimSuffix ( link , originalLink + ")" ) + tempLink + ")"
}
2018-03-27 00:55:11 +08:00
}
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
return link
2018-03-26 22:39:16 +08:00
2018-03-27 00:55:11 +08:00
} )
2018-08-17 17:33:45 +08:00
} else {
2018-03-27 00:55:11 +08:00
markdown = "# " + doc . DocumentName + "\n"
}
2018-03-26 22:39:16 +08:00
if err := ioutil . WriteFile ( docPath , [ ] byte ( markdown ) , 0644 ) ; err != nil {
2018-08-17 17:33:45 +08:00
beego . Error ( "导出Markdown失败->" , err )
2018-03-12 18:24:58 +08:00
return err
}
2018-03-26 22:39:16 +08:00
2018-03-12 18:24:58 +08:00
if subDocCount > 0 {
2018-08-17 17:33:45 +08:00
if err = exportMarkdown ( dirPath , doc . DocumentId , bookId , baseDir , bookUrl ) ; err != nil {
2018-03-12 18:24:58 +08:00
return err
}
}
}
return nil
}
2018-08-17 17:33:45 +08:00
func recursiveJoinDocumentIdentify ( parentDocId int , identify string ) string {
2018-03-27 01:45:34 +08:00
o := orm . NewOrm ( )
doc := NewDocument ( )
2018-08-17 17:33:45 +08:00
err := o . QueryTable ( NewDocument ( ) . TableNameWithPrefix ( ) ) . Filter ( "document_id" , parentDocId ) . One ( doc , "identify" , "parent_id" , "document_id" )
2018-03-27 01:45:34 +08:00
if err != nil {
beego . Error ( err )
return identify
}
if doc . Identify == "" {
identify = strconv . Itoa ( doc . DocumentId ) + "/" + identify
2018-08-17 17:33:45 +08:00
} else {
2018-03-27 01:45:34 +08:00
identify = doc . Identify + "/" + identify
}
if doc . ParentId > 0 {
2018-08-17 17:33:45 +08:00
identify = recursiveJoinDocumentIdentify ( doc . ParentId , identify )
2018-03-27 01:45:34 +08:00
}
return identify
}
2018-03-23 10:00:36 +08:00
//查询项目的第一篇文档
2018-03-24 17:24:02 +08:00
func ( m * BookResult ) FindFirstDocumentByBookId ( bookId int ) ( * Document , error ) {
2018-03-12 18:24:58 +08:00
2018-03-23 10:00:36 +08:00
o := orm . NewOrm ( )
doc := NewDocument ( )
2018-03-12 18:24:58 +08:00
2018-03-24 17:24:02 +08:00
err := o . QueryTable ( doc . TableNameWithPrefix ( ) ) . Filter ( "book_id" , bookId ) . Filter ( "parent_id" , 0 ) . OrderBy ( "order_sort" ) . One ( doc )
2018-03-23 10:00:36 +08:00
2018-03-24 17:24:02 +08:00
return doc , err
2018-03-23 10:00:36 +08:00
}