Abstract the file download things.

This commit is contained in:
zicla
2018-11-27 21:03:11 +08:00
parent 8cede9c087
commit 074580a26b
10 changed files with 891 additions and 716 deletions

View File

@ -17,6 +17,7 @@ type AlienController struct {
matterService *MatterService
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
alienService *AlienService
}
//初始化方法
@ -54,6 +55,11 @@ func (this *AlienController) Init(context *Context) {
this.imageCacheService = c
}
b = context.GetBean(this.alienService)
if c, ok := b.(*AlienService); ok {
this.alienService = c
}
}
//注册自己的路由。
@ -77,17 +83,27 @@ func (this *AlienController) HandleRoutes(writer http.ResponseWriter, request *h
path := request.URL.Path
//匹配 /api/alien/download/{uuid}/{filename}
reg := regexp.MustCompile(`^/api/alien/download/([^/]+)/([^/]+)$`)
//匹配 /api/alien/preview/{uuid}/{filename} (响应头不包含 content-disposition)
reg := regexp.MustCompile(`^/api/alien/preview/([^/]+)/([^/]+)$`)
strs := reg.FindStringSubmatch(path)
if len(strs) != 3 {
return nil, false
} else {
if len(strs) == 3 {
var f = func(writer http.ResponseWriter, request *http.Request) {
this.Preview(writer, request, strs[1], strs[2])
}
return f, true
}
//匹配 /api/alien/download/{uuid}/{filename} (响应头包含 content-disposition)
reg = regexp.MustCompile(`^/api/alien/download/([^/]+)/([^/]+)$`)
strs = reg.FindStringSubmatch(path)
if len(strs) == 3 {
var f = func(writer http.ResponseWriter, request *http.Request) {
this.Download(writer, request, strs[1], strs[2])
}
return f, true
}
return nil, false
}
//直接使用邮箱和密码获取用户
@ -385,71 +401,17 @@ func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, requ
}
//下载一个文件。既可以使用登录的方式下载,也可以使用授权的方式下载。
func (this *AlienController) Download(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
matter := this.matterDao.CheckByUuid(uuid)
//判断是否是文件夹
if matter.Dir {
panic("暂不支持下载文件夹")
}
if matter.Name != filename {
panic("文件信息错误")
}
//验证用户的权限问题。
//文件如果是私有的才需要权限
if matter.Privacy {
//1.如果带有downloadTokenUuid那么就按照token的信息去获取。
downloadTokenUuid := request.FormValue("downloadTokenUuid")
if downloadTokenUuid != "" {
downloadToken := this.downloadTokenDao.CheckByUuid(downloadTokenUuid)
if downloadToken.ExpireTime.Before(time.Now()) {
panic("downloadToken已失效")
}
if downloadToken.MatterUuid != uuid {
panic("token和文件信息不一致")
}
tokenUser := this.userDao.CheckByUuid(downloadToken.UserUuid)
if matter.UserUuid != tokenUser.Uuid {
panic(CODE_WRAPPER_UNAUTHORIZED)
}
//下载之后立即过期掉。
downloadToken.ExpireTime = time.Now().AddDate(0, 0, 1);
this.downloadTokenDao.Save(downloadToken)
} else {
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(CODE_WRAPPER_UNAUTHORIZED)
}
}
}
//对图片处理。
needProcess, _, _, _ := this.imageCacheService.ResizeParams(request)
if needProcess {
//如果是图片,那么能用缓存就用缓存
imageCache := this.imageCacheDao.FindByUri(request.RequestURI)
if imageCache == nil {
imageCache = this.imageCacheService.cacheImage(writer, request, matter)
}
//直接使用缓存中的信息
this.matterService.DownloadFile(writer, request, CONFIG.MatterPath+imageCache.Path, matter.Name)
} else {
this.matterService.DownloadFile(writer, request, CONFIG.MatterPath+matter.Path, matter.Name)
}
//预览一个文件。既可以使用登录的方式,也可以使用授权的方式
func (this *AlienController) Preview(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
operator := this.findUser(writer, request)
this.alienService.PreviewOrDownload(writer, request, uuid, filename, operator, false)
}
//下载一个文件。既可以使用登录的方式,也可以使用授权的方式
func (this *AlienController) Download(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
operator := this.findUser(writer, request)
this.alienService.PreviewOrDownload(writer, request, uuid, filename, operator, true)
}

135
rest/alien_service.go Normal file
View File

@ -0,0 +1,135 @@
package rest
import (
"net/http"
"time"
"fmt"
)
//@Service
type AlienService struct {
Bean
matterDao *MatterDao
matterService *MatterService
userDao *UserDao
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法
func (this *AlienService) Init(context *Context) {
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := context.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = context.GetBean(this.matterService)
if b, ok := b.(*MatterService); ok {
this.matterService = b
}
b = context.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = context.GetBean(this.uploadTokenDao)
if c, ok := b.(*UploadTokenDao); ok {
this.uploadTokenDao = c
}
b = context.GetBean(this.downloadTokenDao)
if c, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = c
}
b = context.GetBean(this.imageCacheDao)
if c, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = c
}
b = context.GetBean(this.imageCacheService)
if c, ok := b.(*ImageCacheService); ok {
this.imageCacheService = c
}
}
//预览或者下载的统一处理.
func (this *AlienService) PreviewOrDownload(
writer http.ResponseWriter,
request *http.Request,
uuid string,
filename string,
operator *User,
withContentDisposition bool) {
LogInfo("预览或下载文件 " + uuid + " " + filename)
matter := this.matterDao.CheckByUuid(uuid)
//判断是否是文件夹
if matter.Dir {
panic("不支持下载文件夹")
}
if matter.Name != filename {
panic("文件信息错误")
}
//验证用户的权限问题。
//文件如果是私有的才需要权限
if matter.Privacy {
//1.如果带有downloadTokenUuid那么就按照token的信息去获取。
downloadTokenUuid := request.FormValue("downloadTokenUuid")
if downloadTokenUuid != "" {
downloadToken := this.downloadTokenDao.CheckByUuid(downloadTokenUuid)
if downloadToken.ExpireTime.Before(time.Now()) {
panic("downloadToken已失效")
}
if downloadToken.MatterUuid != uuid {
panic("token和文件信息不一致")
}
tokenUser := this.userDao.CheckByUuid(downloadToken.UserUuid)
if matter.UserUuid != tokenUser.Uuid {
panic(CODE_WRAPPER_UNAUTHORIZED)
}
//下载之后立即过期掉。
downloadToken.ExpireTime = time.Now().AddDate(0, 0, 1);
this.downloadTokenDao.Save(downloadToken)
} else {
//判断文件的所属人是否正确
if operator == nil || (operator.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != operator.Uuid) {
panic(CODE_WRAPPER_UNAUTHORIZED)
}
}
}
//对图片处理。
needProcess, imageResizeM, imageResizeW, imageResizeH := this.imageCacheService.ResizeParams(request)
if needProcess {
//如果是图片,那么能用缓存就用缓存
imageCache := this.imageCacheDao.FindByMatterUuidAndMode(matter.Uuid, fmt.Sprintf("%s_%d_%d", imageResizeM, imageResizeW, imageResizeH))
if imageCache == nil {
imageCache = this.imageCacheService.cacheImage(writer, request, matter)
}
//直接使用缓存中的信息
this.matterService.DownloadFile(writer, request, CONFIG.MatterPath+imageCache.Path, matter.Name, withContentDisposition)
} else {
this.matterService.DownloadFile(writer, request, CONFIG.MatterPath+matter.Path, matter.Name, withContentDisposition)
}
}

View File

@ -131,6 +131,7 @@ func (this *BaseController) Error(err interface{}) *WebResult {
return webResult
}
//能找到一个user就找到一个遇到问题直接抛出错误
func (this *BaseController) checkLogin(writer http.ResponseWriter, request *http.Request) (*Session, *User) {
//验证用户是否已经登录。
@ -159,11 +160,35 @@ func (this *BaseController) checkLogin(writer http.ResponseWriter, request *http
}
func (this *BaseController) checkUser(writer http.ResponseWriter, request *http.Request) *User {
//能找到一个user就找到一个
func (this *BaseController) findUser(writer http.ResponseWriter, request *http.Request) *User {
//验证用户是否已经登录。
sessionCookie, err := request.Cookie(COOKIE_AUTH_KEY)
if err != nil {
LogError("找不到任何登录信息")
return nil
}
session := this.sessionDao.FindByUuid(sessionCookie.Value)
if session != nil {
if session.ExpireTime.Before(time.Now()) {
LogError("登录信息已过期")
return nil
} else {
user := this.userDao.FindByUuid(session.UserUuid)
if user != nil {
return user
}
}
}
return nil
}
func (this *BaseController) checkUser(writer http.ResponseWriter, request *http.Request) *User {
_, user := this.checkLogin(writer, request)
return user
}
//允许跨域请求

View File

@ -95,6 +95,7 @@ func (this *Context) registerBeans() {
//alien
this.registerBean(new(AlienController))
this.registerBean(new(AlienService))
//downloadToken
this.registerBean(new(DownloadTokenDao))

View File

@ -7,6 +7,7 @@ import (
"github.com/nu7hatch/gouuid"
"os"
"time"
"strings"
)
type ImageCacheDao struct {
@ -38,11 +39,17 @@ func (this *ImageCacheDao) CheckByUuid(uuid string) *ImageCache {
}
//按照名字查询文件夹
func (this *ImageCacheDao) FindByUri(uri string) *ImageCache {
func (this *ImageCacheDao) FindByMatterUuidAndMode(matterUuid string, mode string) *ImageCache {
var wp = &WherePair{}
wp = wp.And(&WherePair{Query: "uri = ?", Args: []interface{}{uri}})
if matterUuid != "" {
wp = wp.And(&WherePair{Query: "matter_uuid = ?", Args: []interface{}{matterUuid}})
}
if mode != "" {
wp = wp.And(&WherePair{Query: "mode = ?", Args: []interface{}{mode}})
}
var imageCache = &ImageCache{}
db := this.context.DB.Model(&ImageCache{}).Where(wp.Query, wp.Args...).First(imageCache)
@ -107,6 +114,7 @@ func (this *ImageCacheDao) Page(page int, pageSize int, userUuid string, matterU
return pager
}
//创建
func (this *ImageCacheDao) Create(imageCache *ImageCache) *ImageCache {
@ -130,18 +138,36 @@ func (this *ImageCacheDao) Save(imageCache *ImageCache) *ImageCache {
return imageCache
}
//删除一个文件包括文件夹
func (this *ImageCacheDao) deleteFileAndDir(imageCache *ImageCache) {
filePath := CONFIG.MatterPath + imageCache.Path
//递归找寻文件的上级目录uuid. 因为是/开头的缘故
parts := strings.Split(imageCache.Path, "/")
dirPath := CONFIG.MatterPath + "/" + parts[1] + "/" + parts[2] + "/" + parts[3] + "/" + parts[4]
//删除文件
err := os.Remove(filePath)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件%s出错不做任何处理 %s", filePath, err.Error()))
}
//删除这一层文件夹
err = os.Remove(dirPath)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件夹%s出错不做任何处理 %s", dirPath, err.Error()))
}
}
//删除一个文件,数据库中删除,物理磁盘上删除。
func (this *ImageCacheDao) Delete(imageCache *ImageCache) {
db := this.context.DB.Delete(&imageCache)
this.PanicError(db.Error)
//删除文件
err := os.Remove(CONFIG.MatterPath + imageCache.Path)
this.deleteFileAndDir(imageCache)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件出错,不做任何处理 %s", err.Error()))
}
}
//删除一个matter对应的所有缓存
@ -162,10 +188,7 @@ func (this *ImageCacheDao) DeleteByMatterUuid(matterUuid string) {
//删除文件实体
for _, imageCache := range imageCaches {
err := os.Remove(CONFIG.MatterPath + imageCache.Path)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件出错,不做任何处理"))
}
this.deleteFileAndDir(imageCache)
}
}

View File

@ -7,7 +7,7 @@ type ImageCache struct {
Base
UserUuid string `json:"userUuid"`
MatterUuid string `json:"matterUuid"`
Uri string `json:"uri"`
Mode string `json:"mode"`
Md5 string `json:"md5"`
Size int64 `json:"size"`
Path string `json:"path"`

View File

@ -6,8 +6,8 @@ import (
"os"
"strconv"
"github.com/disintegration/imaging"
"net/url"
"strings"
"fmt"
)
//@Service
@ -108,7 +108,6 @@ func (this *ImageCacheService) ResizeParams(request *http.Request) (needProcess
}
return true, imageResizeM, imageResizeW, imageResizeH
} else {
LogInfo("没有有效的处理参数,不进行图片处理")
return false, "", 0, 0
}
@ -168,11 +167,8 @@ func (this *ImageCacheService) ResizeImage(request *http.Request, filePath strin
//缓存一张处理完毕了的图片
func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *http.Request, matter *Matter) *ImageCache {
// 防止中文乱码
fileName := url.QueryEscape(matter.Name)
//当前的文件是否是图片,只有图片才能处理。
extension := GetExtension(fileName)
extension := GetExtension(matter.Name)
formats := map[string]imaging.Format{
".jpg": imaging.JPEG,
".jpeg": imaging.JPEG,
@ -194,8 +190,8 @@ func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *h
user := this.userDao.FindByUuid(matter.UserUuid)
//获取文件应该存放在的物理路径的绝对路径和相对路径。
absolutePath, relativePath := GetUserFilePath(user.Username, true)
absolutePath = absolutePath + "/" + fileName
relativePath = relativePath + "/" + fileName
absolutePath = absolutePath + "/" + matter.Name
relativePath = relativePath + "/" + matter.Name
fileWriter, err := os.Create(absolutePath)
this.PanicError(err)
@ -209,11 +205,13 @@ func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *h
fileInfo, err := fileWriter.Stat()
this.PanicError(err)
_, imageResizeM, imageResizeW, imageResizeH := this.ResizeParams(request)
//相关信息写到缓存中去
imageCache := &ImageCache{
UserUuid: matter.UserUuid,
MatterUuid: matter.Uuid,
Uri: request.RequestURI,
Mode: fmt.Sprintf("%s_%d_%d", imageResizeM, imageResizeW, imageResizeH),
Size: fileInfo.Size(),
Path: relativePath,
}

View File

@ -7,6 +7,7 @@ import (
"github.com/nu7hatch/gouuid"
"os"
"time"
"strings"
)
type MatterDao struct {
@ -238,11 +239,21 @@ func (this *MatterDao) Delete(matter *Matter) {
//删除对应的缓存图片。
this.imageCacheDao.DeleteByMatterUuid(matter.Uuid)
//删除文件
err := os.Remove(CONFIG.MatterPath + matter.Path)
filePath := CONFIG.MatterPath + matter.Path
//递归找寻文件的上级目录uuid. 因为是/开头的缘故
parts := strings.Split(matter.Path, "/")
dirPath := CONFIG.MatterPath + "/" + parts[1] + "/" + parts[2] + "/" + parts[3]
//删除文件
err := os.Remove(filePath)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件出错,不做任何处理"))
LogError(fmt.Sprintf("删除磁盘上的文件出错,不做任何处理 %s", err.Error()))
}
//删除这一层文件夹
err = os.Remove(dirPath)
if err != nil {
LogError(fmt.Sprintf("删除磁盘上的文件夹出错,不做任何处理 %s", err.Error()))
}
}

View File

@ -4,16 +4,15 @@ import (
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"net/url"
)
//@Service
@ -431,20 +430,22 @@ func (this *MatterService) sumRangesSize(ranges []httpRange) (size int64) {
//文件下载。具有进度功能。
//下载功能参考https://github.com/Masterminds/go-fileserver
func (this *MatterService) DownloadFile(writer http.ResponseWriter, request *http.Request, filePath string, filename string) {
func (this *MatterService) DownloadFile(
writer http.ResponseWriter,
request *http.Request,
filePath string,
filename string,
withContentDisposition bool) {
diskFile, err := os.Open(filePath)
this.PanicError(err)
defer diskFile.Close()
//如果是图片或者文本或者视频就直接打开。其余的一律以下载形式返回。
//fileName := url.QueryEscape(filename)
//mimeType := GetMimeType(fileName)
//if strings.Index(mimeType, "image") != 0 &&
// strings.Index(mimeType, "text") != 0 &&
// strings.Index(mimeType, "video") != 0 {
// writer.Header().Set("content-disposition", "attachment; filename=\""+fileName+"\"")
//}
if withContentDisposition {
fileName := url.QueryEscape(filename)
writer.Header().Set("content-disposition", "attachment; filename=\""+fileName+"\"")
}
//显示文件大小。
fileInfo, err := diskFile.Stat()
@ -473,7 +474,10 @@ func (this *MatterService) DownloadFile(writer http.ResponseWriter, request *htt
ctypes, haveType := writer.Header()["Content-Type"]
var ctype string
if !haveType {
ctype = mime.TypeByExtension(filepath.Ext(fileInfo.Name()))
//放弃原有的判断mime的方法
//ctype = mime.TypeByExtension(filepath.Ext(fileInfo.Name()))
//使用mimeUtil来获取mime
ctype = GetFallbackMimeType(filename, "")
if ctype == "" {
// read a chunk to decide between utf-8 text and binary
var buf [sniffLen]byte

File diff suppressed because it is too large Load Diff