@@ -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} | |||