mirror of https://github.com/mindoc-org/mindoc.git
1、实现项目队列转换节省资源
2、实现同一个项目通过配置限定转换线程 3、修复转换Word没有图片的问题 4、优化Mac下单页打印中文字体无法显示的问题pull/358/head
parent
01084c7b0b
commit
483df1f67b
|
@ -74,7 +74,12 @@ mail_expired=30
|
||||||
secure=LOGIN
|
secure=LOGIN
|
||||||
|
|
||||||
###############配置PDF生成工具地址###################
|
###############配置PDF生成工具地址###################
|
||||||
wkhtmltopdf=D:/Program Files/wkhtmltopdf/bin/wkhtmltopdf.exe
|
#同一个项目同时运行导出程序的并行数量,取值1-4之间,取值越大导出速度越快,越占用资源
|
||||||
|
export_process_num=1
|
||||||
|
#并发导出的项目限制,指同一时间限制的导出项目数量,如果为0则不限制。设置的越大,越占用资源
|
||||||
|
export_limit_num=1
|
||||||
|
#指同时等待导出的任务数量
|
||||||
|
export_queue_limit_num=100
|
||||||
|
|
||||||
###############配置CDN加速##################
|
###############配置CDN加速##################
|
||||||
cdn=
|
cdn=
|
||||||
|
|
|
@ -135,6 +135,33 @@ func GetUploadFileSize() int64 {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
//同一项目导出线程的并发数
|
||||||
|
func GetExportProcessNum() int {
|
||||||
|
exportProcessNum := beego.AppConfig.DefaultInt("export_process_num",1)
|
||||||
|
|
||||||
|
if exportProcessNum <= 0 || exportProcessNum > 4 {
|
||||||
|
exportProcessNum = 1
|
||||||
|
}
|
||||||
|
return exportProcessNum;
|
||||||
|
}
|
||||||
|
//导出项目队列的并发数量
|
||||||
|
func GetExportLimitNum() int {
|
||||||
|
exportLimitNum := beego.AppConfig.DefaultInt("export_limit_num",1)
|
||||||
|
|
||||||
|
if exportLimitNum < 0 {
|
||||||
|
exportLimitNum = 1
|
||||||
|
}
|
||||||
|
return exportLimitNum;
|
||||||
|
}
|
||||||
|
//等待导出队列的长度
|
||||||
|
func GetExportQueueLimitNum() int {
|
||||||
|
exportQueueLimitNum := beego.AppConfig.DefaultInt("export_queue_limit_num",10)
|
||||||
|
|
||||||
|
if exportQueueLimitNum <= 0 {
|
||||||
|
exportQueueLimitNum = 100
|
||||||
|
}
|
||||||
|
return exportQueueLimitNum
|
||||||
|
}
|
||||||
|
|
||||||
//判断是否是允许商城的文件类型.
|
//判断是否是允许商城的文件类型.
|
||||||
func IsAllowUploadFileExt(ext string) bool {
|
func IsAllowUploadFileExt(ext string) bool {
|
||||||
|
|
|
@ -154,7 +154,7 @@ func (c *BaseController) ShowErrorPage(errCode int, errMsg string) {
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
if err := beego.ExecuteViewPathTemplate(&buf, "document/export.tpl", beego.BConfig.WebConfig.ViewsPath, map[string]interface{}{"ErrorMessage": errMsg, "errCode": errCode, "BaseUrl": conf.BaseUrl}); err != nil {
|
if err := beego.ExecuteViewPathTemplate(&buf, "errors/error.tpl", beego.BConfig.WebConfig.ViewsPath, map[string]interface{}{"ErrorMessage": errMsg, "ErrorCode": errCode, "BaseUrl": conf.BaseUrl}); err != nil {
|
||||||
c.Abort("500")
|
c.Abort("500")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"gopkg.in/russross/blackfriday.v2"
|
"gopkg.in/russross/blackfriday.v2"
|
||||||
"github.com/lifei6671/mindoc/utils/cryptil"
|
"github.com/lifei6671/mindoc/utils/cryptil"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lifei6671/mindoc/utils/filetil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DocumentController struct
|
// DocumentController struct
|
||||||
|
@ -857,29 +858,32 @@ func (c *DocumentController) Export() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eBookResult, err := bookResult.Converter(c.CruSession.SessionID())
|
outputPath := filepath.Join(conf.WorkingDirectory, "uploads", "books", strconv.Itoa(bookResult.BookId))
|
||||||
|
|
||||||
if err != nil {
|
pdfpath := filepath.Join(outputPath, "book.pdf")
|
||||||
beego.Error("转换文档失败:" + bookResult.BookName + " -> " + err.Error())
|
epubpath := filepath.Join(outputPath, "book.epub")
|
||||||
c.Abort("500")
|
mobipath := filepath.Join(outputPath, "book.mobi")
|
||||||
}
|
docxpath := filepath.Join(outputPath, "book.docx")
|
||||||
|
|
||||||
if output == "pdf" {
|
if output == "pdf" && filetil.FileExists(pdfpath){
|
||||||
c.Ctx.Output.Download(eBookResult.PDFPath, bookResult.BookName+".pdf")
|
c.Ctx.Output.Download(pdfpath, bookResult.BookName+".pdf")
|
||||||
|
c.Abort("200")
|
||||||
|
} else if output == "epub" && filetil.FileExists(epubpath){
|
||||||
|
c.Ctx.Output.Download(epubpath, bookResult.BookName+".epub")
|
||||||
|
|
||||||
c.Abort("200")
|
c.Abort("200")
|
||||||
} else if output == "epub" {
|
} else if output == "mobi" && filetil.FileExists(mobipath) {
|
||||||
c.Ctx.Output.Download(eBookResult.EpubPath, bookResult.BookName+".epub")
|
c.Ctx.Output.Download(mobipath, bookResult.BookName+".mobi")
|
||||||
|
|
||||||
c.Abort("200")
|
c.Abort("200")
|
||||||
} else if output == "mobi" {
|
} else if output == "docx" && filetil.FileExists(docxpath){
|
||||||
c.Ctx.Output.Download(eBookResult.MobiPath, bookResult.BookName+".mobi")
|
c.Ctx.Output.Download(docxpath, bookResult.BookName+".docx")
|
||||||
|
|
||||||
c.Abort("200")
|
c.Abort("200")
|
||||||
} else if output == "docx" {
|
|
||||||
c.Ctx.Output.Download(eBookResult.WordPath, bookResult.BookName+".docx")
|
|
||||||
|
|
||||||
c.Abort("200")
|
}else if output == "pdf" || output == "epub" || output == "docx" || output == "mobi"{
|
||||||
|
models.BackgroupConvert(c.CruSession.SessionID(),bookResult)
|
||||||
|
c.ShowErrorPage(200,"文档正在后台转换,请稍后再下载")
|
||||||
}else{
|
}else{
|
||||||
c.ShowErrorPage(200,"不支持的文件格式")
|
c.ShowErrorPage(200,"不支持的文件格式")
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/lifei6671/mindoc/utils/ziptil"
|
"github.com/lifei6671/mindoc/utils/ziptil"
|
||||||
"github.com/lifei6671/mindoc/utils/cryptil"
|
"github.com/lifei6671/mindoc/utils/cryptil"
|
||||||
"sync"
|
"sync"
|
||||||
|
"html"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Converter struct {
|
type Converter struct {
|
||||||
|
@ -26,6 +27,9 @@ type Converter struct {
|
||||||
Config Config
|
Config Config
|
||||||
Debug bool
|
Debug bool
|
||||||
GeneratedCover string
|
GeneratedCover string
|
||||||
|
ProcessNum int //并发的任务数量
|
||||||
|
process chan func()
|
||||||
|
limitChan chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//目录结构
|
//目录结构
|
||||||
|
@ -93,6 +97,9 @@ func NewConverter(configFile string, debug ...bool) (converter *Converter, err e
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
BasePath: basepath,
|
BasePath: basepath,
|
||||||
Debug: db,
|
Debug: db,
|
||||||
|
ProcessNum: 1,
|
||||||
|
process: make(chan func(),4),
|
||||||
|
limitChan: make(chan bool,1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,89 +107,113 @@ func NewConverter(configFile string, debug ...bool) (converter *Converter, err e
|
||||||
}
|
}
|
||||||
|
|
||||||
//执行文档转换
|
//执行文档转换
|
||||||
func (this *Converter) Convert() (err error) {
|
func (convert *Converter) Convert() (err error) {
|
||||||
if !this.Debug { //调试模式下不删除生成的文件
|
if !convert.Debug { //调试模式下不删除生成的文件
|
||||||
defer this.converterDefer() //最后移除创建的多余而文件
|
defer convert.converterDefer() //最后移除创建的多余而文件
|
||||||
|
}
|
||||||
|
if convert.process == nil{
|
||||||
|
convert.process = make(chan func(),4)
|
||||||
|
}
|
||||||
|
if convert.limitChan == nil {
|
||||||
|
if convert.ProcessNum <= 0 {
|
||||||
|
convert.ProcessNum = 1
|
||||||
|
}
|
||||||
|
convert.limitChan = make(chan bool,convert.ProcessNum)
|
||||||
|
for i := 0; i < convert.ProcessNum;i++{
|
||||||
|
convert.limitChan <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = this.generateMimeType(); err != nil {
|
if err = convert.generateMimeType(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = this.generateMetaInfo(); err != nil {
|
if err = convert.generateMetaInfo(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = this.generateTocNcx(); err != nil { //生成目录
|
if err = convert.generateTocNcx(); err != nil { //生成目录
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = this.generateSummary(); err != nil { //生成文档内目录
|
if err = convert.generateSummary(); err != nil { //生成文档内目录
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = this.generateTitlePage(); err != nil { //生成封面
|
if err = convert.generateTitlePage(); err != nil { //生成封面
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = this.generateContentOpf(); err != nil { //这个必须是generate*系列方法的最后一个调用
|
if err = convert.generateContentOpf(); err != nil { //这个必须是generate*系列方法的最后一个调用
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//将当前文件夹下的所有文件压缩成zip包,然后直接改名成content.epub
|
//将当前文件夹下的所有文件压缩成zip包,然后直接改名成content.epub
|
||||||
f := filepath.Join(this.OutputPath, "content.epub")
|
f := filepath.Join(convert.OutputPath, "content.epub")
|
||||||
os.Remove(f) //如果原文件存在了,则删除;
|
os.Remove(f) //如果原文件存在了,则删除;
|
||||||
if err = ziptil.Zip(this.BasePath,f); err == nil {
|
if err = ziptil.Zip(convert.BasePath,f); err == nil {
|
||||||
//创建导出文件夹
|
//创建导出文件夹
|
||||||
os.Mkdir(this.BasePath+"/"+output, os.ModePerm)
|
os.Mkdir(convert.BasePath+"/"+output, os.ModePerm)
|
||||||
if len(this.Config.Format) > 0 {
|
if len(convert.Config.Format) > 0 {
|
||||||
var errs []string
|
var errs []string
|
||||||
group := sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, v := range this.Config.Format {
|
go func(convert *Converter) {
|
||||||
fmt.Println("convert to " + v)
|
for _, v := range convert.Config.Format {
|
||||||
switch strings.ToLower(v) {
|
fmt.Println("convert to " + v)
|
||||||
case "epub":
|
switch strings.ToLower(v) {
|
||||||
group.Add(1)
|
case "epub":
|
||||||
go func(group *sync.WaitGroup) {
|
convert.process <- func() {
|
||||||
if err = this.convertToEpub(); err != nil {
|
if err = convert.convertToEpub(); err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
fmt.Println("转换EPUB文档失败:" + err.Error())
|
fmt.Println("转换EPUB文档失败:" + err.Error())
|
||||||
}
|
}
|
||||||
group.Done()
|
|
||||||
}(&group)
|
|
||||||
|
|
||||||
case "mobi":
|
|
||||||
group.Add(1)
|
|
||||||
go func(group *sync.WaitGroup) {
|
|
||||||
if err = this.convertToMobi(); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
fmt.Println("转换MOBI文档失败:" + err.Error())
|
|
||||||
}
|
|
||||||
group.Done()
|
|
||||||
}(&group)
|
|
||||||
case "pdf":
|
|
||||||
group.Add(1)
|
|
||||||
go func(group *sync.WaitGroup) {
|
|
||||||
if err = this.convertToPdf(); err != nil {
|
|
||||||
fmt.Println("转换PDF文档失败:" + err.Error())
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
}
|
|
||||||
group.Done()
|
|
||||||
}(&group)
|
|
||||||
case "docx":
|
|
||||||
group.Add(1)
|
|
||||||
go func(group *sync.WaitGroup) {
|
|
||||||
if err = this.convertToDocx(); err != nil {
|
|
||||||
fmt.Println("转换WORD文档失败:" + err.Error())
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Done()
|
case "mobi":
|
||||||
}(&group)
|
convert.process <- func() {
|
||||||
|
if err = convert.convertToMobi(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
fmt.Println("转换MOBI文档失败:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "pdf":
|
||||||
|
convert.process <- func() {
|
||||||
|
if err = convert.convertToPdf(); err != nil {
|
||||||
|
fmt.Println("转换PDF文档失败:" + err.Error())
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "docx":
|
||||||
|
convert.process <- func() {
|
||||||
|
if err = convert.convertToDocx(); err != nil {
|
||||||
|
fmt.Println("转换WORD文档失败:" + err.Error())
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
close(convert.process)
|
||||||
|
}(convert)
|
||||||
|
|
||||||
|
group := sync.WaitGroup{}
|
||||||
|
for {
|
||||||
|
action, isClosed := <-convert.process
|
||||||
|
fmt.Println(action,isClosed)
|
||||||
|
if action == nil && !isClosed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
group.Add(1)
|
||||||
|
<- convert.limitChan
|
||||||
|
fmt.Println("正在处理")
|
||||||
|
go func(group *sync.WaitGroup) {
|
||||||
|
action()
|
||||||
|
group.Done()
|
||||||
|
convert.limitChan <- true
|
||||||
|
}(&group)
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Wait()
|
group.Wait()
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
err = errors.New(strings.Join(errs, "\n"))
|
err = errors.New(strings.Join(errs, "\n"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = this.convertToPdf()
|
err = convert.convertToPdf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -272,7 +303,7 @@ func (this *Converter) generateTocNcx() (err error) {
|
||||||
</ncx>
|
</ncx>
|
||||||
`
|
`
|
||||||
codes, _ := this.tocToXml(0, 1)
|
codes, _ := this.tocToXml(0, 1)
|
||||||
ncx = fmt.Sprintf(ncx, this.Config.Language, this.Config.Title, strings.Join(codes, ""))
|
ncx = fmt.Sprintf(ncx, this.Config.Language, html.EscapeString(this.Config.Title), strings.Join(codes, ""))
|
||||||
return ioutil.WriteFile(filepath.Join(this.BasePath, "toc.ncx"), []byte(ncx), os.ModePerm)
|
return ioutil.WriteFile(filepath.Join(this.BasePath, "toc.ncx"), []byte(ncx), os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,11 +358,11 @@ func (this *Converter) tocToSummary(pid int) (summarys []string) {
|
||||||
summarys = append(summarys, "<ul>")
|
summarys = append(summarys, "<ul>")
|
||||||
for _, toc := range this.Config.Toc {
|
for _, toc := range this.Config.Toc {
|
||||||
if toc.Pid == pid {
|
if toc.Pid == pid {
|
||||||
summarys = append(summarys, fmt.Sprintf(`<li><a href="%v">%v</a></li>`, toc.Link, toc.Title))
|
summarys = append(summarys, fmt.Sprintf(`<li><a href="%v">%v</a></li>`, toc.Link, html.EscapeString(toc.Title)))
|
||||||
for _, item := range this.Config.Toc {
|
for _, item := range this.Config.Toc {
|
||||||
|
|
||||||
if item.Pid == toc.Id {
|
if item.Pid == toc.Id {
|
||||||
summarys = append(summarys, fmt.Sprintf(`<li><ul><li><a href="%v">%v</a></li>`, item.Link, item.Title))
|
summarys = append(summarys, fmt.Sprintf(`<li><ul><li><a href="%v">%v</a></li>`, item.Link, html.EscapeString(item.Title)))
|
||||||
summarys = append(summarys, "<li>")
|
summarys = append(summarys, "<li>")
|
||||||
summarys = append(summarys, this.tocToSummary(item.Id)...)
|
summarys = append(summarys, this.tocToSummary(item.Id)...)
|
||||||
summarys = append(summarys, "</li></ul></li>")
|
summarys = append(summarys, "</li></ul></li>")
|
||||||
|
@ -352,7 +383,7 @@ func (this *Converter) getNavPoint(toc Toc, idx int) (navpoint string, nextidx i
|
||||||
<text>%v</text>
|
<text>%v</text>
|
||||||
</navLabel>
|
</navLabel>
|
||||||
<content src="%v"/>`
|
<content src="%v"/>`
|
||||||
navpoint = fmt.Sprintf(navpoint, toc.Id, idx, toc.Title, toc.Link)
|
navpoint = fmt.Sprintf(navpoint, toc.Id, idx, html.EscapeString(toc.Title), toc.Link)
|
||||||
this.Config.Order = append(this.Config.Order, toc.Link)
|
this.Config.Order = append(this.Config.Order, toc.Link)
|
||||||
nextidx = idx + 1
|
nextidx = idx + 1
|
||||||
return
|
return
|
||||||
|
@ -377,7 +408,7 @@ func (this *Converter) generateContentOpf() (err error) {
|
||||||
<dc:creator opf:file-as="Unknown" opf:role="aut">%v</dc:creator>
|
<dc:creator opf:file-as="Unknown" opf:role="aut">%v</dc:creator>
|
||||||
<meta name="calibre:timestamp" content="%v"/>
|
<meta name="calibre:timestamp" content="%v"/>
|
||||||
`
|
`
|
||||||
meta = fmt.Sprintf(meta, this.Config.Title, this.Config.Contributor, this.Config.Publisher, this.Config.Description, this.Config.Language, this.Config.Creator, this.Config.Timestamp)
|
meta = fmt.Sprintf(meta, html.EscapeString(this.Config.Title), html.EscapeString(this.Config.Contributor), html.EscapeString(this.Config.Publisher), html.EscapeString(this.Config.Description), this.Config.Language, html.EscapeString(this.Config.Creator), this.Config.Timestamp)
|
||||||
if len(this.Config.Cover) > 0 {
|
if len(this.Config.Cover) > 0 {
|
||||||
meta = meta + `<meta name="cover" content="cover"/>`
|
meta = meta + `<meta name="cover" content="cover"/>`
|
||||||
guide = `<reference href="titlepage.xhtml" title="Cover" type="cover"/>`
|
guide = `<reference href="titlepage.xhtml" title="Cover" type="cover"/>`
|
||||||
|
|
|
@ -9,8 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
|
@ -23,6 +21,13 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"github.com/lifei6671/mindoc/utils/cryptil"
|
"github.com/lifei6671/mindoc/utils/cryptil"
|
||||||
"github.com/lifei6671/mindoc/utils/requests"
|
"github.com/lifei6671/mindoc/utils/requests"
|
||||||
|
"github.com/lifei6671/mindoc/utils/gopool"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var(
|
||||||
|
exportLimitWorkerChannel = gopool.NewChannelPool(conf.GetExportProcessNum(),conf.GetExportQueueLimitNum())
|
||||||
)
|
)
|
||||||
|
|
||||||
type BookResult struct {
|
type BookResult struct {
|
||||||
|
@ -209,6 +214,14 @@ func (m *BookResult) ToBookResult(book Book) *BookResult {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//后台转换
|
||||||
|
func BackgroupConvert(sessionId string,bookResult *BookResult){
|
||||||
|
exportLimitWorkerChannel.LoadOrStore(bookResult.Identify, func() {
|
||||||
|
bookResult.Converter(sessionId)
|
||||||
|
})
|
||||||
|
exportLimitWorkerChannel.Start()
|
||||||
|
}
|
||||||
|
|
||||||
//导出PDF、word等格式
|
//导出PDF、word等格式
|
||||||
func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
|
func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
|
||||||
|
|
||||||
|
@ -232,7 +245,14 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
|
||||||
beego.Error("创建目录失败 => ",tempOutputPath,err)
|
beego.Error("创建目录失败 => ",tempOutputPath,err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer os.RemoveAll(strings.TrimSuffix(tempOutputPath,"source"))
|
//defer os.RemoveAll(strings.TrimSuffix(tempOutputPath,"source"))
|
||||||
|
sourceDir := strings.TrimSuffix(tempOutputPath,"source");
|
||||||
|
|
||||||
|
if filetil.FileExists(sourceDir) {
|
||||||
|
if err := os.MkdirAll(sourceDir,0755); err != nil {
|
||||||
|
beego.Error("删除临时目录失败 ->", sourceDir , err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if filetil.FileExists(pdfpath) && filetil.FileExists(epubpath) && filetil.FileExists(mobipath) && filetil.FileExists(docxpath) {
|
if filetil.FileExists(pdfpath) && filetil.FileExists(epubpath) && filetil.FileExists(mobipath) && filetil.FileExists(docxpath) {
|
||||||
convertBookResult.EpubPath = epubpath
|
convertBookResult.EpubPath = epubpath
|
||||||
|
@ -333,19 +353,47 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
|
||||||
|
|
||||||
doc, err := goquery.NewDocumentFromReader(bufio)
|
doc, err := goquery.NewDocumentFromReader(bufio)
|
||||||
doc.Find("img").Each(func(i int, contentSelection *goquery.Selection) {
|
doc.Find("img").Each(func(i int, contentSelection *goquery.Selection) {
|
||||||
if src, ok := contentSelection.Attr("src"); ok && strings.HasPrefix(src, "/") {
|
if src, ok := contentSelection.Attr("src"); ok {
|
||||||
//contentSelection.SetAttr("src", baseUrl + src)
|
var encodeString string
|
||||||
spath := filepath.Join(conf.WorkingDirectory, src)
|
|
||||||
|
|
||||||
if ff, e := ioutil.ReadFile(spath); e == nil {
|
//如果是本地路径则直接读取文件内容
|
||||||
|
if strings.HasPrefix(src, "/") {
|
||||||
|
spath := filepath.Join(conf.WorkingDirectory, src)
|
||||||
|
|
||||||
encodeString := base64.StdEncoding.EncodeToString(ff)
|
if ff, e := ioutil.ReadFile(spath); e == nil {
|
||||||
|
encodeString = base64.StdEncoding.EncodeToString(ff)
|
||||||
|
}else{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
client := &http.Client{}
|
||||||
|
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)
|
||||||
|
//10秒连接超时时间
|
||||||
|
client.Timeout = time.Second * 10
|
||||||
|
|
||||||
src = "data:image/" + filepath.Ext(src) + ";base64," + encodeString
|
if resp ,err := client.Do(req);err == nil {
|
||||||
|
|
||||||
contentSelection.SetAttr("src", src)
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if body, err := ioutil.ReadAll(resp.Body);err == nil {
|
||||||
|
encodeString = base64.StdEncoding.EncodeToString(body)
|
||||||
|
}else{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
src = "data:image/" + filepath.Ext(src) + ";base64," + encodeString
|
||||||
|
|
||||||
|
contentSelection.SetAttr("src", src)
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -385,6 +433,7 @@ func (m *BookResult) Converter(sessionId string) (ConvertBookResult, error) {
|
||||||
OutputPath: filepath.Join(strings.TrimSuffix(tempOutputPath, "source"),"output"),
|
OutputPath: filepath.Join(strings.TrimSuffix(tempOutputPath, "source"),"output"),
|
||||||
Config: ebookConfig,
|
Config: ebookConfig,
|
||||||
Debug: true,
|
Debug: true,
|
||||||
|
ProcessNum: conf.GetExportProcessNum(),
|
||||||
}
|
}
|
||||||
|
|
||||||
os.MkdirAll(eBookConverter.OutputPath,0766)
|
os.MkdirAll(eBookConverter.OutputPath,0766)
|
||||||
|
@ -616,3 +665,4 @@ func (m *BookResult) FindFirstDocumentByBookId(bookId int) (*Document, error) {
|
||||||
|
|
||||||
return doc, err
|
return doc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Montserrat:300,400,500,600,700,800");
|
||||||
|
@font-face {
|
||||||
|
font-family: 'FontAwesome';
|
||||||
|
src: url("../font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0");
|
||||||
|
src: url("../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"), url("../font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"), url("../plugins/font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"), url("../plugins/font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"), url("../plugins/font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
html,body {
|
html,body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -15,10 +24,11 @@ body {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,h2,h3,h4,h5,h6,strong,input,select,textarea,button,body,code {
|
h1,h2,h3,h4,h5,h6,strong,input,select,textarea,button,body,code {
|
||||||
font-family: "Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Microsoft Yahei","Helvetica Neue",Helvetica;
|
font-family: "Montserrat","Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Microsoft Yahei","Helvetica Neue",Helvetica;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.25em;
|
font-size: 2.25em;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
/*************表格样式****************/
|
/*************表格样式****************/
|
||||||
|
.markdown-body{
|
||||||
|
font-family: "Montserrat", sans-serif !important;
|
||||||
|
}
|
||||||
.editormd-preview-container table {
|
.editormd-preview-container table {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package gopool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
HandlerIsExistErr = errors.New("指定的键已存在")
|
||||||
|
WorkerChanClosedErr = errors.New("队列已关闭")
|
||||||
|
)
|
||||||
|
type ChannelHandler func()
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
handler ChannelHandler
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelPool struct {
|
||||||
|
maxWorkerNum int
|
||||||
|
maxPoolNum int
|
||||||
|
wait *sync.WaitGroup
|
||||||
|
cache *sync.Map
|
||||||
|
worker chan *entry
|
||||||
|
limit chan bool
|
||||||
|
isClosed bool
|
||||||
|
once *sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChannelPool(maxWorkerNum, maxPoolNum int) (*ChannelPool) {
|
||||||
|
if maxWorkerNum <= 0 {
|
||||||
|
maxWorkerNum = 1
|
||||||
|
}
|
||||||
|
if maxPoolNum <= 0 {
|
||||||
|
maxWorkerNum = 100
|
||||||
|
}
|
||||||
|
return &ChannelPool{
|
||||||
|
maxWorkerNum: maxWorkerNum,
|
||||||
|
maxPoolNum: maxPoolNum,
|
||||||
|
wait: &sync.WaitGroup{},
|
||||||
|
cache: &sync.Map{},
|
||||||
|
worker: make(chan *entry, maxWorkerNum),
|
||||||
|
limit: make(chan bool, maxWorkerNum),
|
||||||
|
isClosed: false,
|
||||||
|
once: &sync.Once{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *ChannelPool) LoadOrStore(key string,value ChannelHandler) error {
|
||||||
|
if pool.isClosed {
|
||||||
|
return WorkerChanClosedErr
|
||||||
|
}
|
||||||
|
if _,loaded := pool.cache.LoadOrStore(key,false); loaded {
|
||||||
|
return HandlerIsExistErr
|
||||||
|
}else{
|
||||||
|
pool.worker <- &entry{handler:value,key:key}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *ChannelPool) Start() {
|
||||||
|
pool.once.Do(func() {
|
||||||
|
go func() {
|
||||||
|
for i :=0; i < pool.maxWorkerNum; i ++ {
|
||||||
|
pool.limit <- true
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
actual, isClosed := <-pool.worker
|
||||||
|
//当队列被关闭,则跳出循环
|
||||||
|
if actual == nil && !isClosed {
|
||||||
|
fmt.Println("工作队列已关闭")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
limit := <-pool.limit
|
||||||
|
|
||||||
|
if limit {
|
||||||
|
pool.wait.Add(1)
|
||||||
|
go func(actual *entry) {
|
||||||
|
defer pool.wait.Done()
|
||||||
|
defer func() {
|
||||||
|
pool.cache.Delete(actual.key)
|
||||||
|
pool.limit <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
actual.handler()
|
||||||
|
|
||||||
|
}(actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *ChannelPool) Wait() {
|
||||||
|
close(pool.worker)
|
||||||
|
|
||||||
|
pool.wait.Wait()
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||||
|
|
||||||
<link href="{{cdncss "/static/jstree/3.3.4/themes/default/style.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/jstree/3.3.4/themes/default/style.min.css"}}" rel="stylesheet">
|
||||||
|
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/nprogress/nprogress.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/nprogress/nprogress.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/css/kancloud.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/css/kancloud.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<link href="{{cdncss "/static/highlight/styles/vs.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/highlight/styles/vs.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/katex/katex.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/katex/katex.min.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/css/print.css"}}" media="print" rel="stylesheet">
|
<link href="{{cdncss "/static/css/print.css"}}" media="print" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue