110 lines
4.1 KiB
Go
110 lines
4.1 KiB
Go
package dramaser
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"91porn-server/app/appg"
|
|
"91porn-server/common/db"
|
|
"91porn-server/models/v/media_buy_record_mod"
|
|
"91porn-server/models/v/mediacontentmod"
|
|
"91porn-server/models/v/mediamod"
|
|
"91porn-server/models/v/usermod"
|
|
"91porn-server/models/v/walletmod"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
var (
|
|
ErrDramaEntitlementRequired = errors.New("active drama entitlement is required")
|
|
ErrDownloadResourceInvalid = errors.New("short drama episode is unavailable")
|
|
)
|
|
|
|
const downloadTicketTTL = 6 * time.Hour
|
|
|
|
type DownloadAuthorizeRequest struct {
|
|
MediaID primitive.ObjectID `json:"mediaId" binding:"required"`
|
|
ContentID primitive.ObjectID `json:"contentId" binding:"required"`
|
|
}
|
|
|
|
type DownloadAuthorizeResponse struct {
|
|
MediaID primitive.ObjectID `json:"mediaId"`
|
|
ContentID primitive.ObjectID `json:"contentId"`
|
|
EpisodeNumber int `json:"episodeNumber"`
|
|
Name string `json:"name"`
|
|
Cover string `json:"cover"`
|
|
DownloadURL string `json:"downloadUrl"`
|
|
H265DownloadURL string `json:"h265DownloadUrl"`
|
|
MediaSize int64 `json:"mediaSize"`
|
|
ExpiresAt time.Time `json:"expiresAt"`
|
|
RemainingDownloadCount int64 `json:"remainingDownloadCount"`
|
|
Charged bool `json:"charged"`
|
|
}
|
|
|
|
func AuthorizeDownload(ctx context.Context, uid uint64, requestID string, req DownloadAuthorizeRequest, now time.Time) (DownloadAuthorizeResponse, error) {
|
|
requestID = strings.TrimSpace(requestID)
|
|
if len(requestID) < 1 || len(requestID) > 128 {
|
|
return DownloadAuthorizeResponse{}, errors.New("X-Request-ID长度必须为1到128")
|
|
}
|
|
media, err := mediamod.GetInfo(req.MediaID)
|
|
if err != nil || media.MediaType != mediamod.MediaTypeDrama || media.Status != 1 || media.IsDelete {
|
|
return DownloadAuthorizeResponse{}, ErrDownloadResourceInvalid
|
|
}
|
|
content, err := mediacontentmod.GetInfo(req.ContentID)
|
|
if err != nil || content.MediaID != req.MediaID || content.MediaType != mediamod.MediaTypeDrama || content.Status != 1 || !content.IsActive || content.IsDelete {
|
|
return DownloadAuthorizeResponse{}, ErrDownloadResourceInvalid
|
|
}
|
|
requestKey := downloadRequestKey(uid, requestID)
|
|
fingerprint := req.MediaID.Hex() + ":" + req.ContentID.Hex()
|
|
var wallet *walletmod.Wallet
|
|
var charged bool
|
|
for attempt := 0; attempt < 3; attempt++ {
|
|
err = appg.VideoDB.TransCtx(ctx, func(t *db.MongoTool) error {
|
|
user, userErr := usermod.FindUserByUIDTrans(t, uid)
|
|
if userErr != nil {
|
|
return userErr
|
|
}
|
|
bought := false
|
|
if user != nil && !user.HasLocked && !user.DramaExpire.After(now) {
|
|
bought, userErr = media_buy_record_mod.IsBuyWithTool(t, uid, req.MediaID, req.ContentID)
|
|
if userErr != nil {
|
|
return userErr
|
|
}
|
|
}
|
|
if !canAuthorizeDramaDownload(user, bought, now) {
|
|
return ErrDramaEntitlementRequired
|
|
}
|
|
wallet, charged, userErr = walletmod.AuthorizeDramaDownload(t, uid, requestKey, fingerprint, now)
|
|
return userErr
|
|
})
|
|
if !errors.Is(err, walletmod.ErrDownloadAuthorizationRace) {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return DownloadAuthorizeResponse{}, err
|
|
}
|
|
name := strings.TrimSpace(content.Name)
|
|
if name == "" {
|
|
name = fmt.Sprintf("第%d集", content.EpisodeNumber)
|
|
}
|
|
return DownloadAuthorizeResponse{
|
|
MediaID: req.MediaID, ContentID: req.ContentID, EpisodeNumber: content.EpisodeNumber,
|
|
Name: name, Cover: content.Cover, DownloadURL: content.VideoUrl,
|
|
H265DownloadURL: appg.H265URLForApp(content.H265Url), MediaSize: content.MediaSize,
|
|
ExpiresAt: now.Add(downloadTicketTTL), RemainingDownloadCount: wallet.DownloadCount, Charged: charged,
|
|
}, nil
|
|
}
|
|
|
|
func downloadRequestKey(uid uint64, requestID string) string {
|
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d:%s", uid, requestID))))
|
|
}
|
|
|
|
func canAuthorizeDramaDownload(user *usermod.User, bought bool, now time.Time) bool {
|
|
return user != nil && !user.HasLocked && (user.DramaExpire.After(now) || bought)
|
|
}
|