fixed: webdav 上传接受图片时不生成缩略图的问题 (#779)

* fix:修复webdav上传图片时无缩略图问题

* 修改 Trim 为 TrimRight

* 1.增加图片生成缩略图时自动识别EXIF信息并存储到数据库

2.修改迁移配置、manage 接口返回文件增加 exif 信息字段

3.计划任务增加自动识别将经纬度转换为文本地址任务

* 变更assets

* fix:修复前端页面合并遗留bug

* 1. 将获取更新EXIF地址的文件列表函数更名为 GetEmptyLocationFilesByPage

2.页面API展示列表接口增加过滤默认空时间

* update assets
feat-album
chauncey 4 years ago committed by GitHub
parent e44ec0e6bf
commit 959e1f2576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit e2d4f13a54dfd424cfbc129664772e104ccf97fc Subproject commit 1c827ee20a1628089cbab73ae8cbd81e2c8310c9

@ -31,6 +31,7 @@ require (
github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1
github.com/rakyll/statik v0.1.7 github.com/rakyll/statik v0.1.7
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/speps/go-hashids v2.0.0+incompatible github.com/speps/go-hashids v2.0.0+incompatible
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1

@ -208,6 +208,8 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=

@ -26,6 +26,12 @@ type File struct {
// 数据库忽略字段 // 数据库忽略字段
Position string `gorm:"-"` Position string `gorm:"-"`
// exif 信息
ExifModel string
ExifDateTime time.Time
ExifLatLong string
ExifAddress string
} }
func init() { func init() {
@ -79,6 +85,16 @@ func GetFilesByIDs(ids []uint, uid uint) ([]File, error) {
return files, result.Error return files, result.Error
} }
// GetEmptyLocationFilesByPage 分页获取所有经纬度未更新文件
func GetEmptyLocationFilesByPage(page uint, pageSize uint) ([]File, error) {
var files []File
var result *gorm.DB
var offset = int(page) * int(pageSize) - int(pageSize)
result = DB.Where("exif_lat_long != '' AND (exif_address is null or exif_address = '')").Limit(pageSize).
Offset(offset).Find(&files)
return files, result.Error
}
// GetFilesByKeywords 根据关键字搜索文件, // GetFilesByKeywords 根据关键字搜索文件,
// UID为0表示忽略用户只根据文件ID检索 // UID为0表示忽略用户只根据文件ID检索
func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) { func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) {
@ -199,6 +215,23 @@ func (file *File) UpdateSourceName(value string) error {
return DB.Model(&file).Set("gorm:association_autoupdate", false).Update("source_name", value).Error return DB.Model(&file).Set("gorm:association_autoupdate", false).Update("source_name", value).Error
} }
// UpdatePicExifModel 更新文件的设备信息
func (file *File) UpdatePicExifModel(value string) error {
return DB.Model(&file).Update("exif_model", value).Error
}
// UpdatePicExifDateTime 更新图片EXIF时间
func (file *File) UpdatePicExifDateTime(value time.Time) error {
return DB.Model(&file).Update("exif_date_time", value).Error
}
// UpdatePicExifLatLong 更新图片EXIF坐标
func (file *File) UpdatePicExifLatLong(value string) error {
return DB.Model(&file).Update("exif_lat_long", value).Error
}
// UpdatePicExifAddress 更新图片EXIF位置信息
func (file *File) UpdatePicExifAddress(value string) error {
return DB.Model(&file).Update("exif_address", value).Error
}
/* /*
webdav.FileInfo webdav.FileInfo
*/ */

@ -147,6 +147,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "home_view_method", Value: "icon", Type: "view"}, {Name: "home_view_method", Value: "icon", Type: "view"},
{Name: "share_view_method", Value: "list", Type: "view"}, {Name: "share_view_method", Value: "list", Type: "view"},
{Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"}, {Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"},
{Name: "cron_sync_photo_lat_long_to_address", Value: "@hourly", Type: "cron"},
{Name: "authn_enabled", Value: "0", Type: "authn"}, {Name: "authn_enabled", Value: "0", Type: "authn"},
{Name: "captcha_height", Value: "60", Type: "captcha"}, {Name: "captcha_height", Value: "60", Type: "captcha"},
{Name: "captcha_width", Value: "240", Type: "captcha"}, {Name: "captcha_width", Value: "240", Type: "captcha"},

@ -4,7 +4,7 @@ package conf
var BackendVersion = "3.2.1" var BackendVersion = "3.2.1"
// RequiredDBVersion 与当前版本匹配的数据库版本 // RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.2.0" var RequiredDBVersion = "3.2.1"
// RequiredStaticVersion 与当前版本匹配的静态资源版本 // RequiredStaticVersion 与当前版本匹配的静态资源版本
var RequiredStaticVersion = "3.2.1" var RequiredStaticVersion = "3.2.1"

@ -0,0 +1,108 @@
package crontab
import (
"net/http"
"encoding/json"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
)
type AddressJson struct {
Status int `json:"status"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Result struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
Address string `json:"address"`
FormattedAddresses struct {
Recommend string `json:"recommend"`
Rough string `json:"rough"`
} `json:"formatted_addresses"`
AddressComponent struct {
Nation string `json:"nation"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Street string `json:"street"`
StreetNumber string `json:"street_number"`
} `json:"address_component"`
} `json:"result"`
}
type AddressExif struct{
Address string `json:"address"`
Nation string `json:"nation"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Street string `json:"street"`
StreetNumber string `json:"street_number"`
}
func syncPhotoAddress() {
// 同步照片的经纬度为文本地址
syncPhotoLatLongToAddress()
util.Log().Info("定时任务 [cron_sync_photo_lat_long_to_address] 执行完毕")
}
func syncPhotoLatLongToAddress() {
page := 1
pageSize := 10
for true{
files , _ := model.GetEmptyLocationFilesByPage(uint(page), uint(pageSize))
if len(files) <= 0 {
break
}
for i := 0; i < len(files); i++{
file := files[i]
util.Log().Debug("file name: %s",file.Name)
util.Log().Debug("file ExifLatLong: %s",file.ExifLatLong)
util.Log().Debug("file ExifAddress: %s",file.ExifAddress)
// 获取文件数据流
url := "https://apis.map.qq.com/ws/geocoder/v1/?location="+ file.ExifLatLong +"&get_poi=1&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
client := request.HTTPClient{}
resp := client.Request(
"GET",
url,
nil,
request.WithHeader(
http.Header{"Referer": {"https://lbs.qq.com/"}},
),
)
respString, err := resp.GetResponse()
if err != nil{
util.Log().Warning("response error: %s",err)
}
var addressJson AddressJson
err = json.Unmarshal([]byte(respString), &addressJson)
if err != nil {
util.Log().Warning("解析经纬度结果错误原始文本: %s",respString)
util.Log().Warning("解析经纬度结果错误: %s",err)
continue
}
var addressInfo AddressExif
addressInfo.Address = addressJson.Result.Address
addressInfo.StreetNumber = addressJson.Result.AddressComponent.StreetNumber
addressInfo.District = addressJson.Result.AddressComponent.District
addressInfo.City = addressJson.Result.AddressComponent.City
addressInfo.Province = addressJson.Result.AddressComponent.Province
addressInfo.Nation = addressJson.Result.AddressComponent.Nation
if addressInfoStr, err := json.Marshal(addressInfo); err == nil {
file.UpdatePicExifAddress(string(addressInfoStr))
util.Log().Debug("解析经纬度结果重组: %s",string(addressInfoStr))
}
}
page++
}
}

@ -21,13 +21,15 @@ func Reload() {
func Init() { func Init() {
util.Log().Info("初始化定时任务...") util.Log().Info("初始化定时任务...")
// 读取cron日程设置 // 读取cron日程设置
options := model.GetSettingByNames("cron_garbage_collect") options := model.GetSettingByNames("cron_garbage_collect","cron_sync_photo_lat_long_to_address")
Cron := cron.New() Cron := cron.New()
for k, v := range options { for k, v := range options {
var handler func() var handler func()
switch k { switch k {
case "cron_garbage_collect": case "cron_garbage_collect":
handler = garbageCollect handler = garbageCollect
case "cron_sync_photo_lat_long_to_address":
handler = syncPhotoAddress
default: default:
util.Log().Warning("未知定时任务类型 [%s],跳过", k) util.Log().Warning("未知定时任务类型 [%s],跳过", k)
continue continue

@ -321,6 +321,7 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
fs.recycleLock.Lock() fs.recycleLock.Lock()
go func() { go func() {
defer fs.recycleLock.Unlock() defer fs.recycleLock.Unlock()
file.Name = strings.TrimRight(file.Name, ".tacitpart")
fs.GenerateThumbnail(ctx, file) fs.GenerateThumbnail(ctx, file)
}() }()
} }

@ -11,6 +11,8 @@ import (
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/thumb" "github.com/cloudreve/Cloudreve/v3/pkg/thumb"
"github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/rwcarlsen/goexif/exif"
) )
/* ================ /* ================
@ -54,7 +56,6 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
if !IsInExtensionList(HandledExtension, file.Name) { if !IsInExtensionList(HandledExtension, file.Name) {
return return
} }
// 新建上下文 // 新建上下文
newCtx, cancel := context.WithCancel(context.Background()) newCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -65,7 +66,6 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
return return
} }
defer source.Close() defer source.Close()
image, err := thumb.NewThumbFromFile(source, file.Name) image, err := thumb.NewThumbFromFile(source, file.Name)
if err != nil { if err != nil {
util.Log().Warning("生成缩略图时无法解析 [%s] 图像数据:%s", file.SourceName, err) util.Log().Warning("生成缩略图时无法解析 [%s] 图像数据:%s", file.SourceName, err)
@ -74,7 +74,6 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
// 获取原始图像尺寸 // 获取原始图像尺寸
w, h := image.GetSize() w, h := image.GetSize()
// 生成缩略图 // 生成缩略图
image.GetThumb(fs.GenerateThumbnailSize(w, h)) image.GetThumb(fs.GenerateThumbnailSize(w, h))
// 保存到文件 // 保存到文件
@ -83,7 +82,6 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
util.Log().Warning("无法保存缩略图:%s", err) util.Log().Warning("无法保存缩略图:%s", err)
return return
} }
// 更新文件的图像信息 // 更新文件的图像信息
if file.Model.ID > 0 { if file.Model.ID > 0 {
err = file.UpdatePicInfo(fmt.Sprintf("%d,%d", w, h)) err = file.UpdatePicInfo(fmt.Sprintf("%d,%d", w, h))
@ -91,6 +89,40 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
file.PicInfo = fmt.Sprintf("%d,%d", w, h) file.PicInfo = fmt.Sprintf("%d,%d", w, h)
} }
// 更新文件的图像信息
// 记录文件句柄位置并还原, 获取 exif 信息
currentPosition, err := source.Seek(0, 1)
source.Seek(0,0)
x, err := exif.Decode(source)
source.Seek(currentPosition, 0)
if err != nil {
util.Log().Warning("照片解析EXIF失败%s", err)
}else{
ExifCamModel, _ := x.Get(exif.Model)
file.ExifModel,_ = ExifCamModel.StringVal()
ExifDateTime, _ := x.DateTime()
if !ExifDateTime.IsZero() {
file.ExifDateTime = ExifDateTime
}
lat, long, _ := x.LatLong()
if lat > 0 && long > 0 {
file.ExifLatLong = fmt.Sprintf("%f,%f", lat, long)
}
util.Log().Debug("照片的经纬度:%f,%f", lat,long)
if file.Model.ID > 0 {
file.UpdatePicExifModel(file.ExifModel)
if !ExifDateTime.IsZero() {
file.UpdatePicExifDateTime(file.ExifDateTime)
}
if lat > 0 && long > 0 {
file.UpdatePicExifLatLong(file.ExifLatLong)
}
}
}
// 失败时删除缩略图文件 // 失败时删除缩略图文件
if err != nil { if err != nil {
_, _ = fs.Handler.Delete(newCtx, []string{file.SourceName + conf.ThumbConfig.FileSuffix}) _, _ = fs.Handler.Delete(newCtx, []string{file.SourceName + conf.ThumbConfig.FileSuffix})

@ -28,6 +28,10 @@ type Object struct {
Type string `json:"type"` Type string `json:"type"`
Date string `json:"date"` Date string `json:"date"`
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
ExifModel string `json:"exif_model"`
ExifDateTime string `json:"exif_date_time"`
ExifLatLong string `json:"exif_lat_long"`
ExifAddress string `json:"exit_address"`
} }
// Rename 重命名对象 // Rename 重命名对象
@ -350,6 +354,10 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo
Size: 0, Size: 0,
Type: "dir", Type: "dir",
Date: subFolder.CreatedAt.Format("2006-01-02 15:04:05"), Date: subFolder.CreatedAt.Format("2006-01-02 15:04:05"),
ExifModel: "",
ExifDateTime: "",
ExifLatLong: "",
ExifAddress: "",
}) })
} }
@ -361,7 +369,10 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo
processedPath = parent processedPath = parent
} }
} }
ExifDateTimeText := ""
if !file.ExifDateTime.IsZero(){
ExifDateTimeText = file.ExifDateTime.Format("2006-01-02 15:04:05")
}
newFile := Object{ newFile := Object{
ID: hashid.HashID(file.ID, hashid.FileID), ID: hashid.HashID(file.ID, hashid.FileID),
Name: file.Name, Name: file.Name,
@ -370,6 +381,10 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo
Size: file.Size, Size: file.Size,
Type: "file", Type: "file",
Date: file.CreatedAt.Format("2006-01-02 15:04:05"), Date: file.CreatedAt.Format("2006-01-02 15:04:05"),
ExifModel: file.ExifModel,
ExifDateTime: ExifDateTimeText,
ExifLatLong: file.ExifLatLong,
ExifAddress: file.ExifAddress,
} }
if shareKey != "" { if shareKey != "" {
newFile.Key = shareKey newFile.Key = shareKey

Loading…
Cancel
Save