diff --git a/controllers/video.go b/controllers/video.go index 82d1465..bd9f14e 100644 --- a/controllers/video.go +++ b/controllers/video.go @@ -1,9 +1,14 @@ package controllers import ( + "bytes" "encoding/json" + "fmt" + "io" "github.com/casbin/casbase/object" + "github.com/casbin/casbase/util" + "github.com/casbin/casbase/video" ) func (c *ApiController) GetGlobalVideos() { @@ -59,3 +64,47 @@ func (c *ApiController) DeleteVideo() { c.Data["json"] = object.DeleteVideo(&video) c.ServeJSON() } + +func (c *ApiController) UploadVideo() { + owner := c.GetSessionUsername() + + file, header, err := c.GetFile("file") + if err != nil { + panic(err) + } + defer file.Close() + + filename := header.Filename + fileId := util.RemoveExt(filename) + + fileBuffer := bytes.NewBuffer(nil) + if _, err = io.Copy(fileBuffer, file); err != nil { + c.ResponseError(err.Error()) + return + } + + fileType := "unknown" + contentType := header.Header.Get("Content-Type") + fileType, _ = util.GetOwnerAndNameFromId(contentType) + + if fileType != "video" { + c.ResponseError(fmt.Sprintf("contentType: %s is not video", contentType)) + return + } + + videoId := video.UploadVideo(fileId, filename, fileBuffer) + if videoId != "" { + video := &object.Video{ + Owner: owner, + Name: fileId, + CreatedTime: util.GetCurrentTime(), + DisplayName: fileId, + VideoId: videoId, + Labels: []*object.Label{}, + } + object.AddVideo(video) + c.ResponseOk(fileId) + } else { + c.ResponseError("videoId is empty") + } +} diff --git a/routers/router.go b/routers/router.go index 46b0925..7d78ca9 100644 --- a/routers/router.go +++ b/routers/router.go @@ -44,6 +44,7 @@ func initAPI() { beego.Router("/api/update-video", &controllers.ApiController{}, "POST:UpdateVideo") beego.Router("/api/add-video", &controllers.ApiController{}, "POST:AddVideo") beego.Router("/api/delete-video", &controllers.ApiController{}, "POST:DeleteVideo") + beego.Router("/api/upload-video", &controllers.ApiController{}, "POST:UploadVideo") beego.Router("/api/get-global-stores", &controllers.ApiController{}, "GET:GetGlobalStores") beego.Router("/api/get-stores", &controllers.ApiController{}, "GET:GetStores") diff --git a/util/path.go b/util/path.go index da9eab4..1923dec 100644 --- a/util/path.go +++ b/util/path.go @@ -29,6 +29,10 @@ func EnsureFileFolderExists(path string) { } } +func RemoveExt(filename string) string { + return filename[:len(filename)-len(filepath.Ext(filename))] +} + func ListFiles(path string) []string { res := []string{} diff --git a/util/string.go b/util/string.go index d2c5950..a546a30 100644 --- a/util/string.go +++ b/util/string.go @@ -1,6 +1,7 @@ package util import ( + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -115,3 +116,12 @@ func WriteBytesToPath(b []byte, path string) { panic(err) } } + +func DecodeBase64(s string) string { + res, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err) + } + + return string(res) +} diff --git a/video/oss_api.go b/video/oss_api.go new file mode 100644 index 0000000..ef95d52 --- /dev/null +++ b/video/oss_api.go @@ -0,0 +1,28 @@ +package video + +import ( + "bytes" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +func getOssClient(endpoint string, accessKeyId string, accessKeySecret string, securityToken string) *oss.Client { + client, err := oss.New(endpoint, accessKeyId, accessKeySecret, oss.SecurityToken(securityToken)) + if err != nil { + panic(err) + } + + return client +} + +func uploadLocalFile(ossClient *oss.Client, bucketName string, objectKey string, fileBuffer *bytes.Buffer) { + bucket, err := ossClient.Bucket(bucketName) + if err != nil { + panic(err) + } + + err = bucket.PutObject(objectKey, fileBuffer) + if err != nil { + panic(err) + } +} diff --git a/video/vod_api.go b/video/vod_api.go index ea36acb..0c228af 100644 --- a/video/vod_api.go +++ b/video/vod_api.go @@ -1,9 +1,12 @@ package video import ( + "bytes" "fmt" + "time" "github.com/aliyun/alibaba-cloud-sdk-go/services/vod" + "github.com/casbin/casbase/util" ) func GetVideoPlayAuth(videoId string) string { @@ -20,3 +23,57 @@ func GetVideoPlayAuth(videoId string) string { playAuth := resp.PlayAuth return playAuth } + +type UploadAddress struct { + Endpoint string `json:"Endpoint"` + Bucket string `json:"Bucket"` + FileName string `json:"FileName"` +} + +type UploadAuth struct { + SecurityToken string `json:"SecurityToken"` + AccessKeyId string `json:"AccessKeyId"` + ExpireUTCTime time.Time `json:"ExpireUTCTime"` + AccessKeySecret string `json:"AccessKeySecret"` + Expiration string `json:"Expiration"` + Region string `json:"Region"` +} + +func UploadVideo(fileId string, filename string, fileBuffer *bytes.Buffer) string { + // https://help.aliyun.com/document_detail/476208.html + + r := vod.CreateCreateUploadVideoRequest() + r.Scheme = "https" + r.FileName = filename + r.Title = fileId + + resp, err := vodClient.CreateUploadVideo(r) + if err != nil { + panic(err) + } + + encodedUploadAddress := resp.UploadAddress + videoId := resp.VideoId + encodedUploadAuth := resp.UploadAuth + + uploadAddressStr := util.DecodeBase64(encodedUploadAddress) + uploadAuthStr := util.DecodeBase64(encodedUploadAuth) + + uploadAddress := &UploadAddress{} + err = util.JsonToStruct(uploadAddressStr, uploadAddress) + if err != nil { + panic(err) + } + + uploadAuth := &UploadAuth{} + err = util.JsonToStruct(uploadAuthStr, uploadAuth) + if err != nil { + panic(err) + } + + ossClient := getOssClient(uploadAddress.Endpoint, uploadAuth.AccessKeyId, uploadAuth.AccessKeySecret, uploadAuth.SecurityToken) + + uploadLocalFile(ossClient, uploadAddress.Bucket, uploadAddress.FileName, fileBuffer) + + return videoId +} diff --git a/web/src/VideoEditPage.js b/web/src/VideoEditPage.js index c4868e8..d7e6fe7 100644 --- a/web/src/VideoEditPage.js +++ b/web/src/VideoEditPage.js @@ -136,7 +136,7 @@ class VideoEditPage extends React.Component { {i18next.t("video:Video ID")}: - { + { this.updateVideoField('videoId', e.target.value); }} /> diff --git a/web/src/VideoListPage.js b/web/src/VideoListPage.js index 47db3cf..59efd3a 100644 --- a/web/src/VideoListPage.js +++ b/web/src/VideoListPage.js @@ -1,6 +1,7 @@ import React from "react"; import {Link} from "react-router-dom"; -import {Button, Col, Popconfirm, Row, Table} from 'antd'; +import {Button, Col, Popconfirm, Row, Table, Upload} from 'antd'; +import {UploadOutlined} from "@ant-design/icons"; import moment from "moment"; import * as Setting from "./Setting"; import * as VideoBackend from "./backend/VideoBackend"; @@ -68,6 +69,45 @@ class VideoListPage extends React.Component { }); } + uploadFile(info) { + const { status, response: res } = info.file; + if (status !== 'uploading') { + console.log(info.file, info.fileList); + } + if (status === 'done') { + if (res.status === 'ok') { + Setting.showMessage("success", `上传视频成功`); + const videoName = res.data; + this.props.history.push(`/videos/${videoName}`); + } else { + Setting.showMessage("error", `上传视频失败:${res.msg}`); + } + } else if (status === 'error') { + Setting.showMessage("success", `上传视频失败`); + } + } + + renderUpload() { + const props = { + name: 'file', + accept: '.mp4', + method: 'post', + action: `${Setting.ServerUrl}/api/upload-video`, + withCredentials: true, + onChange: (info) => { + this.uploadFile(info); + }, + }; + + return ( + + + + ) + } + renderTable(videos) { const columns = [ { @@ -158,8 +198,13 @@ class VideoListPage extends React.Component { (
- {i18next.t("general:Videos")}     - + {i18next.t("general:Videos")} + {/*    */} + {/**/} +      + { + this.renderUpload() + }
)} loading={videos === null}