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.

223 lines
6.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package model
import (
"fmt"
"path/filepath"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Article struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Identifier string `form:"identifier" json:"identifier,omitempty" gorm:"column:identifier;type:varchar(64);size:64;index:identifier,unique;comment:文章标识,唯一;"`
Author string `form:"author" json:"author,omitempty" gorm:"column:author;type:varchar(64);size:64;comment:作者;"`
ViewCount int `form:"view_count" json:"view_count,omitempty" gorm:"column:view_count;type:int(11);size:11;default:0;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:摘要;"`
Content string `form:"content" json:"content,omitempty" gorm:"column:content;type:longtext;comment:内容;"`
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:更新时间;"`
}
func (Article) TableName() string {
return tablePrefix + "article"
}
// CreateArticle 创建Article
func (m *DBModel) CreateArticle(article *Article) (err error) {
err = m.db.Create(article).Error
if err != nil {
m.logger.Error("CreateArticle", zap.Error(err))
return
}
m.checkArticleFile(article)
return
}
// UpdateArticle 更新Article如果需要更新指定字段则请指定updateFields参数
// 注意不支持更新identifier
func (m *DBModel) UpdateArticle(article *Article, updateFields ...string) (err error) {
db := m.db.Model(article)
tableName := Article{}.TableName()
updateFields = m.FilterValidFields(tableName, updateFields...)
if len(updateFields) > 0 { // 更新指定字段
db = db.Select(updateFields)
} else { // 更新全部字段,包括零值字段
db = db.Select(m.GetTableFields(tableName))
}
err = db.Where("id = ?", article.Id).Omit("identifier").Updates(article).Error
if err != nil {
m.logger.Error("UpdateArticle", zap.Error(err))
}
m.checkArticleFile(article)
return
}
// UpdateArticleViewCount 更新浏览量
func (m *DBModel) UpdateArticleViewCount(id int64, viewCount int) (err error) {
sql := fmt.Sprintf("update %s set view_count=? where id=?", Article{}.TableName())
err = m.db.Exec(sql, viewCount, id).Error
if err != nil {
m.logger.Error("UpdateArticleViewCount", zap.Error(err))
}
return
}
// GetArticle 根据id获取Article
func (m *DBModel) GetArticle(id interface{}, fields ...string) (article Article, err error) {
db := m.db
fields = m.FilterValidFields(Article{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&article).Error
return
}
// GetArticleByIdentifier(identifier string, fields ...string) 根据唯一索引获取Article
func (m *DBModel) GetArticleByIdentifier(identifier string, fields ...string) (article Article, err error) {
db := m.db
fields = m.FilterValidFields(Article{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
db = db.Where("identifier = ?", identifier)
err = db.First(&article).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetArticleByIdentifier", zap.Error(err))
return
}
return
}
type OptionGetArticleList 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
}
// GetArticleList 获取Article列表
func (m *DBModel) GetArticleList(opt *OptionGetArticleList) (articleList []Article, total int64, err error) {
tableName := Article{}.TableName()
db := m.db.Model(&Article{})
db = m.generateQueryRange(db, tableName, opt.QueryRange)
db = m.generateQueryIn(db, tableName, opt.QueryIn)
db = m.generateQueryLike(db, tableName, opt.QueryLike)
if len(opt.Ids) > 0 {
db = db.Where("id in (?)", opt.Ids)
}
if opt.WithCount {
err = db.Count(&total).Error
if err != nil {
m.logger.Error("GetArticleList", zap.Error(err))
return
}
}
opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
}
if len(opt.Sort) > 0 {
db = m.generateQuerySort(db, tableName, opt.Sort)
} else {
db = db.Order("id desc")
}
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
err = db.Find(&articleList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetArticleList", zap.Error(err))
}
return
}
// DeleteArticle 删除数据
func (m *DBModel) DeleteArticle(ids []int64) (err error) {
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
err = sess.Where("id in (?)", ids).Delete(&Article{}).Error
if err != nil {
m.logger.Error("DeleteArticle", zap.Error(err))
return
}
// 附件标记删除将type_id设置为0
err = sess.Model(&Attachment{}).Where("type = ? and type_id in (?)", AttachmentTypeArticle, ids).Update("type_id", 0).Error
if err != nil {
m.logger.Error("DeleteArticle", zap.Error(err))
return
}
return
}
// checkArticleFile 检查文章中的文件,包括音频视频和图片等
func (m *DBModel) checkArticleFile(article *Article) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content))
if err != nil {
m.logger.Error("checkArticleFile", zap.Error(err))
return
}
var (
hashes []string
tags = []string{"img", "video", "audio"}
)
for _, tag := range tags {
doc.Find(tag).Each(func(i int, selection *goquery.Selection) {
src, ok := selection.Attr("src")
if !ok {
src, ok = selection.Find("source").Attr("src")
}
if ok && strings.HasPrefix(src, "/uploads/") {
src = strings.Split(src, "?")[0]
hashes = append(hashes, strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)))
}
})
}
if len(hashes) > 0 { // 更新内容ID
err = m.db.Model(&Attachment{}).Where("hash in (?) and type = ? and type_id = 0", hashes, AttachmentTypeArticle).Update("type_id", article.Id).Error
if err != nil {
m.logger.Error("checkArticleFile", zap.Error(err))
}
}
}
func (m *DBModel) CountArticle() (count int64, err error) {
err = m.db.Model(&Article{}).Count(&count).Error
return
}