package controllers import ( "backend/internal/utils" "errors" "fmt" "pkg/models" u "pkg/utils" "time" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v5" "github.com/spf13/cast" ) type DownloadShareClaims struct { ShareId string `json:"share_id"` jwt.RegisteredClaims } func DownloadShare(c *echo.Context) error { token := c.FormValue("token") if token == "" { return utils.HTTPErrorHandler(c, errors.New("缺少token")) } claims := DownloadShareClaims{} t, err := jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) { return []byte(u.GetEnv("share.download_secret")), nil }) if err != nil { return utils.HTTPErrorHandler(c, err) } if !t.Valid { return utils.HTTPErrorHandler(c, errors.New("token格式错误")) } shareInfo, _ := models.GetRedisShareInfo(claims.ShareId) if shareInfo.Type == models.ShareTypeFile { fileInfo, _ := models.GetRedisFileInfo(shareInfo.Data) uploadPath, err := u.GetUploadDirPath() if err != nil { return err } return c.Attachment(fmt.Sprintf("%s/%s", uploadPath, u.GetFileId(fileInfo.FileHash, fileInfo.FileSize)), shareInfo.FileName) } return utils.HTTPSuccessHandler(c, map[string]any{ "data": shareInfo.Data, }) } type VaildateShareProps struct { ShareId string `json:"share_id"` Password string `json:"password"` } func VaildateShare(c *echo.Context) error { r := new(VaildateShareProps) if err := c.Bind(r); err != nil { return utils.HTTPErrorHandler(c, err) } if r.ShareId == "" { return utils.HTTPErrorHandler(c, errors.New("缺少分享ID")) } shareInfo, err := models.GetRedisShareInfo(r.ShareId) if err != nil { return utils.HTTPErrorHandler(c, err) } if shareInfo == nil { return utils.HTTPErrorHandler(c, errors.New("分享不存在")) } if shareInfo.Password != "" { if r.Password == "" { return utils.HTTPErrorHandler(c, errors.New("缺少分享密码")) } hash, err := utils.GeneratePasswordHash(r.Password) if err != nil { return utils.HTTPErrorHandler(c, err) } if hash != shareInfo.Password { return utils.HTTPErrorHandler(c, errors.New("分享密码错误")) } } // 如果下载次数为0,则设置为-1 防止空值问题 if shareInfo.ViewNum < 1 { return utils.HTTPErrorHandler(c, errors.New("下载次数不足")) } downloadWindow := u.GetEnvWithDefault("share.download_window", "12") token := jwt.NewWithClaims(jwt.SigningMethodHS256, DownloadShareClaims{ ShareId: r.ShareId, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(cast.ToDuration(downloadWindow + "h"))), }, }) // Sign and get the complete encoded token as a string using the secret downloadToken, err := token.SignedString([]byte(u.GetEnv("share.download_secret"))) if err != nil { return utils.HTTPErrorHandler(c, err) } if shareInfo.Type == models.ShareTypeFile { fileInfo, err := models.GetRedisFileInfo(shareInfo.Data) if err != nil { return utils.HTTPErrorHandler(c, err) } if fileInfo == nil { return utils.HTTPErrorHandler(c, errors.New("分享文件不存在")) } if fileInfo.FileType != models.FileTypeUpload { return utils.HTTPErrorHandler(c, errors.New("分享文件状态错误")) } } // download_nums 必须放在创建token的时候减掉,不然多线程下载会导致多次减掉 latestViewNum := shareInfo.ViewNum - 1 // 如果下载次数为0,则设置为-1 防止空值问题 if latestViewNum < 1 { latestViewNum = -1 } models.SetRedisShareInfo(r.ShareId, models.RedisShareInfo{ ViewNum: latestViewNum, }) // 统计分享数 currentDate := time.Now().Format("2006-01-02") statData, _ := models.GetRedisStat(currentDate) if statData == nil { statData = &models.StatData{ FileSize: 0, FileNum: 0, ShareNum: 0, DownloadNum: 0, } } statData.DownloadNum += 1 models.SetRedisStat(currentDate, *statData) if shareInfo.Type == models.ShareTypeFile { return utils.HTTPSuccessHandler(c, map[string]any{ "token": downloadToken, }) } return utils.HTTPSuccessHandler(c, map[string]any{ "token": downloadToken, }) }