You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

841 lines
28 KiB

package model
import (
"fmt"
"moredoc/util"
"moredoc/util/converter"
"os"
"path/filepath"
"strings"
"time"
"go.uber.org/zap"
"golang.org/x/net/html"
"gorm.io/gorm"
)
const (
// 封面按照A4纸的尺寸比例
DocumentCoverWidth = 210
DocumentCoverHeight = 297
)
2 years ago
const (
DocumentStatusPending = iota // 待转换
DocumentStatusConverting // 转换中
DocumentStatusConverted // 已转换
DocumentStatusFailed // 转换失败
DocumentStatusDisabled // 已禁用
DocumentStatusRePending // 重新等待转换
2 years ago
)
var DocumentStatusMap = map[int]struct{}{
DocumentStatusPending: {},
DocumentStatusConverting: {},
DocumentStatusConverted: {},
DocumentStatusFailed: {},
DocumentStatusDisabled: {},
}
type Document struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:文档名称;"`
Keywords string `form:"keywords" json:"keywords,omitempty" gorm:"column:keywords;type:varchar(255);size:255;comment:文档关键字;"`
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(255);size:255;comment:文档描述;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;index:user_id;comment:文档所属用户ID;"`
Width int `form:"width" json:"width,omitempty" gorm:"column:width;type:int(11);size:11;default:0;comment:宽;"`
Height int `form:"height" json:"height,omitempty" gorm:"column:height;type:int(11);size:11;default:0;comment:高;"`
Preview int `form:"preview" json:"preview,omitempty" gorm:"column:preview;type:int(11);size:11;default:0;comment:允许预览页数;"`
Pages int `form:"pages" json:"pages,omitempty" gorm:"column:pages;type:int(11);size:11;default:0;comment:文档页数;index:idx_pages;"`
DownloadCount int `form:"download_count" json:"download_count,omitempty" gorm:"column:download_count;type:int(11);size:11;default:0;comment:下载人次;index:idx_download_count;"`
ViewCount int `form:"view_count" json:"view_count,omitempty" gorm:"column:view_count;type:int(11);size:11;default:0;comment:浏览人次;index:idx_view_count;"`
FavoriteCount int `form:"favorite_count" json:"favorite_count,omitempty" gorm:"column:favorite_count;type:int(11);size:11;default:0;comment:收藏人次;index:idx_favorite_count;"`
CommentCount int `form:"comment_count" json:"comment_count,omitempty" gorm:"column:comment_count;type:int(11);size:11;default:0;comment:评论人次;"`
Score int `form:"score" json:"score,omitempty" gorm:"column:score;type:int(11);size:11;default:300;comment:评分3位整数表示500表示5分;"`
ScoreCount int `form:"score_count" json:"score_count,omitempty" gorm:"column:score_count;type:int(11);size:11;default:0;comment:评分数量;"`
Price int `form:"price" json:"price,omitempty" gorm:"column:price;type:int(11);size:11;default:0;comment:价格0表示免费;index:idx_price;"`
Size int64 `form:"size" json:"size,omitempty" gorm:"column:size;type:bigint(20);size:20;default:0;comment:文件大小;"`
Ext string `form:"ext" json:"ext,omitempty" gorm:"column:ext;type:varchar(16);size:16;index:idx_ext;comment:文件扩展名"`
Status int `form:"status" json:"status,omitempty" gorm:"column:status;type:smallint(6);size:6;default:0;index:status;comment:文档状态0 待转换1 转换中2 转换完成3 转换失败4 禁用;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
DeletedAt *gorm.DeletedAt `form:"deleted_at" json:"deleted_at,omitempty" gorm:"column:deleted_at;type:datetime;index:idx_deleted_at;comment:删除时间;"`
DeletedUserId int64 `form:"deleted_user_id" json:"deleted_user_id,omitempty" gorm:"column:deleted_user_id;type:bigint(20);size:20;default:0;comment:删除用户ID;"`
EnableGZIP bool `form:"enable_gzip" json:"enable_gzip,omitempty" gorm:"column:enable_gzip;type:tinyint(1);size:1;default:0;comment:是否启用GZIP压缩;"`
1 year ago
RecommendAt *time.Time `form:"recommend_at" json:"recommend_at,omitempty" gorm:"column:recommend_at;type:datetime;comment:推荐时间;index:idx_recommend_at;"`
PreviewExt string `form:"preview_ext" json:"preview_ext,omitempty" gorm:"column:preview_ext;type:varchar(16);size:16;default:.svg;comment:预览图扩展名;"`
}
func (Document) TableName() string {
return tablePrefix + "document"
}
// UpdateDocument 更新Document如果需要更新指定字段则请指定updateFields参数
2 years ago
func (m *DBModel) UpdateDocument(document *Document, categoryId []int64, updateFields ...string) (err error) {
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
var (
oldDocCategories []DocumentCategory
oldDocCategoryIds []int64
newDocCategories []DocumentCategory
modelDocumentCategory = &DocumentCategory{}
modelCategory = &Category{}
)
sess.Table(modelDocumentCategory.TableName()).Select("category_id").Where("document_id = ?", document.Id).Find(&oldDocCategories)
for _, cate := range oldDocCategories {
oldDocCategoryIds = append(oldDocCategoryIds, cate.CategoryId)
}
if len(oldDocCategoryIds) > 0 {
err = sess.Where("document_id = ?", document.Id).Delete(modelDocumentCategory).Error
if err != nil {
m.logger.Error("Delete DocumentCategory", zap.Error(err))
return
}
// 更新分类统计
err = sess.Model(modelCategory).Where("id in (?)", oldDocCategoryIds).Update("doc_count", gorm.Expr("doc_count - ?", 1)).Error
if err != nil {
m.logger.Error("Update doc_count--", zap.Error(err))
return
}
}
for _, cateId := range categoryId {
newDocCategories = append(newDocCategories, DocumentCategory{
DocumentId: document.Id,
CategoryId: cateId,
})
}
if len(newDocCategories) > 0 {
m.logger.Debug("newDocCategories", zap.Any("newDocCategories", newDocCategories))
err = sess.Create(&newDocCategories).Error
if err != nil {
m.logger.Error("Create New Category", zap.Error(err))
return
}
err = sess.Model(modelCategory).Where("id in (?)", categoryId).Update("doc_count", gorm.Expr("doc_count + ?", 1)).Error
if err != nil {
m.logger.Error("Update doc_count++", zap.Error(err))
return
}
}
updateFields = m.FilterValidFields(Document{}.TableName(), updateFields...)
if len(updateFields) > 0 { // 更新指定字段
2 years ago
sess = sess.Select(updateFields)
} else {
sess = sess.Select(m.GetTableFields(document.TableName())).Omit("deleted_at", "deleted_user_id")
}
2 years ago
err = sess.Where("id = ?", document.Id).Updates(document).Error
if err != nil {
m.logger.Error("UpdateDocument", zap.Error(err))
2 years ago
return
}
2 years ago
return
}
func (m *DBModel) SetDocumentReconvert() (err error) {
err = m.db.Model(&Document{}).
Where("status = ?", DocumentStatusFailed).
Update("status", DocumentStatusPending).Error
if err != nil {
m.logger.Error("SetDocumentReconvert", zap.Error(err))
}
return
}
func (m *DBModel) UpdateDocumentField(id int64, fieldValue map[string]interface{}) (err error) {
err = m.db.Model(&Document{}).Where("id = ?", id).Updates(fieldValue).Error
if err != nil {
m.logger.Error("UpdateDocumentField", zap.Error(err))
return
}
2 years ago
return
}
// GetDocument 根据id获取Document
func (m *DBModel) GetDocument(id interface{}, fields ...string) (document Document, err error) {
db := m.db
fields = m.FilterValidFields(Document{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&document).Error
return
}
type OptionGetDocumentList struct {
Page int
Size int
WithCount bool // 是否返回总数
Ids []interface{} // id列表
SelectFields []string // 查询字段
QueryRange map[string][2]interface{} // map[field][]{min,max}
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
Sort []string
IsRecycle bool // 是否是回收站模式查询
1 year ago
IsRecommend []bool
FeeType string // 费用类型free免费charge收费
}
// GetDocumentList 获取Document列表
2 years ago
func (m *DBModel) GetDocumentList(opt *OptionGetDocumentList) (documentList []Document, total int64, err error) {
1 year ago
tableDocument := Document{}.TableName() + " d"
db := m.db.Unscoped().Table(tableDocument)
if opt.IsRecycle {
// 回收站模式,只根据删除的倒序排序
1 year ago
opt.Sort = []string{"d.deleted_at desc"}
db = db.Where("d.deleted_at IS NOT NULL")
} else {
db = db.Where("d.deleted_at IS NULL")
}
m.logger.Debug("GetDocumentList", zap.Any("opt", opt))
1 year ago
db = m.generateQueryIn(db, tableDocument, opt.QueryIn)
db = m.generateQueryLike(db, tableDocument, opt.QueryLike)
db = m.generateQueryRange(db, tableDocument, opt.QueryRange)
if len(opt.Ids) > 0 {
db = db.Where("id in (?)", opt.Ids)
}
if categoryIds, ok := opt.QueryIn["category_id"]; ok && len(categoryIds) > 0 {
tableCategory := DocumentCategory{}.TableName()
1 year ago
db = db.Joins("left join "+tableCategory+" dc on dc.document_id = d.id").Where("dc.category_id in (?)", categoryIds)
}
1 year ago
if l := len(opt.IsRecommend); l == 1 {
if opt.IsRecommend[0] {
1 year ago
db = db.Where("d.`recommend_at` IS NOT NULL")
1 year ago
} else {
1 year ago
db = db.Where("d.`recommend_at` IS NULL")
1 year ago
}
}
if opt.FeeType != "" {
switch opt.FeeType {
case "free":
db = db.Where("d.`price` = ?", 0)
case "charge":
db = db.Where("d.`price` > ?", 0)
}
}
if opt.WithCount {
12 months ago
err = db.Group("d.id").Count(&total).Error
if err != nil {
m.logger.Error("GetDocumentList", zap.Error(err))
return
}
}
1 year ago
opt.SelectFields = m.FilterValidFields(tableDocument, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
} else {
db = db.Select(m.GetTableFields(tableDocument))
}
if len(opt.Sort) > 0 {
1 year ago
db = m.generateQuerySort(db, tableDocument, opt.Sort)
2 years ago
} else {
1 year ago
db = db.Order("d.id desc")
}
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
12 months ago
err = db.Group("d.id").Find(&documentList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetDocumentList", zap.Error(err))
}
return
}
// DeleteDocument 删除数据
func (m *DBModel) DeleteDocument(ids []int64, deletedUserId int64, deepDelete ...bool) (err error) {
var (
docs []Document
docCates []DocumentCategory
1 year ago
docFields = []string{"id", "status", "user_id", "deleted_at", "deleted_user_id", "title"}
docCateFields = []string{"id", "document_id", "category_id"}
modelUser = &User{}
modelDocument = &Document{}
modelDocumentCategory = &DocumentCategory{}
modelCategory = &Category{}
docCateMap = make(map[int64][]int64)
)
// 1. 查询文档信息
m.db.Model(modelDocument).Select(docFields).Unscoped().Where("id in (?)", ids).Find(&docs)
m.db.Model(modelDocumentCategory).Select(docCateFields).Where("document_id in (?)", ids).Find(&docCates)
for _, docCate := range docCates {
docCateMap[docCate.DocumentId] = append(docCateMap[docCate.DocumentId], docCate.CategoryId)
}
cfgScore := m.GetConfigOfScore(ConfigScoreDeleteDocument, ConfigScoreCreditName)
1 year ago
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
if len(deepDelete) > 0 && deepDelete[0] { // 标记附件为删除状态
err = sess.Where("type = ? and type_id in (?)", AttachmentTypeDocument, ids).Delete(&Attachment{}).Error
1 year ago
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
}
for _, doc := range docs {
if doc.DeletedAt == nil {
err = sess.Model(modelUser).Where("id = ?", doc.UserId).Update("doc_count", gorm.Expr("doc_count - ?", 1)).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
if cateIds, ok := docCateMap[doc.Id]; ok && len(cateIds) > 0 {
err = sess.Model(modelCategory).Where("id in (?)", cateIds).Update("doc_count", gorm.Expr("doc_count - ?", 1)).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
}
}
if len(deepDelete) > 0 && deepDelete[0] { // 彻底删除
err = sess.Unscoped().Delete(&doc).Error
2 years ago
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
// 关联的分类也需要删除
err = sess.Unscoped().Where("document_id = ?", doc.Id).Delete(modelDocumentCategory).Error
2 years ago
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
1 year ago
continue
}
1 year ago
// 逻辑删除
err = sess.Model(&doc).Where("id = ?", doc.Id).Updates(map[string]interface{}{
"deleted_at": time.Now(),
"deleted_user_id": deletedUserId,
}).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
// 扣除积分和添加动态
dynamic := &Dynamic{
UserId: doc.UserId,
Type: DynamicTypeDeleteDocument,
Content: fmt.Sprintf("删除了文档《%s》", doc.Title),
}
if cfgScore.DeleteDocument != 0 { // 小于0表示扣除积分
score := cfgScore.DeleteDocument
if score < 0 {
score = -score
}
dynamic.Content += fmt.Sprintf(",扣除了 %d %s", score, cfgScore.CreditName)
1 year ago
err = sess.Model(modelUser).Where("id = ?", doc.UserId).Update("credit_count", gorm.Expr("credit_count - ?", score)).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
}
err = sess.Create(dynamic).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
2 years ago
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
}
return
}
// RecoverRecycleDocument 恢复回收站中的文档
func (m *DBModel) RecoverRecycleDocument(documentId []int64) (err error) {
var (
modelDocument = &Document{}
modelCategory = &Category{}
modelUser = &User{}
documentCategories []DocumentCategory
docs []Document
)
m.db.Select([]string{"category_id"}).Where("document_id in (?)", documentId).Find(&documentCategories)
m.db.Select("user_id").Unscoped().Where("id in (?)", documentId).Find(&docs)
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
err = sess.Model(modelDocument).Unscoped().Where("id in ?", documentId).Updates(map[string]interface{}{
"deleted_at": nil,
"deleted_user_id": 0,
}).Error
if err != nil {
m.logger.Error("RecoverRecycleDocument", zap.Error(err))
return
}
for _, docCate := range documentCategories {
err = sess.Model(modelCategory).Where("id = ?", docCate.CategoryId).Update("doc_count", gorm.Expr("doc_count + ?", 1)).Error
if err != nil {
m.logger.Error("RecoverRecycleDocument", zap.Error(err))
return
}
}
for _, doc := range docs {
err = sess.Model(modelUser).Where("id = ?", doc.UserId).Update("doc_count", gorm.Expr("doc_count + ?", 1)).Error
if err != nil {
m.logger.Error("RecoverRecycleDocument", zap.Error(err))
return
}
}
return
}
func (m *DBModel) ClearRecycleDocument() (err error) {
var docs []Document
err = m.db.Unscoped().Select("id").Where("deleted_at is not null").Find(&docs).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("ClearRecycleDocument", zap.Error(err))
return
}
if len(docs) == 0 {
err = nil
return
}
var ids []int64
for _, doc := range docs {
ids = append(ids, doc.Id)
}
err = m.DeleteDocument(ids, 0, true)
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
}
return
}
// 批量创建文档
func (m *DBModel) CreateDocuments(documents []Document, categoryIds []int64) (docs []Document, err error) {
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
docCount := len(documents)
// 1. 分类下的文档数增加
err = sess.Model(&Category{}).
Where("id in ?", categoryIds).
Update("doc_count", gorm.Expr("doc_count + ?", docCount)).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
// 2. 批量创建文档
err = sess.Create(documents).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
docs = documents
// 3. 文档与分类关联
var docCates []DocumentCategory
for _, doc := range documents {
for _, cateId := range categoryIds {
docCates = append(docCates, DocumentCategory{
DocumentId: doc.Id,
CategoryId: cateId,
})
}
}
err = sess.Create(docCates).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
// 用户文档数增加
err = sess.Model(&User{}).Where("id = ?", documents[0].UserId).Update("doc_count", gorm.Expr("doc_count + ?", docCount)).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
// 奖励的数量
awardCount := docCount
cfg := m.GetConfigOfScore()
m.logger.Debug("CreateDocuments", zap.Any("GetConfigOfScore", cfg))
if cfg.UploadDocumentLimit > 0 {
var todayUploadCount int64
sess.Model(&Document{}).Where("user_id = ? and created_at >= ?", documents[0].UserId, time.Now().Format("2006-01-02")).Count(&todayUploadCount)
// 默认获得的积分奖励
creditCount := cfg.UploadDocument * int32(docCount)
if todayUploadCount > int64(cfg.UploadDocumentLimit) {
awardCount = int(cfg.UploadDocumentLimit + int32(docCount) - int32(todayUploadCount))
creditCount = cfg.UploadDocument * int32(awardCount)
}
m.logger.Debug("CreateDocuments", zap.Int32("creditCount", creditCount))
if creditCount > 0 {
err = sess.Model(&User{}).Where("id = ?", documents[0].UserId).Update("credit_count", gorm.Expr("credit_count + ?", creditCount)).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
}
}
// 添加动态
var dynamics []Dynamic
for idx, doc := range documents {
var award int32
if idx < awardCount {
award = cfg.UploadDocument
}
content := fmt.Sprintf(`上传了文档《<a href="/document/%d">%s</a>》`, doc.Id, html.EscapeString(doc.Title))
if award > 0 {
content += fmt.Sprintf(`,获得了 %d %s奖励`, award, m.GetCreditName())
}
dynamics = append(dynamics, Dynamic{
UserId: doc.UserId,
Type: DynamicTypeUpload,
Content: content,
})
}
err = sess.Create(dynamics).Error
if err != nil {
m.logger.Error("CreateDocuments", zap.Error(err))
return
}
return
}
// GetDocumentStatusConvertedByHash 根据文档hash查询已转换了的文档状态
func (m *DBModel) GetDocumentStatusConvertedByHash(hash []string) (hashMapDocuments map[string]Document) {
var (
tableDocument = Document{}.TableName()
tableAttachment = Attachment{}.TableName()
attachMapIndex = make(map[int64]int)
documentIds []int64
docs []Document
)
hashMapDocuments = make(map[string]Document)
sql := fmt.Sprintf(
"select a.hash,a.type_id from %s a left join %s d on a.type_id = d.id where a.hash in ? and d.status = ? group by a.hash",
tableAttachment, tableDocument,
)
var attachemnts []Attachment
err := m.db.Raw(sql, hash, DocumentStatusConverted).Find(&attachemnts).Error
if err != nil {
m.logger.Error("GetDocumentStatusConvertedByHash", zap.Error(err))
return
}
for idx, attachment := range attachemnts {
attachMapIndex[attachment.TypeId] = idx
documentIds = append(documentIds, attachment.TypeId)
}
if len(documentIds) == 0 {
return
}
m.db.Where("id in ?", documentIds).Find(&docs)
for _, doc := range docs {
hashMapDocuments[attachemnts[attachMapIndex[doc.Id]].Hash] = doc
}
return
}
// ConvertDocument 文档转换。如果err返回gorm.ErrRecordNotFound表示已没有文档需要转换
// 1. 查询待转换的文档
// 2. 文档对应的md5 hash中是否有已转换的文档如果有则直接关联和调整状态为已转换
// 3. 文档转PDF
// 4. PDF截取第一章图片作为封面
// 5. 根据允许最大的预览页面将PDF转为svg同时转gzip压缩如果有需要的话
// 6. 提取PDF文本以及获取文档信息
// 7. 更新文档状态
func (m *DBModel) ConvertDocument() (err error) {
var document Document
err = m.db.Where("status in ?", []int{DocumentStatusPending, DocumentStatusRePending}).First(&document).Error
if err != nil {
if err != gorm.ErrRecordNotFound {
m.logger.Error("ConvertDocument", zap.Error(err))
}
return
}
defer func() {
m.SetDocumentConvertError(document.Id, err)
}()
// 文档转为PDF
cfg := m.GetConfigOfConverter()
m.SetDocumentStatus(document.Id, DocumentStatusConverting)
attachment := m.GetAttachmentByTypeAndTypeId(AttachmentTypeDocument, document.Id)
if attachment.Id == 0 { // 附件不存在
m.SetDocumentStatus(document.Id, DocumentStatusFailed)
if err != nil {
m.logger.Error("ConvertDocument", zap.Error(err))
}
return
}
11 months ago
localFile := strings.TrimLeft(attachment.Path, "./")
baseDir := strings.TrimSuffix(localFile, filepath.Ext(localFile))
cover := baseDir + "/cover.png"
if !cfg.EnableConvertRepeatedDocument && document.Status != DocumentStatusRePending {
11 months ago
_, errCover := os.Stat(cover)
hashMapDocs := m.GetDocumentStatusConvertedByHash([]string{attachment.Hash}) // 文档hash
if len(hashMapDocs) > 0 && errCover == nil { // 双重确认文档是否已转换成功1. 存在相同hash的已转换的文档2. 存在封面图片
m.logger.Info("ConvertDocument", zap.Bool("EnableConvertRepeatedDocument", cfg.EnableConvertRepeatedDocument), zap.String("hash", attachment.Hash), zap.Any("hashMapDocs", hashMapDocs))
// 已有文档转换成功将hash相同的文档相关数据迁移到当前文档
sql := " UPDATE `%s` SET `description`= ? , `enable_gzip` = ?, `width` = ?, `height`= ?, `preview`= ?, `pages` = ?, `status` = ? WHERE status in ? and id in (select type_id from `%s` where `hash` = ? and `type` = ?)"
sql = fmt.Sprintf(sql, Document{}.TableName(), Attachment{}.TableName())
for hash, doc := range hashMapDocs {
err = m.db.Exec(sql,
doc.Description, doc.EnableGZIP, doc.Width, doc.Height, doc.Preview, doc.Pages, DocumentStatusConverted, []int{DocumentStatusPending, DocumentStatusConverting, DocumentStatusFailed}, hash, AttachmentTypeDocument,
).Error
if err != nil {
m.logger.Error("ConvertDocument", zap.Error(err))
return
}
}
return
}
}
timeout := 30 * time.Minute
if cfg.Timeout > 0 {
timeout = time.Duration(cfg.Timeout) * time.Minute
}
cvt := converter.NewConverter(m.logger, timeout)
defer cvt.Clean() // 清除缓存目录
dstPDF, err := cvt.ConvertToPDF(localFile)
if err != nil {
m.SetDocumentStatus(document.Id, DocumentStatusFailed)
m.logger.Error("ConvertDocument", zap.Error(err))
return
}
document.Pages, _ = cvt.CountPDFPages(dstPDF)
document.Preview = cfg.MaxPreview
if document.Pages < cfg.MaxPreview {
document.Preview = document.Pages
}
// PDF截取第一章图片作为封面(封面不是最重要的,期间出现错误,不影响文档转换)
pages, err := cvt.ConvertPDFToPNG(dstPDF, 1, 1)
if err != nil {
m.logger.Error("get pdf cover", zap.Error(err))
}
if len(pages) > 0 {
coverBig := baseDir + "/cover.big.png"
util.CopyFile(pages[0].PagePath, coverBig)
util.CopyFile(pages[0].PagePath, cover)
util.CropImage(cover, DocumentCoverWidth, DocumentCoverHeight)
document.Width, document.Height, _ = util.GetImageSize(coverBig) // 页面宽高
}
// PDF转为SVG
toPage := document.Pages
if cfg.MaxPreview > 0 {
toPage = cfg.MaxPreview
}
if toPage > document.Pages {
toPage = document.Pages
}
pages, err = cvt.ConvertPDFToPages(dstPDF, 1, toPage, &converter.OptionConvertPages{
EnableSVGO: cfg.EnableSVGO,
EnableGZIP: cfg.EnableGZIP,
Extension: cfg.Extension,
})
if err != nil {
m.SetDocumentStatus(document.Id, DocumentStatusFailed)
m.logger.Error("ConvertDocument", zap.Error(err))
return
}
ext := "." + cfg.Extension
if ext == ".svg" && cfg.EnableGZIP {
ext = ".gzip.svg"
}
for _, page := range pages {
dst := fmt.Sprintf(baseDir+"/%d%s", page.PageNum, ext)
m.logger.Debug("ConvertDocument CopyFile", zap.String("src", page.PagePath), zap.String("dst", dst))
errCopy := util.CopyFile(page.PagePath, dst)
if errCopy != nil {
m.logger.Error("ConvertDocument CopyFile", zap.Error(errCopy))
}
}
// 提取PDF文本以及获取文档信息
textFile, _ := cvt.ConvertPDFToTxt(dstPDF)
util.CopyFile(textFile, baseDir+"/content.txt")
// 读取文本内容,以提取关键字和摘要
if content, errRead := os.ReadFile(textFile); errRead == nil {
contentStr := string(content)
replacer := strings.NewReplacer("\r", " ", "\n", " ", "\t", " ")
document.Description = strings.TrimSpace(replacer.Replace(util.Substr(contentStr, 255)))
}
document.Status = DocumentStatusConverted
document.EnableGZIP = cfg.EnableGZIP
document.PreviewExt = strings.TrimPrefix(ext, ".gzip")
err = m.db.Select("description", "cover", "width", "height", "preview", "pages", "status", "enable_gzip", "preview_ext").Where("id = ?", document.Id).Updates(document).Error
if err != nil {
m.SetDocumentStatus(document.Id, DocumentStatusFailed)
m.logger.Error("ConvertDocument", zap.Error(err))
}
return
}
func (m *DBModel) SetDocumentStatus(documentId int64, status int) (err error) {
err = m.db.Model(&Document{}).Where("id = ?", documentId).Update("status", status).Error
if err != nil {
m.logger.Error("SetDocumentStatus", zap.Error(err))
}
return
}
1 year ago
// 设置文章推荐状态
func (m *DBModel) SetDocumentRecommend(documentIds []int64, typ int32) (err error) {
db := m.db.Model(&Document{}).Where("id in (?)", documentIds)
switch typ {
case 0: // 取消推荐
err = db.Update("recommend_at", nil).Error
case 1: // 推荐
err = db.Where("recommend_at IS NULL").Update("recommend_at", time.Now()).Error
case 2: // 置顶
err = db.Update("recommend_at", time.Now()).Error
}
if err != nil {
m.logger.Error("SetDocumentRecommend", zap.Error(err))
}
return
}
1 year ago
func (m *DBModel) CountDocument(status ...int) (count int64, err error) {
db := m.db.Model(&Document{})
if len(status) > 0 {
db = db.Where("status in (?)", status)
}
err = db.Count(&count).Error
if err != nil {
m.logger.Error("CountDocument", zap.Error(err))
}
return
}
1 year ago
func (m *DBModel) SetDocumentsCategory(documentId, categoryId []int64) (err error) {
tx := m.db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
for _, id := range documentId {
// 1. 旧的文档分类,减少计数
var docCates []DocumentCategory
m.db.Model(&DocumentCategory{}).Where("document_id = ?", id).Find(&docCates)
for _, cate := range docCates {
err = tx.Model(&Category{}).Where("id = ?", cate.CategoryId).Update("doc_count", gorm.Expr("doc_count - ?", 1)).Error
if err != nil {
m.logger.Error("SetDocumentsCategory", zap.Error(err))
return
}
}
// 2. 删除旧的分类
err = tx.Model(&DocumentCategory{}).Where("document_id = ?", id).Delete(&DocumentCategory{}).Error
if err != nil {
m.logger.Error("SetDocumentsCategory", zap.Error(err))
return
}
// 3. 添加新的分类
docCates = []DocumentCategory{}
for _, cid := range categoryId {
docCates = append(docCates, DocumentCategory{
DocumentId: id,
CategoryId: cid,
})
}
err = tx.Create(&docCates).Error
if err != nil {
m.logger.Error("SetDocumentsCategory", zap.Error(err))
return
}
// 4. 更新文档分类统计
err = tx.Model(&Category{}).Where("id in (?)", categoryId).Update("doc_count", gorm.Expr("doc_count + ?", 1)).Error
if err != nil {
m.logger.Error("SetDocumentsCategory", zap.Error(err))
return
}
}
return
}