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.

236 lines
8.7 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 (
"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
}
}
}