| @@ -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") | |||
| } | |||
| } | |||
| @@ -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") | |||
| @@ -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{} | |||
| @@ -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) | |||
| } | |||
| @@ -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) | |||
| } | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -136,7 +136,7 @@ class VideoEditPage extends React.Component { | |||
| {i18next.t("video:Video ID")}: | |||
| </Col> | |||
| <Col span={22} > | |||
| <Input value={this.state.video.videoId} onChange={e => { | |||
| <Input disabled={true} value={this.state.video.videoId} onChange={e => { | |||
| this.updateVideoField('videoId', e.target.value); | |||
| }} /> | |||
| </Col> | |||
| @@ -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 ( | |||
| <Upload {...props}> | |||
| <Button type="primary" size="small"> | |||
| <UploadOutlined /> 上传视频(.mp4) | |||
| </Button> | |||
| </Upload> | |||
| ) | |||
| } | |||
| renderTable(videos) { | |||
| const columns = [ | |||
| { | |||
| @@ -158,8 +198,13 @@ class VideoListPage extends React.Component { | |||
| <Table columns={columns} dataSource={videos} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} | |||
| title={() => ( | |||
| <div> | |||
| {i18next.t("general:Videos")} | |||
| <Button type="primary" size="small" onClick={this.addVideo.bind(this)}>{i18next.t("general:Add")}</Button> | |||
| {i18next.t("general:Videos")} | |||
| {/* */} | |||
| {/*<Button type="primary" size="small" onClick={this.addVideo.bind(this)}>{i18next.t("general:Add")}</Button>*/} | |||
| | |||
| { | |||
| this.renderUpload() | |||
| } | |||
| </div> | |||
| )} | |||
| loading={videos === null} | |||