|
|
package model
|
|
|
|
|
|
import (
|
|
|
"path/filepath"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
"gorm.io/gorm"
|
|
|
)
|
|
|
|
|
|
// TODO: 附件管理,需要有一个定时任务,定时根据type和type_id清理无效的附件数据,同时删除无效的文件
|
|
|
|
|
|
const (
|
|
|
AttachmentTypeUnknown = iota // 未知
|
|
|
AttachmentTypeAvatar // 用户头像
|
|
|
AttachmentTypeDocument // 文档
|
|
|
AttachmentTypeArticle // 文章
|
|
|
AttachmentTypeComment // 评论
|
|
|
AttachmentTypeBanner // 横幅
|
|
|
AttachmentTypeCategoryCover // 分类封面
|
|
|
AttachmentTypeConfig // 配置
|
|
|
)
|
|
|
|
|
|
var attachmentTypeName = map[int]string{
|
|
|
AttachmentTypeAvatar: "头像",
|
|
|
AttachmentTypeArticle: "文章",
|
|
|
AttachmentTypeBanner: "横幅",
|
|
|
AttachmentTypeCategoryCover: "分类封面",
|
|
|
AttachmentTypeComment: "评论",
|
|
|
AttachmentTypeDocument: "文档",
|
|
|
AttachmentTypeConfig: "配置",
|
|
|
}
|
|
|
|
|
|
type Attachment struct {
|
|
|
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:附件 id;"`
|
|
|
Hash string `form:"hash" json:"hash,omitempty" gorm:"column:hash;type:char(32);size:32;index:hash;comment:文件MD5;"`
|
|
|
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);default:0;index:user_id;comment:用户 id;"`
|
|
|
TypeId int64 `form:"type_id" json:"type_id,omitempty" gorm:"column:type_id;type:bigint(20);default:0;index:idx_type_id;comment:类型数据ID,对应与用户头像时,则为用户id,对应为文档时,则为文档ID;"`
|
|
|
Type int `form:"type" json:"type,omitempty" gorm:"column:type;type:smallint(5);default:0;index:idx_type;comment:附件类型(0 未知,1 头像,2 文档,3 文章附件 ...);"`
|
|
|
Enable bool `form:"enable" json:"enable,omitempty" gorm:"column:enable;type:tinyint(3);default:1;comment:是否合法;"`
|
|
|
Path string `form:"path" json:"path,omitempty" gorm:"column:path;type:varchar(255);size:255;comment:文件存储路径;"`
|
|
|
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(255);size:255;comment:文件原名称;"`
|
|
|
Size int64 `form:"size" json:"size,omitempty" gorm:"column:size;type:bigint(20);default:0;comment:文件大小;"`
|
|
|
Width int `form:"width" json:"width,omitempty" gorm:"column:width;type:int(11);default:0;comment:宽度;"`
|
|
|
Height int `form:"height" json:"height,omitempty" gorm:"column:height;type:int(11);default:0;comment:高度;"`
|
|
|
Ext string `form:"ext" json:"ext,omitempty" gorm:"column:ext;type:varchar(32);size:32;comment:文件类型,如 .pdf 。统一处理成小写;"`
|
|
|
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(64);size:64;comment:上传文档的用户IP地址;"`
|
|
|
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(255);size:255;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:更新时间;"`
|
|
|
DeletedAt *gorm.DeletedAt `form:"deleted_at" json:"deleted_at,omitempty" gorm:"column:deleted_at;type:datetime;comment:删除时间;index:idx_deleted_at"`
|
|
|
}
|
|
|
|
|
|
func (Attachment) TableName() string {
|
|
|
return tablePrefix + "attachment"
|
|
|
}
|
|
|
|
|
|
// CreateAttachment 创建Attachment
|
|
|
func (m *DBModel) CreateAttachment(attachment *Attachment) (err error) {
|
|
|
err = m.db.Create(attachment).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("CreateAttachment", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// CreateAttachment 创建Attachment
|
|
|
func (m *DBModel) CreateAttachments(attachments []*Attachment) (err error) {
|
|
|
err = m.db.Create(attachments).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("CreateAttachment", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// GetAttachmentTypeName 获取附件类型名称
|
|
|
func (m *DBModel) GetAttachmentTypeName(typ int) string {
|
|
|
name, _ := attachmentTypeName[typ]
|
|
|
return name
|
|
|
}
|
|
|
|
|
|
// UpdateAttachment 更新Attachment,如果需要更新指定字段,则请指定updateFields参数
|
|
|
func (m *DBModel) UpdateAttachment(attachment *Attachment, updateFields ...string) (err error) {
|
|
|
db := m.db.Model(attachment)
|
|
|
tableName := Attachment{}.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 = ?", attachment.Id).Updates(attachment).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("UpdateAttachment", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// GetAttachment 根据id获取Attachment
|
|
|
func (m *DBModel) GetAttachment(id int64, fields ...string) (attachment Attachment, err error) {
|
|
|
db := m.db
|
|
|
|
|
|
fields = m.FilterValidFields(Attachment{}.TableName(), fields...)
|
|
|
if len(fields) > 0 {
|
|
|
db = db.Select(fields)
|
|
|
}
|
|
|
|
|
|
err = db.Where("id = ?", id).First(&attachment).Error
|
|
|
return
|
|
|
}
|
|
|
|
|
|
type OptionGetAttachmentList 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
|
|
|
}
|
|
|
|
|
|
// GetAttachmentList 获取Attachment列表
|
|
|
func (m *DBModel) GetAttachmentList(opt *OptionGetAttachmentList) (attachmentList []Attachment, total int64, err error) {
|
|
|
tableName := Attachment{}.TableName()
|
|
|
db := m.db.Model(&Attachment{})
|
|
|
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("GetAttachmentList", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
|
|
|
opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
|
|
|
if len(opt.SelectFields) > 0 {
|
|
|
db = db.Select(opt.SelectFields)
|
|
|
}
|
|
|
|
|
|
// TODO: 没有排序参数的话,可以自行指定排序字段
|
|
|
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(&attachmentList).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetAttachmentList", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// DeleteAttachment 删除数据
|
|
|
// TODO: 删除数据之后,存在 attachment_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
|
|
|
// TODO: 检查是否有相同hash的文件存在,没有的话,需要同时删除文件
|
|
|
func (m *DBModel) DeleteAttachment(ids []int64) (err error) {
|
|
|
err = m.db.Where("id in (?)", ids).Delete(&Attachment{}).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("DeleteAttachment", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
func (m *DBModel) GetAttachmentByTypeAndTypeId(typ int, typeId int64, fields ...string) (attachment Attachment) {
|
|
|
db := m.db
|
|
|
if len(fields) > 0 {
|
|
|
db = db.Select(fields)
|
|
|
}
|
|
|
err := db.Where("type = ? and type_id = ?", typ, typeId).Last(&attachment).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetAttachmentByTypeAndTypeId", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
func (m *DBModel) setAttachmentType(attachmentType int, attachmentTypeId int64, paths []string) {
|
|
|
var hashes []string
|
|
|
for _, path := range paths {
|
|
|
if strings.HasPrefix(strings.TrimLeft(path, "."), "/uploads/") {
|
|
|
filename := filepath.Base(path)
|
|
|
hashes = append(hashes, strings.TrimSuffix(filename, filepath.Ext(filename)))
|
|
|
}
|
|
|
}
|
|
|
if len(hashes) > 0 {
|
|
|
err := m.db.Model(&Attachment{}).Where("hash in (?) and type = ? and type_id = 0", hashes, attachmentType).Update("type_id", attachmentTypeId).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("setAttachmentType", zap.Error(err))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置附件type_id。
|
|
|
// attachIdTypeIdMap map[attachment_id]type_id
|
|
|
func (m *DBModel) SetAttachmentTypeId(attachIdTypeIdMap map[int64]int64) {
|
|
|
if len(attachIdTypeIdMap) == 0 {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
var err error
|
|
|
sess := m.db.Begin()
|
|
|
defer func() {
|
|
|
if err != nil {
|
|
|
sess.Rollback()
|
|
|
} else {
|
|
|
sess.Commit()
|
|
|
}
|
|
|
}()
|
|
|
|
|
|
for attachmentId, typeId := range attachIdTypeIdMap {
|
|
|
err = sess.Model(&Attachment{}).
|
|
|
Where("id = ?", attachmentId).
|
|
|
Update("type_id", typeId).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("SetAttachmentTypeId", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
}
|