feat: 课程创建
This commit is contained in:
parent
d649a14244
commit
220b46091d
9
apps/admin/src/api/dto.ts
Normal file
9
apps/admin/src/api/dto.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export interface IgetVodRequest {
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface IGetVodeResponse {
|
||||
MediaInfoSet: any[];
|
||||
TotalCount: number;
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
import axios from "axios";
|
||||
import R from "./request";
|
||||
import P from "./process";
|
||||
import { IgetVodRequest } from "./dto";
|
||||
|
||||
export const getVod = () => axios.post("/api/vod", { offset: 0, limit: 5000 });
|
||||
/**
|
||||
* 腾讯vod媒资
|
||||
*/
|
||||
export const getVod = (p: IgetVodRequest) =>
|
||||
R.post("/api/vod", { ...p }).then((d: any) => P.getVod(d.data));
|
||||
|
|
23
apps/admin/src/api/process.ts
Normal file
23
apps/admin/src/api/process.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { IGetVodeResponse } from "./dto";
|
||||
|
||||
/**
|
||||
* 清洗数据
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* 腾讯vod媒资
|
||||
*/
|
||||
getVod: (p: IGetVodeResponse) => {
|
||||
const { TotalCount: total, MediaInfoSet } = p;
|
||||
const mediaList = MediaInfoSet.map((item) => {
|
||||
return {
|
||||
FileId: item.FileId,
|
||||
AdaptStream:
|
||||
item.AdaptiveDynamicStreamingInfo.AdaptiveDynamicStreamingSet[0],
|
||||
BasicInfo: item.BasicInfo,
|
||||
MetaData: item.MetaData,
|
||||
};
|
||||
});
|
||||
return { total, mediaList };
|
||||
},
|
||||
};
|
34
apps/admin/src/api/request.ts
Normal file
34
apps/admin/src/api/request.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { message } from "antd";
|
||||
import axios from "axios";
|
||||
|
||||
const config = {
|
||||
baseURL: "",
|
||||
timeout: 1000 * 15,
|
||||
headers: {},
|
||||
};
|
||||
|
||||
const instance = axios.create(config);
|
||||
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Add a response interceptor
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.data.code === 10000)
|
||||
message.success(`接口: ${response.config.url}, 请求成功`);
|
||||
return response?.data;
|
||||
},
|
||||
(error) => {
|
||||
message.error(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
5
apps/admin/src/view/Course/Create/Appendix.tsx
Normal file
5
apps/admin/src/view/Course/Create/Appendix.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
const Appendix = () => {
|
||||
return <div>附件</div>;
|
||||
};
|
||||
|
||||
export default Appendix;
|
30
apps/admin/src/view/Course/Create/BasicForm/index.less
Normal file
30
apps/admin/src/view/Course/Create/BasicForm/index.less
Normal file
|
@ -0,0 +1,30 @@
|
|||
.preview-course {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 6px;
|
||||
height: 100%;
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 60%;
|
||||
transform: translate(-50%, -50%);
|
||||
p {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
&.title {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
font-size: 22px;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
&.summary {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 6px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
apps/admin/src/view/Course/Create/BasicForm/index.tsx
Normal file
112
apps/admin/src/view/Course/Create/BasicForm/index.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { InboxOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
message,
|
||||
Row,
|
||||
Upload,
|
||||
UploadProps,
|
||||
} from "antd";
|
||||
import { useState } from "react";
|
||||
import "./index.less";
|
||||
const { Dragger } = Upload;
|
||||
const { Meta } = Card;
|
||||
|
||||
interface IProps {
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
const BasicForm = (props: IProps) => {
|
||||
const [preview, setPreivew] = useState({
|
||||
coverUrl: "",
|
||||
title: "标题",
|
||||
summary: "摘要",
|
||||
});
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onValuesChange = (_: any, all: any) => {
|
||||
setPreivew((p) => ({ ...p, ...all }));
|
||||
};
|
||||
|
||||
const coverDragger: UploadProps = {
|
||||
name: "file",
|
||||
multiple: true,
|
||||
action: "/api/upload",
|
||||
onChange(info) {
|
||||
const { status } = info.file;
|
||||
if (status !== "uploading") console.log(info.file, info.fileList);
|
||||
if (status === "done") {
|
||||
const { code, data } = info.file.response;
|
||||
if (code === 10000) {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
const { MediaUrl } = data;
|
||||
setPreivew((p) => ({ ...p, coverUrl: MediaUrl }));
|
||||
}
|
||||
} else if (status === "error") {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
},
|
||||
onDrop(e) {
|
||||
console.log("Dropped files", e.dataTransfer.files);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Form form={form} onValuesChange={onValuesChange}>
|
||||
<Form.Item>
|
||||
<Dragger {...coverDragger} maxCount={1}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p className="ant-upload-hint">支持单个或批量上传</p>
|
||||
</Dragger>
|
||||
</Form.Item>
|
||||
<Form.Item name="title" rules={[{ required: true }]}>
|
||||
<Input type="text" placeholder="标题" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="summary"
|
||||
rules={[{ required: true }]}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="摘要"
|
||||
style={{ height: 120, resize: "none" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div
|
||||
className="preview-course"
|
||||
style={{
|
||||
backgroundImage: !preview.coverUrl
|
||||
? `linear-gradient(
|
||||
to right,
|
||||
#e95659,
|
||||
#e15084,
|
||||
#c55aaa,
|
||||
#976bc4,
|
||||
#5678ce
|
||||
)`
|
||||
: `url(${preview.coverUrl})`,
|
||||
}}
|
||||
>
|
||||
<div className="mask">
|
||||
<p className="title">{preview.title}</p>
|
||||
<p className="summary">{preview.summary} </p>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicForm;
|
5
apps/admin/src/view/Course/Create/MediaBind.tsx
Normal file
5
apps/admin/src/view/Course/Create/MediaBind.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
const MediaBind = () => {
|
||||
return <div>123</div>;
|
||||
};
|
||||
|
||||
export default MediaBind;
|
8
apps/admin/src/view/Course/Create/index.less
Normal file
8
apps/admin/src/view/Course/Create/index.less
Normal file
|
@ -0,0 +1,8 @@
|
|||
.create-course {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.content {
|
||||
padding: 20px 0;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,67 @@
|
|||
import { Button, Form, Input, InputNumber, Row, Space } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Row,
|
||||
Space,
|
||||
Steps,
|
||||
} from "antd";
|
||||
import { useState } from "react";
|
||||
import Appendix from "./Appendix";
|
||||
import BasicForm from "./BasicForm";
|
||||
import "./index.less";
|
||||
import MediaBind from "./MediaBind";
|
||||
|
||||
const CourseCreate = () => {
|
||||
const [current, setCurrent] = useState(0);
|
||||
const steps = [
|
||||
{
|
||||
title: "基本信息",
|
||||
content: <BasicForm />,
|
||||
},
|
||||
{
|
||||
title: "媒体资源",
|
||||
content: <MediaBind />,
|
||||
},
|
||||
{
|
||||
title: "附件",
|
||||
content: <Appendix />,
|
||||
},
|
||||
];
|
||||
|
||||
const items = steps.map((item) => ({ key: item.title, title: item.title }));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form>
|
||||
<div className="create-course">
|
||||
<Steps current={current} items={items} />
|
||||
<div className="content">{steps[current].content}</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<Button
|
||||
style={{
|
||||
margin: "0 8px",
|
||||
visibility: current > 0 ? "visible" : "hidden",
|
||||
}}
|
||||
onClick={() => setCurrent(current - 1)}
|
||||
>
|
||||
上一步
|
||||
</Button>
|
||||
{current < steps.length - 1 && (
|
||||
<Button type="primary" onClick={() => setCurrent(current + 1)}>
|
||||
下一步
|
||||
</Button>
|
||||
)}
|
||||
{current === steps.length - 1 && (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => message.success("Processing complete!")}
|
||||
>
|
||||
创建
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{/* <Form>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: 24 }}
|
||||
name="title"
|
||||
|
@ -11,13 +69,11 @@ const CourseCreate = () => {
|
|||
>
|
||||
<Input placeholder="标题" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<InputNumber placeholder="售价" />
|
||||
</Form.Item>
|
||||
<Form.Item><InputNumber placeholder="售价" /></Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary">提交</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Form> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,69 +10,159 @@ import {
|
|||
Segmented,
|
||||
Tooltip,
|
||||
Image,
|
||||
Typography,
|
||||
Modal,
|
||||
Tag,
|
||||
} from "antd";
|
||||
import { useEffect } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { getVod } from "../../../api";
|
||||
import { useMount } from "../../../hooks";
|
||||
const { Paragraph, Text } = Typography;
|
||||
const { Search } = Input;
|
||||
|
||||
const Library = () => {
|
||||
const dataSource = [
|
||||
{
|
||||
key: "1",
|
||||
name: "K线篇1--特殊K线的量化描述.mp4",
|
||||
duration: 845,
|
||||
const [dataSource, setDataSource] = useState<any>([]);
|
||||
const [total, setTotal] = useState<number | string>("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
|
||||
cover: (
|
||||
<Image
|
||||
height={60}
|
||||
src="https://1500018521.vod2.myqcloud.com/a28b6648vodtranssh1500018521/8a1352da243791580308966554/coverBySnapshot_10_0.jpg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: "Vite + React + TS - Google Chrome 2023-02-15 09-55-08.mp4",
|
||||
duration: 16,
|
||||
cover: (
|
||||
<Image
|
||||
height={60}
|
||||
src="https://1500018521.vod2.myqcloud.com/a28b6648vodtranssh1500018521/29226db4243791580097740418/coverBySnapshot/coverBySnapshot_10_0.jpg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
const colors = [
|
||||
"magenta",
|
||||
"red",
|
||||
"volcano",
|
||||
"orange",
|
||||
"gold",
|
||||
"green",
|
||||
"cyan",
|
||||
].reverse();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "媒体文件名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (_: any, record: any) => {
|
||||
return (
|
||||
<div>
|
||||
<Row>{record.name}</Row>
|
||||
<Row style={{ paddingTop: "5px" }}>
|
||||
{record.m3u8SubStreamList.map((item: any, index: number) => (
|
||||
<Tag color={colors[index]}>{item}</Tag>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={{ paddingTop: "5px" }}>
|
||||
<Text type="secondary">时长: {record.duration}s</Text>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "时长",
|
||||
dataIndex: "duration",
|
||||
key: "duration",
|
||||
title: "FileID",
|
||||
dataIndex: "key",
|
||||
key: "key",
|
||||
},
|
||||
{
|
||||
title: "封面图片地址",
|
||||
title: "m3u8 大小",
|
||||
dataIndex: "m3u8Size",
|
||||
key: "m3u8Size",
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "createtime",
|
||||
key: "createtime",
|
||||
},
|
||||
|
||||
{
|
||||
title: "m3u8",
|
||||
dataIndex: "m3u8",
|
||||
key: "m3u8",
|
||||
},
|
||||
{
|
||||
title: "视频封面图",
|
||||
dataIndex: "cover",
|
||||
key: "cover",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "operation",
|
||||
key: "operation",
|
||||
render: (_: any, record: any) => {
|
||||
return (
|
||||
<Space>
|
||||
<a href={record.mp4} target="_blank">
|
||||
预览
|
||||
</a>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||||
console.log("selectedRowKeys changed: ", newSelectedRowKeys);
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
};
|
||||
|
||||
const rowSelection = { selectedRowKeys, onChange: onSelectChange };
|
||||
|
||||
const onSearch = (value: string) => console.log(value);
|
||||
|
||||
useMount(() => {
|
||||
getVod().then((res) => {
|
||||
console.log(res);
|
||||
const computeMediaList = (mediaList: any) => {
|
||||
return mediaList.map((m: any) => ({
|
||||
key: m.FileId,
|
||||
mp4: m.BasicInfo.MediaUrl,
|
||||
m3u8: (
|
||||
<Paragraph style={{ margin: 0 }} copyable={{ text: m.AdaptStream.Url }}>
|
||||
复制
|
||||
</Paragraph>
|
||||
),
|
||||
m3u8Size: (m.AdaptStream.Size / 1024 / 1024).toFixed(2) + " MB",
|
||||
m3u8SubStreamList: m.AdaptStream.SubStreamSet.map(
|
||||
(i: any) => i.Height + "p"
|
||||
),
|
||||
name: m.BasicInfo.Name,
|
||||
duration: m.MetaData.Duration,
|
||||
cover: (
|
||||
<Paragraph
|
||||
style={{ margin: 0 }}
|
||||
copyable={{ text: m.BasicInfo.CoverUrl }}
|
||||
>
|
||||
复制
|
||||
</Paragraph>
|
||||
),
|
||||
status: m.BasicInfo.Status,
|
||||
createtime: dayjs(m.BasicInfo.CreateTime).format("YYYY-MM-DD HH:mm:ss"),
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 最大每次5000条数据,估计这辈子也不可能了
|
||||
*/
|
||||
const fetchVod = () => {
|
||||
setLoading(true);
|
||||
getVod({ offset: 0, limit: 5000 }).then((process: any) => {
|
||||
const { mediaList, total } = process;
|
||||
setDataSource(computeMediaList(mediaList));
|
||||
setTotal(total);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useMount(() => {
|
||||
fetchVod();
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col span={20}>
|
||||
<Col span={14}>
|
||||
<Space>
|
||||
<Segmented options={["生", "熟"]} />
|
||||
<Search
|
||||
|
@ -82,19 +172,29 @@ const Library = () => {
|
|||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={4} style={{ textAlign: "right" }}>
|
||||
<Tooltip title="从腾讯云VOD同步全部视频" placement="left">
|
||||
<Button type="primary" icon={<CloudSyncOutlined />}>
|
||||
同步
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Col span={10} style={{ textAlign: "right" }}>
|
||||
<Space>
|
||||
<Text type="secondary">共计 {total} 条媒体资源</Text>
|
||||
<Tooltip title="从腾讯云VOD同步全部视频" placement="left">
|
||||
<Button
|
||||
onClick={() => fetchVod()}
|
||||
type="primary"
|
||||
icon={<CloudSyncOutlined />}
|
||||
>
|
||||
同步
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginTop: "16px" }}>
|
||||
<Col span={24}>
|
||||
<Card>
|
||||
<Table dataSource={dataSource} columns={columns} />
|
||||
</Card>
|
||||
<Table
|
||||
rowSelection={rowSelection}
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@ export default defineConfig({
|
|||
port: 5174,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://172.20.160.221:7001/api",
|
||||
target: "http://127.0.0.1:7001/api",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
|
|
|
@ -4,3 +4,4 @@ OSS_SECRET=12345
|
|||
|
||||
SECRET_ID=AKID534tZ7OvYzb2KQMwLYaVEl5FBwUtQWbU
|
||||
SECRET_KEY=q9HD6lQimeLp9IH5h7NRJzUpNjwxmPq5
|
||||
SUBAPPID=1500018521
|
|
@ -16,6 +16,7 @@
|
|||
"@midwayjs/static-file": "^3.0.0",
|
||||
"@midwayjs/redis": "^3.0.0",
|
||||
"@midwayjs/typeorm": "^3.0.0",
|
||||
"@midwayjs/upload": "3.10.14",
|
||||
"mongoose": "^6.0.7",
|
||||
"@midwayjs/typegoose": "3.0.0",
|
||||
"@typegoose/typegoose": "10.1.1",
|
||||
|
@ -23,7 +24,8 @@
|
|||
"mysql2": "3.0.1",
|
||||
"dotenv": "16.0.3",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"tencentcloud-sdk-nodejs": "4.0.552"
|
||||
"tencentcloud-sdk-nodejs": "4.0.552",
|
||||
"vod-node-sdk": "1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@midwayjs/cli": "^2.0.0",
|
||||
|
|
7
apps/server/src/biz/code.ts
Normal file
7
apps/server/src/biz/code.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* 业务状态码
|
||||
*/
|
||||
export enum BizCode {
|
||||
OK = 10000,
|
||||
ERROR = 20000,
|
||||
}
|
|
@ -1,4 +1,34 @@
|
|||
import { MidwayAppInfo, MidwayConfig } from '@midwayjs/core';
|
||||
import { uploadWhiteList } from '@midwayjs/upload';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
// '.jpg',
|
||||
// '.jpeg',
|
||||
// '.png',
|
||||
// '.gif',
|
||||
// '.bmp',
|
||||
// '.wbmp',
|
||||
// '.webp',
|
||||
// '.tif',
|
||||
// '.psd',
|
||||
// '.svg',
|
||||
// '.js',
|
||||
// '.jsx',
|
||||
// '.json',
|
||||
// '.css',
|
||||
// '.less',
|
||||
// '.html',
|
||||
// '.htm',
|
||||
// '.xml',
|
||||
// '.pdf',
|
||||
// '.zip',
|
||||
// '.gz',
|
||||
// '.tgz',
|
||||
// '.gzip',
|
||||
// '.mp3',
|
||||
// '.mp4',
|
||||
// '.avi',
|
||||
|
||||
export default (appInfo: MidwayAppInfo): MidwayConfig => {
|
||||
return {
|
||||
|
@ -6,5 +36,19 @@ export default (appInfo: MidwayAppInfo): MidwayConfig => {
|
|||
koa: {
|
||||
port: 7001,
|
||||
},
|
||||
upload: {
|
||||
// mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
|
||||
mode: 'file',
|
||||
// fileSize: string, 最大上传文件大小,默认为 10mb
|
||||
fileSize: '10mb',
|
||||
// whitelist: string[],文件扩展名白名单
|
||||
whitelist: uploadWhiteList,
|
||||
// tmpdir: string,上传的文件临时存储路径
|
||||
tmpdir: join(tmpdir(), 'midway-upload-files'),
|
||||
// cleanTimeout: number,上传的文件在临时目录中多久之后自动删除,默认为 5 分钟
|
||||
cleanTimeout: 5 * 60 * 1000,
|
||||
// base64: boolean,设置原始body是否是base64格式,默认为false,一般用于腾讯云的兼容
|
||||
base64: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as staticFile from '@midwayjs/static-file';
|
|||
import * as orm from '@midwayjs/typeorm';
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as redis from '@midwayjs/redis';
|
||||
import * as upload from '@midwayjs/upload';
|
||||
import { join } from 'path';
|
||||
import { DefaultErrorFilter } from './filter/default.filter';
|
||||
import { NotFoundFilter } from './filter/notfound.filter';
|
||||
|
@ -21,6 +22,7 @@ dotenv.config();
|
|||
staticFile,
|
||||
orm,
|
||||
redis,
|
||||
upload,
|
||||
{
|
||||
component: info,
|
||||
enabledEnvironment: ['local'],
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Inject, Controller, Post, Body } from '@midwayjs/core';
|
||||
import { Inject, Controller, Post, Body, Files, Fields } from '@midwayjs/core';
|
||||
import { Context } from '@midwayjs/koa';
|
||||
import { Validate } from '@midwayjs/validate';
|
||||
import { UserDTO } from '../dto/user.dto';
|
||||
import * as tencentcloud from 'tencentcloud-sdk-nodejs';
|
||||
import { VodSearchDTO } from '../dto/vod.dto';
|
||||
import { BizCode } from '../biz/code';
|
||||
import { uploadImagePromise } from '../util/vod';
|
||||
|
||||
@Controller('/api')
|
||||
export class APIController {
|
||||
|
@ -39,10 +41,29 @@ export class APIController {
|
|||
},
|
||||
};
|
||||
const client = new VodClient(clientConfig);
|
||||
const params = { SubAppId: 1500018521, Offset, Limit };
|
||||
const params = {
|
||||
SubAppId: +process.env.SUBAPPID,
|
||||
Categories: ['Video'],
|
||||
Offset,
|
||||
Limit,
|
||||
};
|
||||
return await client.SearchMedia(params).then(
|
||||
data => data,
|
||||
err => this.ctx.logger.error(err)
|
||||
data => ({ code: BizCode.OK, data }),
|
||||
err => {
|
||||
this.ctx.logger.error(err);
|
||||
throw new Error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/upload')
|
||||
async upload(@Files() files, @Fields() fields) {
|
||||
const tmpPath = files[0].data;
|
||||
return await uploadImagePromise(tmpPath)
|
||||
.then(data => ({ code: BizCode.OK, data }))
|
||||
.catch(err => {
|
||||
this.ctx.logger.error(err);
|
||||
throw new Error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
18
apps/server/src/util/vod.ts
Normal file
18
apps/server/src/util/vod.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { VodUploadClient, VodUploadRequest } from 'vod-node-sdk';
|
||||
|
||||
/**
|
||||
* 上传课程封面图片
|
||||
* @param {string} tmpPath 图片上传midwayjs缓存的地址
|
||||
*/
|
||||
export const uploadImagePromise = (tmpPath: string) => {
|
||||
const { SECRET_ID, SECRET_KEY } = process.env;
|
||||
const client = new VodUploadClient(SECRET_ID, SECRET_KEY);
|
||||
const req = new VodUploadRequest();
|
||||
req.MediaFilePath = tmpPath;
|
||||
req.SubAppId = +process.env.SUBAPPID;
|
||||
return new Promise((resolve, reject) => {
|
||||
client.upload('ap-shanghai', req, (err, data) => {
|
||||
err ? reject(err) : resolve(data);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -16,7 +16,6 @@
|
|||
"@arco-design/web-react": "2.45.0",
|
||||
"@ricons/fluent": "0.12.0",
|
||||
"@ricons/utils": "0.1.6",
|
||||
"dayjs": "1.11.7",
|
||||
"dplayer": "1.27.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -48,7 +48,8 @@
|
|||
"rollup-plugin-babel": "4.4.0",
|
||||
"@babel/core": "7.21.0",
|
||||
"rollup-plugin-dts": "5.2.0",
|
||||
"axios": "1.3.4"
|
||||
"axios": "1.3.4",
|
||||
"dayjs": "1.11.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"object-hash": "^3.0.0"
|
||||
|
|
1468
pnpm-lock.yaml
1468
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user