index.js 14.1 KB
const express = require('express');
const router = express.Router();
const { spawn, exec } = require('child_process');
const fs = require("fs");
var path = require('path')
const OSS = require('ali-oss');
require('dotenv').config(); // 加载环境变量

const method = require("../config/method")
const config = require("../config/config")

// const { GETCLASSURL, GETCLASSURLPARAMETER, PROJECTCATALOG, PROJECTWINCATALOG, BACKMEDIACONFIG } = config
const { YesterdayTime, getRequestClassIds, dayTimeYMD } = method

let siteIds = []

let classid = []
let classidPost = []
let parentData = {}
var classobj = {};
let className = ""
let yesterday = "" // get写入课堂的时间

// spawn("export DISPLAY=:7", { shell: true})

/**
 *
 * @param {*} id 课堂id
 */
class MediaCreat {
    constructor() {
    }

    // 取出所有数据
    async allData() {
        const { startTime, endTime } = YesterdayTime()
        let fileConfig = new MediaCreat().getConfigFileJson()
        if (!fileConfig) return false
        className = siteIds.shift()
        const { GETCLASSURL, GETCLASSURLPARAMETER, classLastNumber } = JSON.parse(fileConfig)
        let page = 1
        if (className) {
            let result = await getRequestClassIds(GETCLASSURL, className, GETCLASSURLPARAMETER.key, startTime, endTime, page)
            // parentData[result.data.data.siteId] = new Set()
            let resultList = result.data.data.list
            for (let j = 0; j < resultList.length; j++) {
                let item = resultList[j]
                let classId = item['classId']
                let number = classId.substr(classId.length - 1, 1)
                if (classLastNumber.includes(number)){
                    classid.push(item)
                    // parentData[result.data.data.siteId].add(classId)
                }
            }
            const { siteId, list, totalPage } = result.data.data
            for (let i = page += 1; i <= totalPage; i++) {
                let result = await getRequestClassIds(GETCLASSURL, className, GETCLASSURLPARAMETER.key, startTime, endTime, i)
                let resultList = result.data.data.list
                for (let j = 0; j < resultList.length; j++) {
                    let item = resultList[j]
                    let classId = item['classId']
                    let number = classId.substr(classId.length - 1, 1)
                    if (classLastNumber.includes(number)) {
                        classid.push(item)
                        // parentData[result.data.data.siteId].add(classId)
                    }
                }
            }
            // parentData[result.data.data.siteId] = Array.from(parentData[result.data.data.siteId])
            if (siteIds.length) {
                return await new MediaCreat().allData()
            }
            this.wrieLog("去重前的classId:------>" + JSON.stringify(classid))
            this.wrieLog("httpData:------>" + JSON.stringify(result.data))
            return true
        } else {
            return false
        }
    }

    wrieLog(text) {
        // 写入log
        let logFile = `./log/${dayTimeYMD().ymd}.txt`
        fs.appendFileSync(logFile, new Date().toLocaleString() + " " + text + '\r\n');
    }

    recordingCreat(id, siteId, type) {
        this.wrieLog(" 课堂录制开始:------>" + id)
        let fileConfig = this.getConfigFileJson()
        if (!fileConfig) return false
        const { BACKMEDIACONFIG, PROJECTWINCATALOG, PROJECTCATALOG } = JSON.parse(fileConfig)
        let mediaDir = PROJECTCATALOG + "/media/"
        let classDir = PROJECTCATALOG + "/media/" + siteId
        let ymdDir = null
        if (type == 'post') {
            ymdDir = PROJECTCATALOG + "/media/" + siteId + "/" + dayTimeYMD().ymd
        } else {
            // get 的目录创建为上一天日期
            ymdDir = PROJECTCATALOG + "/media/" + siteId + "/" + yesterday
        }

        if (!fs.existsSync(mediaDir)) {
            fs.mkdirSync(mediaDir);
        }
        if (!fs.existsSync(classDir)) {
            fs.mkdirSync(classDir);
        }
        if (!fs.existsSync(ymdDir)) {
            fs.mkdirSync(ymdDir);
        }
        let files = fs.readdirSync(ymdDir);
        // let interValGetFile = setInterval(()=>{
        this.wrieLog("ymdDir" + ymdDir)
	this.wrieLog("files:" + files)
        if (files.indexOf(id + ".mp4") != -1) {
            this.wrieLog("已存在:" + id + "课堂号,停止继续录制")
            if (type == 'post') {
                if (classidPost.length) {
                    let shiftData = classidPost.shift()
                    this.wrieLog(" 录制下一节课 课堂号:" + shiftData['classId'])
                    this.recordingCreat(shiftData['classId'], shiftData['siteId'], type)
                }
            } else {
                if (classid.length) {
                    let shiftData = classid.shift()
                    this.wrieLog(" 录制下一节课 课堂号:" + shiftData['classId'])
                    this.recordingCreat(shiftData['classId'], shiftData['siteId'], type)
                }
            }
            return
        }
        // 目前url是linux的写法 win系统不支持
        // export DISPLAY=:7
        // let url = `${path.resolve(__dirname, PROJECTWINCATALOG+"/web_capture_c")} -o=${ymdDir}/${id}.mp4 -u="${BACKMEDIACONFIG.url}?classId=${id}&recordMp4=${BACKMEDIACONFIG.recordMp4}&userId=${BACKMEDIACONFIG.userId}&userName=${BACKMEDIACONFIG.userName}&userRole=${BACKMEDIACONFIG.userRole}&portalIP=${BACKMEDIACONFIG.portalIP}&portalPort=${BACKMEDIACONFIG.portalPort}&channels=${BACKMEDIACONFIG.channels}&playRecord=${BACKMEDIACONFIG.playRecord}" -d=${BACKMEDIACONFIG.d} -s=${BACKMEDIACONFIG.s} -fa=${BACKMEDIACONFIG.fa} -k=${BACKMEDIACONFIG.k} -w=${BACKMEDIACONFIG.w} -h=${BACKMEDIACONFIG.h}`
        // console.log("url", url)
        let url = `${PROJECTWINCATALOG}/web_capture_c -o=${ymdDir}/${id}.mp4 -u="${BACKMEDIACONFIG.url}?classId=${id}&recordMp4=${BACKMEDIACONFIG.recordMp4}&userId=${BACKMEDIACONFIG.userId}&userName=${BACKMEDIACONFIG.userName}&userRole=${BACKMEDIACONFIG.userRole}&portalIP=${BACKMEDIACONFIG.portalIP}&portalPort=${BACKMEDIACONFIG.portalPort}&channels=${BACKMEDIACONFIG.channels}&playRecord=${BACKMEDIACONFIG.playRecord}&language=zh-cn" -d=${BACKMEDIACONFIG.d} -s=${BACKMEDIACONFIG.s} -fa=${BACKMEDIACONFIG.fa} -k=${BACKMEDIACONFIG.k} -w=${BACKMEDIACONFIG.w} -h=${BACKMEDIACONFIG.h}`
        this.wrieLog(" 启动url " + url)
	exec(url, { maxBuffer: 1073741824 }, (err, stdout, stderr) => {
            if (err != null) {
                this.wrieLog(" 错误" + id + ":" + err)
                this.wrieLog(" 错误 stdout" + id + ":" + stdout)
                this.wrieLog(" 错误 stderr" + id + ":" + stderr)
                return
            }
            let files = fs.readdirSync(ymdDir);
            // let interValGetFile = setInterval(()=>{
            if (files.indexOf(id + ".mp4") == -1) {
                this.wrieLog(" 课堂录制未发现该" + id + "课堂号")
            } else {
                if (type == 'get') {
                    if (classid.length) {
                        let shiftData = classid.shift()
                        this.wrieLog(" 录制下一节课 课堂号:" + shiftData['classId'])
                        this.recordingCreat(shiftData['classId'], shiftData['siteId'], type)
                    } else {
                        this.wrieLog("录制结束:------>")
                        fs.writeFile(ymdDir + "/download.json", `{ "code": "0", "success": "ok"}`, function (err) {
                            if (err) {
                                console.log(err);
                            }
                        });
                    }
                } else {
                    if (classidPost.length) {
                        let shiftData = classidPost.shift()
                        this.wrieLog(" 录制下一节课 课堂号:" + shiftData['classId'])
                        this.recordingCreat(shiftData['classId'], shiftData['siteId'], type)
                    } else {
                        this.wrieLog("录制结束:------>")
                        fs.writeFile(ymdDir + "/download.json", `{ "code": "0", "success": "ok"}`, function (err) {
                            if (err) {
                                console.log(err);
                            }
                        });
                    }
                }

            }
        })
    }

    getConfigFileJson() {
        const buffer = fs.readFileSync(process.cwd() + "/config/config.json")
        return String(buffer)
    }
}


router.get('/recording', async function (req, res, next) {
    if (classid.length > 0) {
        // 有正在录制中的课堂,禁止重复
        res.send({ code: "1", message: "有正在录制中的课堂", data: classid });
        return
    }
    new MediaCreat().wrieLog("脚本录制开始:------>")
    let fileConfig = new MediaCreat().getConfigFileJson()
    if (!fileConfig) return false

    const { GETCLASSURLPARAMETER } = JSON.parse(fileConfig)
    siteIds = GETCLASSURLPARAMETER.siteId
    if (siteIds.length == 0) {
        new MediaCreat().wrieLog("脚本录制,未配置siteId:------>" + siteIds)
        res.send({ code: "1", message: "未配置siteId", data: siteIds });
        return
    }
    let result = await new MediaCreat().allData()
    if (result) {
        // 去重
        classobj = {}
        classid = classid.reduce(function (item, next) {
            classobj[next.classId] ? '' : classobj[next.classId] = true && item.push(next);
            return item;
        }, []);
        // 写入log
        new MediaCreat().wrieLog("去重后的classid:------>" + JSON.stringify(classid))
        if (classid.length) {
            for (let i = 0; i < GETCLASSURLPARAMETER.maxMedia; i++) {
                let shiftData = classid.shift()
                if (shiftData) {
                    yesterday = YesterdayTime().ymd
                    new MediaCreat().recordingCreat(shiftData['classId'], shiftData['siteId'], 'get')
                } else {
                    return false
                }
            }
            res.send({ code: "0" });
        } else {
            res.send({ code: "1", message: "无录制数据" });
        }
    } else {
        res.send({ code: "1", message: "无录制数据" });
    }
});

/**
 * {
 * "classId":[{ classId: '389675110', siteId: 'kuaikuenglish' }]
 * }
 */
router.post('/recording', async function (req, res, next) {
    new MediaCreat().wrieLog("录制启动:------>")
    let fileConfig = new MediaCreat().getConfigFileJson()
    if (!fileConfig) return false
    let { classId, maxMedia } = req.body
    if (classidPost.length > 0) {
        // 有正在录制中的课堂,禁止重复
        res.send({ code: "1", message: "有正在录制中的课堂", data: classidPost });
        return
    }
    if (classId && classId.length) {
        classidPost = classId
        if (!maxMedia || maxMedia == undefined) {
            maxMedia = 1
        }
        for (let i = 0; i < maxMedia; i++) {
            let shiftData = classidPost.shift()
            if (shiftData) {
                new MediaCreat().recordingCreat(shiftData['classId'], shiftData['siteId'], 'post')
            } else {
                return false
            }
        }
        res.send({ code: "0" });
    } else {
        res.send({ code: "1", message: "无录制数据" });
    }

})

// 判断该视频文件是否存在
router.post('/fileExists', async (req, res) => {
    const { siteId, classId, classStartTime } = req.body;

    // 检查输入参数
    if (!siteId) {
        return res.status(400).send({ code: 2, message: "机构编码无效" });
    }
    if (!classId) {
        return res.status(400).send({ code: 3, message: "课堂号无效" });
    }

    try {
        // 检查环境变量是否设置
        if (!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || !process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET) {
            throw new Error('Environment variables are not set correctly.');
        }

        // 创建OSS客户端实例
        const client = new OSS({
            region: 'oss-cn-beijing',
            accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
            accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
            authorizationV4: true,
            bucket: 'xdymp4'
        });

        // 构建文件前缀
        let prefix = '';
        if (classStartTime) {
            prefix = `oss/${siteId}/${classStartTime}/${classId}.mp4`;
        } else {
            // 如果没有提供classStartTime,默认返回文件未生成
            let classUrl = `https://pclive.xuedianyun.com/pcBase/pclive2/release/index.html?portalIP=saas.xuedianyun.com&portalPort=80&classId=${classId}&channels=2&playRecord=1#/`;
            return res.send({ code: 1, message: "文件未生成", classUrl });
        }

        // 使用 await 处理异步操作
        const result = await client.list({ prefix });
        console.log('OSS list result:', JSON.stringify(result, null, 2)); // 日志记录详细结果

        // 检查是否有匹配的文件
        let isVideo = false;
        let url = "";
        if (result.res.status === 200 && result.objects && result.objects.length > 0) {
            for (let item of result.objects) {
                // 更精确的匹配逻辑,确保找到的是目标文件
                if (item.name === `${prefix}`) {
                    isVideo = true;
                    url = item.name;
                    break;
                }
            }
        }

        // 根据结果发送响应
        if (!isVideo) {
            let classUrl = `https://pclive.xuedianyun.com/pcBase/pclive2/release/index.html?portalIP=saas.xuedianyun.com&portalPort=80&classId=${classId}&channels=2&playRecord=1#/`;
            res.send({ code: 1, message: "文件未生成", classUrl });
        } else {
            let classUrl = `https://xdymp4.xuedianyun.com/${url}`;
            res.send({ code: 0, message: "文件已生成", classUrl });
        }

    } catch (err) {
        console.error('Error checking file existence:', err);
        res.status(500).send({ code: -1, message: "服务器内部错误", error: err.message });
    }
});

module.exports = router