Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
package http
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"reflect"
"time"
)
var (
client = http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConnsPerHost: 5,
MaxConnsPerHost: 100,
},
}
)
type ByteSize int64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
)
var (
outOfBodySizeErr = errors.New("out of body size limit")
)
type ResponseBody struct {
StatusCode int
Data []byte
}
type RequestFormData map[string]string
// Post
// Note: 只会读取小于1M的数据
func Post(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) {
fr, err = do(http.MethodPost, url, header, body, int64(1024*MB))
return
}
// PostFormData 提交标准的form表单
func PostFormData(url string, fromdata RequestFormData) (fr *ResponseBody, err error) {
var b bytes.Buffer
writer := multipart.NewWriter(&b)
for k, v := range fromdata {
if err = writer.WriteField(k, v); err != nil {
return
}
}
if err = writer.Close(); err != nil {
return
}
header := map[string]string{"Content-Type": writer.FormDataContentType()}
fr, err = do(http.MethodPost, url, header, &b, 2*int64(KB))
return
}
// Get
// Note: 只会读取小于1M的数据
func Get(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(KB))
return
}
func GetWithBody(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, body, int64(20*MB))
return
}
// GetLargeFile 获取大文件。2M
func GetLargeFile(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(20*MB))
return
}
func do(method string, url string, header map[string]string, body io.Reader, size int64) (fr *ResponseBody, err error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return
}
for k, v := range header {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.ContentLength > size {
err = outOfBodySizeErr
return
}
data, err := ioutil.ReadAll(io.LimitReader(resp.Body, size))
if err != nil {
return
}
fr = &ResponseBody{
StatusCode: resp.StatusCode,
Data: data,
}
return
}
// 推送对象到文件服务器
func PostObject(ctx context.Context, url string, header map[string]string, body []byte, objName string) (string, error) {
base64data := base64.StdEncoding.EncodeToString(body)
req := struct {
Name string `json:"fileName"`
Data string `json:"fileData"`
}{
Name: objName,
Data: base64data,
}
jsonBytes, _ := json.Marshal(req)
resp, err := Post(url, header, bytes.NewReader(jsonBytes))
if err != nil {
return "", err
}
type Obj struct {
Domain string `json:"domain"`
Name string `json:"fileName"`
}
fsResp := struct {
Code int `json:"code"`
Object Obj `json:"data"`
Msg string `json:"msg"`
}{}
if err = json.Unmarshal(resp.Data, &fsResp); err != nil {
return "", err
}
if fsResp.Code != 200 || fsResp.Object.Name == "" {
return "", fmt.Errorf("上传fs失败")
}
return fsResp.Object.Name, nil
}
func PostWithBind(ctx context.Context, bind interface{}, url string, body io.Reader) error {
if err := verifyBind(bind); err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("response status code:%d", resp.StatusCode)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return json.Unmarshal(data, bind)
}
func verifyBind(bind interface{}) error {
bType := reflect.TypeOf(bind)
if bType.Kind() != reflect.Ptr {
return errors.New("bind must be a Ptr")
}
return nil
}
+16
View File
@@ -0,0 +1,16 @@
package http
import "fmt"
func ExampleGet() {
urlstr := "https://palce.hzbeisheng.com/api/sms"
data, err := Get(urlstr, nil)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data.Data))
// Output:
}
+348
View File
@@ -0,0 +1,348 @@
package tianyuctrl
import (
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/mediatagmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"91porn-server/web/api/tianyuctrl/proxy"
"91porn-server/web/service/vidser"
"context"
"fmt"
"math/rand"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// VideoSearch doc
// @Tags 媒体文件
// @Summary 根据参数获取视频文件列表
// @Description 获取视频列表文件
// @Accept json
// @Produce json
// @Param req body proxy.MediaSearchReq true "参数列表"
// @Success 200 {object} proxy.MediaSearchResp "成功"
// @Failure 400 {string} string "获取失败的返回结果"
// @Router /api/web/admin/tianyu/search [post]
func VideoSearch(ctx *gin.Context) {
var req proxy.MediaSearchReq
if err := ctx.ShouldBind(&req); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err.Error())
return
}
videoUploadDomain := proxy.BucketVideoAddressV2
result, err := proxy.PostSearch(&req, videoUploadDomain)
if err != nil {
common.ServeJSON(ctx, stderr.Failure, err.Error())
return
}
sourceMediaIds := make([]string, 0)
for _, mediaInfo := range result.Data.List {
sourceMediaIds = append(sourceMediaIds, mediaInfo.ID.Hex())
}
videoList, _ := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": sourceMediaIds}})
if err != nil {
log.Error(fmt.Sprintf("mediamod queryAllList error:%+v:", err))
common.ServeJSON(ctx, stderr.Failure, err)
return
}
for _, v := range result.Data.List {
if v.Type == "AI" {
v.FieldNameFs += ".mp4"
}
for _, v1 := range videoList {
if v.ID.Hex() == v1.SourceID {
v.IsAdd = true
}
}
}
common.ServeJSON(ctx, stderr.Success, result.Data)
}
// AddOneMedia doc
// @Tags 视频
// @Summary 添加一个视频视频
// @Description 添加一个视频视频
// @Accept json
// @Produce json
// @Param param body MediaAdd true "参数列表"
// @Success 200 {string} string "成功"
// @Failure 400 {string} string "获取失败的返回结果"
// @Router /api/web/admin/tianyu/add [post]
func AddOneMedia(ctx *gin.Context) {
var req = &MediaAdd{}
if err := ctx.ShouldBind(req); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err)
return
}
manager, err := common.GetAdminAct(ctx)
if err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err.Error())
return
}
var resp = MediaAddResp{SensitiveHits: make([]vidser.SensitiveHit, 0)}
terms := vidser.LoadEnabledSensitiveTerms()
for _, s := range req.FileId {
in := proxy.MediaQueryeReq{
Id: s,
}
videoUploadDomain := proxy.BucketVideoAddressV2
result, err := proxy.QueryMedia(&in, videoUploadDomain)
if err != nil || result.Code != http.StatusOK {
resp.FailCount++
continue
}
var hit *vidser.SensitiveHit
if result.Data.ContentType == 3 {
hit, err = AddMedia(ctx, &result.Data, manager, terms)
} else {
hit, err = AddVideo(ctx, &result.Data, manager, terms)
}
if err != nil {
log.Error(fmt.Sprintf("add tianyu media error:%+v:", err))
resp.FailCount++
continue
}
if hit != nil {
resp.SensitiveHits = append(resp.SensitiveHits, *hit)
}
resp.SuccessCount++
}
if len(resp.SensitiveHits) > 0 {
resp.SensitiveTip = vidser.SensitiveForcedOfflineTip
}
common.ServeJSON(ctx, stderr.Success, resp)
}
func AddMedia(ctx context.Context, data *proxy.MediaResp, manager string, terms []string) (*vidser.SensitiveHit, error) {
now := time.Now()
mediaResource, err := mediamod.GetListByLsjId(data.FileId)
if err != nil {
err = fmt.Errorf("mediamod GetListByLsjId error:%+v", err)
return nil, err
}
if !mediaResource.ID.IsZero() {
return nil, nil
}
tag, err := mediatagmod.FindOneTagOrInset(data.FirstTag)
if err != nil {
return nil, err
}
media := mediamod.Media{
ID: primitive.NewObjectID(),
LsjId: data.FileId,
SortCode: 0,
Status: 1,
Title: data.Title,
HorizontalCover: data.CoverImg,
VerticalCover: "",
Tags: []primitive.ObjectID{tag.ID},
TagDetails: nil,
Summary: data.Desc,
TotalEpisode: 1,
CurrentEpisode: 1,
FreeEpisode: 0,
UpdateStatus: 2,
MediaType: "video",
MediaSubType: 0,
Kind: 0,
PermissionIconHide: false,
Permission: 0,
Style: 0,
Price: 100,
ContentsPrice: 0,
Direction: 0,
FreeTime: 30,
DefaultContent: mediamod.DefaultContent{},
MID: primitive.NilObjectID,
ModuleName: "",
SectionName: "",
SID: primitive.NilObjectID,
SectionSort: 0,
Choice: primitive.NilObjectID,
ChoiceSort: 0,
CountComment: 0,
CountPurchases: 0,
CountContentPurchases: 0,
CountBrowse: 0,
CountCollect: 0,
CountLike: 0,
CountDisLike: 0,
HasFollow: false,
CountView: 0,
Hot: 0,
Number: 0,
Anchor: data.Studio,
CountPurchasesRate: 0,
IsDelete: false,
UpdatedAct: "",
CreatedAt: now,
UpdateTime: time.Now(),
//ContentUpdateTime: time.Time{},
MediaCenterId: 0,
SyncTime: 0,
}
if data.CoverType == 1 {
media.VerticalCover = data.CoverImg
}
// 创建子集
mediaContent := mediacontentmod.MediaContent{
ID: primitive.ObjectID{},
MediaID: media.ID,
MediaType: "video",
EpisodeNumber: 1,
ListenPermission: 0,
Cover: data.CoverImg,
Price: 100,
Name: data.Title,
Text: "",
Md5: "",
AudioUrl: "",
Author: "",
VideoUrl: strings.TrimSpace(data.FieldNameFs) + ".m3u8",
UrlSet: nil,
Height: 0,
Weight: 0,
MediaSize: data.Size,
PlayTime: uint(data.VideoTime),
Status: 1,
IsActive: true,
CountComment: 0,
CountPurchases: 0,
CountBrowse: 0,
FakeCountBrowse: 0,
CountCollect: 0,
CountLike: 0,
CountDisLike: 0,
Ratio: 0,
IsDelete: false,
UpdatedAct: "",
CreatedAt: time.Now(),
HashId: data.FileId,
}
// 敏感词命中 → 强制下架(Status=0)
hit := vidser.CheckTextHits(terms, data.FileId, media.Title, media.Summary, "")
if hit != nil {
media.Status = 0
log.Warn("tianyu AddMedia sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
// 不处理标签
_, err = mediamod.Insert(nil, media)
if err != nil {
return nil, err
}
if _, err = mediacontentmod.Insert(nil, mediaContent); err != nil {
return nil, err
}
return hit, nil
}
func AddVideo(ctx context.Context, data *proxy.MediaResp, manager string, terms []string) (*vidser.SensitiveHit, error) {
now := time.Now()
postList, err := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": []string{data.FileId}}})
if err != nil {
log.Warn("syncOneMovie GetList failed", log.Any("id", data.FileId), log.E(err))
return nil, err
}
if postList != nil && len(postList) > 0 {
return nil, nil
}
upUsers, err := usermod.FindMany(bson.M{"uid": bson.M{"$gt": 300000}}, &options.FindOptions{}, 0, 300)
if err != nil {
log.Warn("PostAddList FindManyUser failed", log.E(err))
return nil, err
}
vidInfo := vidmod.VideoModel{
ID: primitive.NewObjectID(),
Title: data.Title,
Content: data.Desc,
Tags: make([]primitive.ObjectID, 0),
MimeType: "video/mp4",
SeriesCover: make([]string, 0),
Via: "tianyu",
Status: vidmod.CheckPass,
Location: primitive.ObjectID{},
Actor: strings.Join(data.Actors, " "),
NewUpdatedAt: "tianyu",
ReviewAt: now,
CreatedAt: now,
Cover: data.CoverImg,
ReviewAccount: manager,
}
if data.CoverType == 1 {
vidInfo.VerticalCover = data.CoverImg
}
if data.CoverType == 1 {
vidInfo.NewsType = vidmod.SHORT
vidInfo.Cover = data.CoverImg
vidInfo.CoverThumb = data.CoverImg
vidInfo.Direction = "vertical"
} else {
vidInfo.NewsType = vidmod.SP
vidInfo.Cover = data.CoverImg
vidInfo.CoverThumb = data.CoverImg
vidInfo.Direction = "horizontal"
}
vidInfo.PlayTime = uint(data.VideoTime)
uIndex := rand.Intn(len(upUsers))
u := upUsers[uIndex]
vidInfo.PublisherID = u.UID
vidInfo.SourceURL = data.FieldNameFs
vidInfo.PreviewURL = data.PreFileName
vidInfo.SourceID = data.FileId
vidInfo.Filename = data.FieldNameFs
if !strings.Contains(data.PreFileName, ".m3u8") {
vidInfo.SourceURL = strings.TrimSpace(data.PreFileName) + ".m3u8"
}
if !strings.Contains(data.FieldNameFs, ".m3u8") {
vidInfo.SourceURL = strings.TrimSpace(data.FieldNameFs) + ".m3u8"
}
// 敏感词命中 → 强制为待审核(WaitingCheck=0)
hit := vidser.CheckTextHits(terms, data.FileId, vidInfo.Title, vidInfo.Content, "")
if hit != nil {
vidInfo.Status = vidmod.WaitingCheck
log.Warn("tianyu AddVideo sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
// 不处理标签
if _, err = vidmod.InsertBase(vidInfo); err != nil {
return nil, err
}
return hit, nil
}
+14
View File
@@ -0,0 +1,14 @@
package tianyuctrl
import "91porn-server/web/service/vidser"
type MediaAdd struct {
FileId []string `json:"fileId" bson:"fileId,required"` // 媒资库ID
}
type MediaAddResp struct {
SuccessCount int `json:"successCount"`
FailCount int `json:"failCount"`
SensitiveHits []vidser.SensitiveHit `json:"sensitiveHits"` // 命中敏感词被强制下架的记录
SensitiveTip string `json:"sensitiveTip,omitempty"` // 命中提示文案;无命中时为空
}
+382
View File
@@ -0,0 +1,382 @@
package proxy
import (
"91porn-server/common/log"
"91porn-server/web/api/tianyuctrl/http"
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
)
const (
apiDomain = BucketVideoAddressV2 + "/file/upload"
imgDomain = BucketImageAddressV2 + "/file/upload"
)
var (
header = map[string]string{"Content-Type": "application/json"}
)
const (
Success = 200
)
func UploadImg(fileName string, data []byte, domain string) (pathStr string, err error) {
r := objreq{
Name: fileName,
Data: base64.StdEncoding.EncodeToString(data),
}
jsonbytes, err := json.Marshal(r)
if err != nil {
return
}
if len(domain) == 0 {
domain = imgDomain
}
url := domain
if !strings.Contains(domain, "/file/upload") {
url = domain + "/file/upload"
}
resp, err := http.Post(url, header, bytes.NewReader(jsonbytes))
if err != nil {
return
}
var or objresp
if err = json.Unmarshal(resp.Data, &or); err != nil {
return
}
if or.Code != Success {
err = fmt.Errorf("上传失败:%s", or.Msg)
return
}
pathStr = or.Object.Name
return
}
func UploadBase64Img(fileName string, data string, domain string) (pathStr string, err error) {
r := objreq{
Name: fileName,
Data: data,
}
jsonbytes, err := json.Marshal(r)
if err != nil {
return
}
if len(domain) == 0 {
domain = apiDomain
}
url := domain
if !strings.Contains(domain, "/file/upload") {
url = domain + "/file/upload"
}
resp, err := http.Post(url, header, bytes.NewReader(jsonbytes))
if err != nil {
return
}
var or objresp
if err = json.Unmarshal(resp.Data, &or); err != nil {
return
}
if or.Code != Success {
err = fmt.Errorf("上传失败:%s", or.Msg)
return
}
pathStr = or.Object.Name
return
}
func fileServer() (url string) {
return BucketVideoAddressV2
}
func PostOk(baseInfo *ShardOk, domain string) (result *VideoOkResult, err error) {
result = &VideoOkResult{}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/proxy/videoOk") {
url = domain + "/file/web/proxy/videoOk"
}
fmt.Println(paramJson)
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
fmt.Println(result)
return nil, err
}
fmt.Println(result)
return
}
func PostOkV2(baseInfo *ShardOk, domain string) (result *VideoOkResult, err error) {
result = &VideoOkResult{}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/proxy/videoOkv2") {
url = domain + "/file/web/proxy/videoOkv2"
}
fmt.Println(paramJson)
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
fmt.Println(result)
return nil, err
}
fmt.Println(result)
return
}
func QueryMedia(baseInfo *MediaQueryeReq, domain string) (result *VideoQueryResult, err error) {
result = &VideoQueryResult{}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/query") {
url = domain + "/file/web/media/query"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
fmt.Println(err)
return nil, err
}
return
}
func QueryMedias(baseInfo *MediaQueryesReq, domain string) (result *VideoQuerysResult, err error) {
result = &VideoQuerysResult{
Data: make([]MediaResp, 0),
}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/querys") {
url = domain + "/file/web/media/querys"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
fmt.Println(err)
return nil, err
}
return
}
func SyncMedia(baseInfo *MediaSyncReq, domain string) (result *VideoSyncResult, err error) {
result = &VideoSyncResult{}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/search") {
url = domain + "/file/web/media/search"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
fmt.Println(err, string(fr.Data))
return nil, err
}
return
}
func UploadVideo(domain string, body *ShardBody) (result *Result, err error) {
if body == nil {
return nil, errors.New("no video to upload")
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/proxy/videoBody") {
url = domain + "/file/web/proxy/videoBody"
}
fmt.Println(url)
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
resp, err := http.Post(url, header, bytes.NewBuffer(buf))
if err != nil {
return nil, err
}
fmt.Println("resp-->", string(resp.Data))
result = new(Result)
if err = json.Unmarshal(resp.Data, result); err != nil {
return nil, err
}
return
}
func PostSearch(param *MediaSearchReq, domain string) (result *MediaSearchResult, err error) {
result = &MediaSearchResult{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/search") {
url = domain + "/file/web/media/search"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
log.Error(fmt.Sprintf("ty media search err:%v", err))
return
}
if fr.StatusCode != 200 {
log.Error(fmt.Sprintf("ty media search fr.StatusCode:%v", fr.StatusCode))
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
log.Error(fmt.Sprintf("ty json Unmarshal search err:%v", err))
return nil, err
}
log.Info("proxy:test", log.Any("url", url), log.Any("result", result))
return
}
func PostUpdate(param *MediaUpdateReq, domain string) (result *MediaUpdateResult, err error) {
result = &MediaUpdateResult{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/update") {
url = domain + "/file/web/media/update"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func PostDelete(param *MediaDelReq, domain string) (result *MediaDelResult, err error) {
result = &MediaDelResult{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/delete") {
url = domain + "/file/web/media/delete"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func PostUpdatePreTime(param *MediaUpdatePreTime, domain string) (result *MediaDelResult, err error) {
result = &MediaDelResult{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/media/update/prefile") {
url = domain + "/file/web/media/update/prefile"
}
fr, err := http.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func UploadVideoV2(domain string, body *ShardBodyV2) (result *Result, err error) {
if body == nil {
return nil, errors.New("no video to upload")
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/file/web/proxy/videoBodyV2") {
url = domain + "/file/web/proxy/videoBodyV2"
}
fmt.Println(url)
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
resp, err := http.Post(url, header, bytes.NewBuffer(buf))
if err != nil {
return nil, err
}
fmt.Println("resp-->", string(resp.Data))
result = new(Result)
if err = json.Unmarshal(resp.Data, result); err != nil {
return nil, err
}
fmt.Println("result-->", result)
if result.Code != 200 {
err = fmt.Errorf("result code err")
}
return
}
+1
View File
@@ -0,0 +1 @@
package proxy
+351
View File
@@ -0,0 +1,351 @@
package proxy
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type objreq struct {
Name string `json:"fileName"`
Data string `json:"fileData"`
}
type Obj struct {
Domain string `json:"domain"`
Name string `json:"fileName"`
}
type objresp struct {
Code int `json:"code"`
Object Obj `json:"data"`
Msg string `json:"msg"`
}
type ShardBody struct {
TaskId string `json:"taskId,omitempty"` // 文档md5
Pos int `json:"pos,omitempty"` // 位置
FileData string `json:"fileData,omitempty"` // 数据
Type string `json:"type,omitempty"`
TotalPos int `json:"totalPos,omitempty"` // 分片总数
}
type ShardBodyV2 struct {
TaskId string `json:"taskId,omitempty"` // 文档md5
Pos int `json:"pos,omitempty"` // 位置
FileData string `json:"fileData,omitempty"` // 数据
Type string `json:"type,omitempty"`
TotalPos int `json:"totalPos,omitempty"`
FileName string `json:"fileName,omitempty"`
Publish string `json:"publish,omitempty"`
AppId int `json:"appId,omitempty"`
MP4Folder string `json:"mp4Folder"` // 保存mp4的文件夹名,为空表示不需要保存mp4
}
type Data struct {
Id string `json:"id,omitempty"`
VideoUri string `json:"videoUri,omitempty"`
}
type Result struct {
Code int `json:"code,omitempty"` //
Data Data `json:"data,omitempty"` //
Msg string `json:"msg,omitempty"` //
}
type VideoOkResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data string `json:"data,omitempty"`
}
type ShardOk struct {
TaskId string `json:"taskId,omitempty"` // 文档md5
Id string `json:"id,omitempty"` // Id
Title *string `json:"title,omitempty"` // 标题
CoverImg *string `json:"coverImg,omitempty"` // 封面
}
type MediaQueryeReq struct {
Id string `json:"id"`
TaskId string `json:"taskId"`
}
type MediaQueryesReq struct {
Ids []string `json:"ids"`
}
type VideoQueryResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data MediaResp `json:"data,omitempty"`
}
type VideoQuerysResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data []MediaResp `json:"data,omitempty"`
}
type MediaSyncReq struct {
PageNum int64 `json:"pageNum"`
PageSize int64 `json:"pageSize"`
Param *SyncParam `json:"param"`
}
type SyncParam struct {
Status string `json:"status,omitempty"` // 当前状态
FirstTag string `json:"firstTag,omitempty"` // 一级标签
Publish string `json:"publish,omitempty"` // 视频发布者
// SecondTags string `json:"secondTags,omitempty"` // 二级标签标签
}
type VideoSyncResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data MediaSearchResp `json:"data,omitempty"`
}
// type MediaSearchResp struct {
// Total int64 `json:"total"`
// List []*MediaSync `json:"list"`
// }
type MediaSync struct {
ID string `json:"id" binding:"required"`
FileId string `json:"fileId" bson:"fileId"` // 媒资库ID
Title string `bson:"title" json:"title"` // 标题
Publish string `json:"publish" bson:"publish"` // 视频发布者
Type string `bson:"type" json:"type"` // 长视频/短视频
Country string `bson:"country" json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag string `bson:"firstTag" json:"firstTag"` // 一级标签
SecondTags []string `bson:"secondTags" json:"secondTags"` // 二级标签标签
CoverImg string `bson:"coverImg" json:"coverImg"` // 封面
PreFileName string `bson:"preFileName" json:"preFileName"` // 预览文件
Desc string `bson:"desc" json:"desc"` // 详细描述
Size int64 `bson:"size" json:"size"` // 文件size,单位byte
Bango string `bson:"bango" json:"bango"` // 番号,唯一
Directors []string `bson:"directors" json:"directors"` // 导演
Actors []string `bson:"actors" json:"actors"` // 演员
Studio string `bson:"studio" json:"studio"` // 工作室/所有者
AddedTime time.Time `bson:"addedTime" json:"addedTime"` // 上架时间
VideoTime int64 `json:"videoTime" bson:"videoTime"` // 影片时长,单位秒
OperatAt time.Time `json:"operatAt" bson:"operatAt"` // 编辑时间(排序时间)
FieldNameFs string `bson:"fieldNameFs,omitempty" json:"fieldNameFs,omitempty"` // 文件名称
Height int64 `json:"height" bson:"height"` // 分辨率高
Width int64 `json:"width" bson:"width"` // 分辨率宽
ProxyUpload int32 `bson:"proxyUpload,omitempty" json:"proxyUpload,omitempty"` // 代理上传 1:代理
}
type MediaResp struct {
FileId string `json:"id" bson:"_id"` // 媒资库ID
SrcId int64 `bson:"srcId,omitempty" json:"srcId"` // 原始ID
Title string `bson:"title" json:"title"` // 标题
Type string `bson:"type" json:"type"` // 类型:3.AI
Publish string `json:"publish" bson:"publish"` // 视频发布者
ContentType int64 `bson:"contentType,omitempty" json:"contentType"` // 内容 1:日本 2:国产 3:动漫
FormatType int64 `bson:"formatType,omitempty" json:"formatType"` // 视频格式:1:长视频 2:短视频 3:预览
CoverType CoverType `bson:"coverType,omitempty" json:"coverType"` // 封面类型: 1:竖版封面 2:横版封面 3:系统设置
Country string `bson:"country" json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag string `bson:"firstTag" json:"firstTag"` // 一级标签
SecondTags []string `bson:"secondTags" json:"secondTags"` // 二级标签标签
CoverImg string `bson:"coverImg" json:"coverImg"` // 封面
PreFileName string `bson:"preFileName" json:"preFileName"` // 预览文件
Status string `bson:"status" json:"status"` // 当前状态
Desc string `bson:"desc" json:"desc"` // 详细描述
MediaSet []string `bson:"mediaSet,omitempty" json:"mediaSet"` // 视频归属系列(连续剧、番剧)
Size int64 `bson:"size" json:"size"` // 文件size,单位byte
Bango string `bson:"bango" json:"bango"` // 番号,唯一
Actors []string `bson:"actors" json:"actors"` // 演员
JapanNames []string `json:"japanNames"` // 演员日本名称
Studio string `bson:"studio" json:"studio"` // 工作室/所有者
AddedTime time.Time `bson:"addedTime" json:"addedTime"` // 上架时间
VideoTime int64 `json:"videoTime" bson:"videoTime"` // 影片时长,单位秒
OperatAt time.Time `json:"operatAt" bson:"operatAt"` // 编辑时间(排序时间)
FieldNameFs string `bson:"fieldNameFs,omitempty" json:"fieldNameFs,omitempty"` // 文件名称
Height int64 `json:"height" bson:"height"` // 分辨率高
Width int64 `json:"width" bson:"width"` // 分辨率宽
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` // 更新时间
MP4FileName string `json:"mp4FileName" bson:"mp4FileName"`
}
type MediaType int
const (
ContentAv MediaType = 1 // 日本
ContentChina MediaType = 2 // 国产
ContentCartoon MediaType = 3 // 动漫
)
type CoverType int
const (
CoverHorizontal CoverType = 1 // 竖版封面
CoverVertical CoverType = 2 // 横版封面
CoverSystem CoverType = 3 // 系统设置封面
)
type FailReason string
const (
FailReasonNone FailReason = ""
FailReasonUploadTimeoutError FailReason = "UploadTimeoutError" // 上传超时
FailReasonMergeError FailReason = "MergeError" // 合并错误
FailReasonCompressError FailReason = "CompressError" // 压缩错误
FailReasonSegmentError FailReason = "SegmentError" // 分片错误
FailReasonLinodeError FailReason = "LinodeError" // Linode错误
)
type BaseInfo struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id"`
SrcId int64 `bson:"srcId,omitempty" json:"srcId"` // 原始ID
Title string `bson:"title,omitempty" json:"title"` // 标题
CheckSum string `bson:"checkSum,omitempty" json:"checkSum"` // md5校验码
FormatType int64 `bson:"formatType,omitempty" json:"formatType"` // 视频格式:1:长视频 2:短视频 3:预览
ContentType MediaType `bson:"contentType,omitempty" json:"contentType"` // 内容 1:日本 2:国产 3:动漫
CoverType CoverType `bson:"coverType,omitempty" json:"coverType"` // 封面类型: 1:竖版封面 2:横版封面 3:系统设置
CoverImg string `bson:"coverImg,omitempty" json:"coverImg"` // 封面
CoverImgNew string `bson:"coverImgNew,omitempty" json:"coverImgNew"` // 封面(自动生成)
VideoTime int32 `bson:"videoTime,omitempty" json:"videoTime"` // 影片长度
Publish string `json:"publish,omitempty" bson:"publish"` // 视频发布者
Type string `bson:"type,omitempty" json:"type"` // 类型:1.AV 2.SP 3.AI
Country string `bson:"country,omitempty" json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag string `bson:"firstTag,omitempty" json:"firstTag"` // 一级标签
SecondTags []string `bson:"secondTags,omitempty" json:"secondTags"` // 二级标签标签
MediaSet []string `bson:"mediaSet,omitempty" json:"mediaSet"` // 视频归属系列(连续剧、番剧)
PreFileName string `bson:"preFileName,omitempty" json:"preFileName"` // 预览文件
Status string `bson:"status,omitempty" json:"status"` // 当前状态
Desc string `bson:"desc,omitempty" json:"desc"` // 详细描述
Size int64 `bson:"size,omitempty" json:"size"` // 文件size,单位byte
Bango string `bson:"bango,omitempty" json:"bango"` // 番号,唯一
Actors []string `bson:"actors,omitempty" json:"actors"` // 演员
Studio string `bson:"studio,omitempty" json:"studio"` // 工作室/所有者
AddedTime time.Time `bson:"addedTime,omitempty" json:"addedTime"` // 上架时间
AppIds []int64 `bson:"appids,omitempty" json:"appids,omitempty"` // 应用列表
JapanNames []string `json:"japanNames"` // 演员日本名称
// 切片相关信息
Filename string `bson:"filename,omitempty" json:"filename"` // 文件名称
FieldNameFs string `bson:"fieldNameFs,omitempty" json:"fieldNameFs"` // 文件名称
Ext string `bson:"ext,omitempty" json:"ext"` // 视频后缀
TaskID string `bson:"taskId,omitempty" json:"taskId"` // 项目组传来的唯一标示
Width int `bson:"width,omitempty" json:"width"` // 分辨率宽
Height int `bson:"height,omitempty" json:"height"` // 分辨率高
Ratio float64 `bson:"ratio,omitempty" json:"ratio"` // 宽高比
FrameRate string `bson:"frameRate,omitempty" json:"frameRate"` // 视频贞数
SyBitRate string `bson:"syBitRate,omitempty" json:"syBitRate"` // 音频bit率
VidBitRate string `bson:"vidBitRate,omitempty" json:"vidBitRate"` // 视频bit率
CreatedAt time.Time `bson:"createdAt,omitempty" json:"createdAt"` // 创建时间
UpdatedAt time.Time `bson:"updatedAt,omitempty" json:"updatedAt"` // 更新时间
ProxyUpload *int32 `bson:"proxyUpload,omitempty" json:"proxyUpload"` // 代理上传
IsAdd bool `bson:"isAdd,omitempty" json:"isAdd"` // 是否加入到本地
Retry int `bson:"retry" json:"retry"` // 重试次数 默认为零 最大重试3次
NotifySignal bool `bson:"notifySignal" json:"notifySignal"` //中止通知的信号 false 可以发送通知,true 中止发送通知
ShowLocation int64 `bson:"showLocation" json:"showLocation"` // 显示位置
MergeRetry int `bson:"mergeRetry" json:"mergeRetry"` //合并重试次数
CompressRetry int `bson:"compressRetry" json:"compressRetry"` //压缩重试次数
SegmentRetry int `bson:"segmentRetry" json:"segmentRetry"` //切片重试次数
LinodeRetry int `bson:"linodeRetry" json:"linodeRetry"` //上传linode重试次数
FailReason FailReason `bson:"failReason" json:"failReason"` //失败原因
DeleteDisk bool `bson:"deleteDisk" json:"deleteDisk"` //是否删除了对于磁盘文件
InfoTsName string `bson:"infoTsName" json:"infoTsName"` //从该ts中获取视频分辨率等信息
Remark string `bson:"remark,omitempty" json:"remark"` // 备注
}
type MediaUpdate struct {
ID string `json:"id,omitempty"`
Title *string `bson:"title,omitempty" json:"title,omitempty"` // 标题
AddedTime *time.Time `bson:"addedTime,omitempty" json:"addedTime,omitempty"` // 上架时间
Publish string `json:"publish,omitempty" bson:"publish"` // 视频发布者(麻豆、adday)
FormatType *int64 `bson:"formatType,omitempty" json:"formatType"` // 视频格式:1:长视频 2:短视频 3:预览
FirstTag *string `bson:"firstTag,omitempty" json:"firstTag"` // 一级标签
SecondTags *[]string `bson:"secondTags,omitempty" json:"secondTags"` // 二级标签标签
MediaSet *[]string `bson:"mediaSet,omitempty" json:"mediaSet"` // 视频归属系列(连续剧、番剧)
Bango *string `bson:"bango,omitempty" json:"bango,omitempty"` // 番号,唯一
AppIds *[]int64 `bson:"appids,omitempty" json:"appids,omitempty"` // 应用列表
Desc *string `bson:"desc,omitempty" json:"desc,omitempty"` // 详细描述
PreFileName *string `bson:"preFileName,omitempty" json:"preFileName,omitempty"` // 预览文件
CoverImg *string `bson:"coverImg,omitempty" json:"coverImg,omitempty"` // 封面
ContentType *MediaType `bson:"contentType,omitempty" json:"contentType"` // 内容 1:AV(日本) 2:国产(短视频) 3:动漫
Actors *[]string `bson:"actors,omitempty" json:"actors"` // 演员
Studio *string `bson:"studio,omitempty" json:"studio"` // 工作室/所有者
CoverType *CoverType `bson:"coverType,omitempty" json:"coverType"` // 封面类型 1:竖版 2:横版 3:系统
CreatedAt *time.Time `bson:"createdAt,omitempty" json:"createdAt,omitempty"` // 创建时间
UpdatedAt *time.Time `bson:"updatedAt,omitempty" json:"updatedAt,omitempty"` // 更新时间
ShowLocation *int64 `bson:"showLocation,omitempty" json:"showLocation"` // 显示位置
}
type VideoSearch struct {
ID string `json:"id,omitempty"`
Title *string `json:"title,omitempty"` // 标题
TaskID *string `json:"taskId,omitempty"` // 项目组传来的唯一标示
Publish *string `json:"publish,omitempty"` // 视频发布者
FormatType *int64 `json:"formatType,omitempty"` // 视频格式:1:长视频 2:短视频 3:预览
ContentType *MediaType `json:"contentType,omitempty"` // 内容
CoverType *CoverType `json:"coverType,omitempty"` // 封面类型
Appid *int64 `json:"appid"` // 应用ID
AppIds *[]int64 `json:"appids"` // 应用IDs
FirstTag *string `json:"firstTag,omitempty"` // 一级标签
SecondTags *string `json:"secondTags,omitempty"` // 二级标签标签
MediaSet *[]string `json:"mediaSet,omitempty"` // 视频归属系列(连续剧、番剧)
Status *string `json:"status,omitempty"` // 当前状态
Actors *string `json:"actors,omitempty"` // 演员
Studio *string `json:"studio,omitempty"` // 工作室
Bango *string `json:"bango,omitempty"` // 番号,唯一
Remark *string `json:"remark,omitempty"` // 备注
AddedTime *string `json:"addedTime,omitempty"` // 上架时间
UploadTime *string `json:"uploadTime,omitempty"` // 上传时间
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
Type *string `json:"type,omitempty"` // SP,AV,AI,MP3
ShowLocation *int64 `json:"showLocation"` // 显示位置
}
type MediaSearchReq struct {
PageNum int64 `json:"pageNum"`
PageSize int64 `json:"pageSize"`
Param *VideoSearch `json:"param"`
}
// MediaSearchReq 媒体搜索请求返回
type MediaSearchResp struct {
Total int64 `json:"total"`
List []*BaseInfo `json:"list"`
}
type MediaSearchResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data MediaSearchResp `json:"data,omitempty"`
}
type MediaDelReq struct {
ID string `json:"id,omitempty"`
}
// MediasDelResp 请求批量删除媒体回复
type MediaDelResp struct {
}
type MediaUpdatePreTime struct {
ID string `json:"id,omitempty"`
PreFileTime int64 `bson:"preFileTime,omitempty" json:"preFileTime"` // 预览文件(开始时间)
}
type MediaDelResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
// Data MediaDelResp `json:"data,omitempty"`
}
type MediaUpdateReq struct {
//Ids []string `json:"id"`
MediaUpdate
}
// MediasUpdateResp 请求批量更新媒体回复
type MediaUpdateResp struct {
}
type MediaUpdateResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data MediaUpdateResp `json:"data,omitempty"`
}
+130
View File
@@ -0,0 +1,130 @@
package proxy
import (
"fmt"
"strings"
)
const (
// 视频桶地址
BucketVideoAddressV1 = "http://ts.bbaazz.me"
BucketVideoAddressV2 = "http://tong3yuants.bbaazz.me"
// 图片桶地址
BucketImageAddressV1 = "http://img.bbaazz.me"
BucketImageAddressV2 = "http://tong3yuanimg.bbaazz.me"
// 上传路径
ImageUploadPathV1 = "/file/upload"
ImageUploadPathV2 = "/file/upload" // "/file/web/media/uploadImg"
VideoBodyPathV1 = "/file/web/proxy/videoBody"
VideoBodyPathV2 = "/file/web/proxy/videoBodyV2"
VideoOkPathV1 = "/file/web/proxy/videoOk"
VideoOkPathV2 = "/file/web/proxy/videoOkv2"
)
func DomainAddressRepair(domain string, defaultBucketAddress ...string) string {
if domain == "" {
if len(defaultBucketAddress) > 0 && defaultBucketAddress[0] != "" {
domain = defaultBucketAddress[0]
} else {
domain = BucketVideoAddressV2
}
}
return domain
}
func ImageUploadAddressRepair(domain string, defaultBucketAddress ...string) string {
domain = DomainAddressRepair(domain, defaultBucketAddress...)
if !strings.Contains(domain, ImageUploadPathV1) && !strings.Contains(domain, ImageUploadPathV2) {
if strings.HasPrefix(domain, BucketImageAddressV1) {
return domain + ImageUploadPathV1
} else if strings.HasPrefix(domain, BucketImageAddressV2) {
return domain + ImageUploadPathV2
}
return domain + ImageUploadPathV2
}
return domain
}
func VideoBodyAddressRepair(domain string, defaultBucketAddress ...string) string {
domain = DomainAddressRepair(domain, defaultBucketAddress...)
if strings.HasPrefix(domain, BucketVideoAddressV1) {
if !strings.Contains(domain, VideoBodyPathV1) {
return domain + VideoBodyPathV1
}
return domain
} else if strings.HasPrefix(domain, BucketVideoAddressV2) {
if !strings.Contains(domain, VideoBodyPathV2) {
return domain + VideoBodyPathV2
}
return domain
} else { // 未知存储桶,默认v2
if !strings.Contains(domain, VideoBodyPathV2) {
return domain + VideoBodyPathV2
}
return domain
}
}
func VideoOkAddressRepair(domain string, defaultBucketAddress ...string) string {
domain = DomainAddressRepair(domain, defaultBucketAddress...)
if strings.HasPrefix(domain, BucketVideoAddressV1) {
if !strings.Contains(domain, VideoOkPathV1) {
return domain + VideoOkPathV1
}
return domain
} else if strings.HasPrefix(domain, BucketVideoAddressV2) {
if !strings.Contains(domain, VideoOkPathV2) {
return domain + VideoOkPathV2
}
return domain
} else { // 未知存储桶,默认v2
if !strings.Contains(domain, VideoOkPathV2) {
return domain + VideoOkPathV2
}
return domain
}
}
func GetExistsUrlPathBucketAddress(path string) string {
if strings.HasPrefix(path, "v1/") {
return BucketVideoAddressV1
} else if strings.HasPrefix(path, "v2/") {
return BucketVideoAddressV2
} else if strings.HasPrefix(path, "v3/") {
return BucketVideoAddressV2
} else {
return BucketVideoAddressV1 // 欲漫涩、微博、快看禁漫 默认用桶2,其他的app,默认用桶1
}
}
func AppendV2PrefixToUrlPath(name string) string {
if name != "" && !strings.HasPrefix(name, "v2/") && !strings.HasPrefix(name, "v1/") && !strings.HasPrefix(name, "v3/") &&
!strings.HasPrefix(name, "/v2") && !strings.HasPrefix(name, "/v1") && !strings.HasPrefix(name, "/v3") {
name = fmt.Sprintf("v2/%s", name)
}
return name
}
func AppendV1PrefixToUrlPath(name string) string {
if name != "" && !strings.HasPrefix(name, "v2/") && !strings.HasPrefix(name, "v1/") && !strings.HasPrefix(name, "v3/") &&
!strings.HasPrefix(name, "/v2") && !strings.HasPrefix(name, "/v1") && !strings.HasPrefix(name, "/v3") {
name = fmt.Sprintf("v1/%s", name)
}
return name
}
func AppendsMediaS3Version(name string) string {
if strings.Contains(name, "bktadminup") ||
strings.Contains(name, "bktappupdata") ||
strings.Contains(name, "k12") ||
strings.Contains(name, "bktappup") {
return ""
}
// 默认使用V1
if name != "" && !strings.HasPrefix(name, "v2/") && !strings.HasPrefix(name, "v1/") && !strings.HasPrefix(name, "v3/") &&
!strings.HasPrefix(name, "/v2") && !strings.HasPrefix(name, "/v1") && !strings.HasPrefix(name, "/v3") {
return "/v2"
}
return ""
}