UNPKG

app-lib-mock-server-stream

Version:

流媒体文件-【app-lib-mock-server】

462 lines (379 loc) 16.7 kB
/************************************************************************************* * * 文件操作 * * 1. 从服务中获取或者创建流文件表 * 2. 匹配当前的地址服务 * 3. 初始化磁盘到库 * 4. 返回数据当前请求的数据 * * **************************************************************************************/ /************************************************************************************* * * 功能描述 * 1. 能够编辑管理文件磁盘文件夹下的所有文件 * 2. 操作和查询比较慢-非常快速查找 * 3. 能够快速下载文件 * **************************************************************************************/ /************************************************************************************* * * 处理描述 * 1. 直接抄作表数据的时候 会操作对应的磁盘文件 返回的是数据 * 2. 通过matchUrl进行抄作时候 也会操作表 返回的文件流 * 3. 支持.文件和空文件下载 部支持文件夹下载 * 4. 可以支持自定配置 sendConfig 进行发送文件的配置 * * "sendConfig": { * "fileNameField": "App-FileName", // 文件名字段 * "isAllowEmpty": true, // 是否运行为空 * "dotfiles": "allow", // 是否支持.文件 * "headers": {} // 自定义响应头 * } * * * **************************************************************************************/ /************************************************************************************* * * 遇到的问题 * 1. 匹配地址不能抄作资源 * 解决方法: 直接挂载解析参数-> get 直接下载 post 直接上传 udate 直接更新数据和文件 delete 直接删除文件和数据 * 数据的操作 直接走原始的逻辑即可 * **************************************************************************************/ const { getStreamServers } = require('./base'); const { scanDisk, getFilePath } = require('./scan'); const multer = require('multer') const { STREAM_COMPLETE_PATH, STREAM_NAMESPACE_NAME } = require('./constant'); const startScan = (streamData) => { if (!streamData.length) return; let logData = streamData.map(({ matchUrl, displayName, diskPath, storageDatabaseName }) => ({ stream: displayName, diskPath, matchUrl, database: storageDatabaseName })); let res = scanDisk(streamData); log.T(logData.map((item, index) => { let { useTime, scanData } = res[index]; let total = scanData.length; let fileNum = scanData.filter(v => v.isFile).length; return { ...item, files: fileNum, directory: total - fileNum, total, useTime }; })) } /** * 初始化磁盘数据 */ const initScanStream = (streamData) => { // 启动的进行处理 let scanDatas = streamData.filter(v => v.isEnable); // 未初始化 或者已经初始化 但是需要刷新的进行处理 scanDatas = scanDatas.filter(({ startRefresh, isInit }) => isInit ? startRefresh : true); startScan(scanDatas) } const initStream = (middlewareConfig) => { // 不存在代理数据 则继续 let streamData = getStreamServers(middlewareConfig); if (!streamData.length) { Frame.log.md('stream', `no config or enable any stream path, you can config in ${STREAM_COMPLETE_PATH}`); return []; } return streamData; } const createData = (body, parentItem, file) => { let fullName = file?.originalname || body.name; let [name, ext] = fullName.split('.'); let isFile = !!file; let dateTime = + new Date(); return { id: Frame.option.utils.autoId(), parentId: parentItem.id, path: [...parentItem.path,parentItem.displayName], name, displayName: fullName, isFile, ext, size: file?.size || 0, // accesstime: dateTime, updateTime: dateTime, createTime: dateTime, // changeTime: dateTime, ...body, }; } const getONames = (datas) => { let oDatas = {}; datas.forEach(({ displayName, parentId }) => { let existData = oDatas[parentId]; existData || (oDatas[parentId] = {}); oDatas[parentId][displayName] = true; }) return oDatas; } // 获取所有子节点 const getAllChildIds = (data, oDatas) => { let allDatas = Array.isArray(data) ? data : [data]; let childIds = []; allDatas.forEach((item) => { if (item) { let child = item?.children || []; if (child.length) { childIds = childIds.concat(child); let childInstance = child.map(cId => oDatas[cId]); childIds = childIds.concat(getAllChildIds(childInstance, oDatas)); } } }) return childIds; } const streamMiddleware = ({ middlewareConfig, streamConfig, mathPath }) => { return async (req, res, next) => { // Frame.log.mi("进入流文件拦截", req.file || req.files, middlewareConfig, streamConfig, mathPath) let { storageDatabaseName, storageTableName, sendConfig } = streamConfig; let { getData, isError, setData, setSend, setCode } = res.cusRes; let { node: { getRootPath, writeFileSync, mkdir, delFile, delDir }, error: { AppError }, utils: { findById }, cache: { getCacheData }, middlewares: { dealParamData, dealResourcesData } } = Frame.option; let complatePath = `${STREAM_NAMESPACE_NAME}.${storageDatabaseName}.${storageTableName}`; let urlRessourceId = (req.url.split('?')[0] || '').replace('/', ''); // restfull指定的ID let resourceId = urlRessourceId || req.query?.id; // 自定义参数进行处理 req.CUS_PARAMS = { RESOURCE_NAME: complatePath, RESOURCE_ID: resourceId } let existDatas = getCacheData(complatePath, true); let oDatas = getCacheData(complatePath); /*************************************************************************** * * 下载(单个) * * 支持id 或者path进行下载 * *************************************************************************/ if (req.method === "GET") { next(); let isErrorDeal = isError(); // 缓存操作出现错误的时候 直接返回 if (isErrorDeal) return; let data = getData() if (Array.isArray(data)) { if (data.length == 1) { data = data[0]; } else { throw new AppError('MIDDWARE_STREAM_NO_SUPPORT_MULTIPLE', req.method, data.map(item => item?.displayName).join('/')); } } let { root, fileName } = getFilePath(streamConfig?.diskPath, data); let discPath = getRootPath(root); let responsePath = discPath.replace(/\\/g, '/'); // 不支持文件夹 if (!data.isFile) throw new AppError('MIDDWARE_STREAM_IS_NOT_FILE', `[${fileName}]`, `[${responsePath}]`); // 文件夹不做处理 let option = { root: discPath, fileNameField: "App-FileName", // 文件名称字段 isAllowEmpty: true, // 空文件是否允许下载 dotfiles: 'allow', // .文件是否支持 “allow”, “deny”, “ignore” ...(sendConfig || {}), headers: { ...sendConfig?.headers }, } // 自定义配置文件名字段 option.headers['App-FileName-Field'] = option.fileNameField; option.headers[option.fileNameField] = fileName; // 是否运行空文件下载 if (!option.isAllowEmpty) { if (data.size == 0) throw new AppError('MIDDWARE_STREAM_NO_SIZE', `[${fileName}]`, `[${responsePath}]`); // 文件夹不做处理 } setSend(false); // 不用发送 自己发送 setCode(new Promise((resolve) => { res.sendFile(fileName, option, function (err) { if (err) { let appError = new AppError("MIDDWARE_STREAM_SEND_ERROR", `[${fileName}]`, `[${responsePath}]`) res.status(404).send(appError.print().printResult); resolve(404); } }) })); } /*************************************************************************** * * 上传(多个) * * 支持parentId * *************************************************************************/ if (req.method === "POST") { let { body: { parentId, path, name, displayName }, files } = req; // 指定父级 不指定父级 则添加到跟节点 let parentItem; if (parentId) { parentItem = oDatas[parentId] if (!parentItem) throw new AppError('RESOURCE_NO_EXIST', complatePath, parentId); } else { // 一般扫描的第一个几点即使跟几点 使用 一般都会存在一条数据 parentItem = existDatas[0];// 跟节点 } // 需要创建的所有文件或者文件夹数据 let datas = []; // 不包含文件 创建文件夹 if (!files.length) { if (!name) throw new AppError('REQUIRE', "body", "name"); datas = [createData(req.body, parentItem, null)]; } else { // 上传文件 datas = files.map(file => createData(req.body, parentItem, file)) } // 本身校验是否存在相同的名称 let uploadDatas = datas.map(({ displayName }) => displayName); // 存在重复 if (uploadDatas.length != [...new Set(uploadDatas)].length) { throw new AppError('MIDDWARE_STREAM_EXIST_SAME_FILE') } // 校验数据是否存在 let oNames = getONames(existDatas); datas.forEach((item) => { let { parentId, displayName } = item; if (oNames[parentId]?.[displayName]) { let { complatePath } = getFilePath(streamConfig?.diskPath, item); let responsePath = getRootPath(complatePath).replace(/\\/g, '/'); throw new AppError("MIDDWARE_STREAM_EXIST_FILE", responsePath) } }) // 扩展参数 --支持自动刷新 dealParamData(req, res, () => { }); // 批量写入缓存 datas.map((streamData, index) => { req.body = streamData; dealResourcesData(req, res, () => { }) // 写入对应的文件 let { root, complatePath } = getFilePath(streamConfig?.diskPath, streamData); if (streamData.isFile) { writeFileSync(getRootPath(complatePath), req.files[index].buffer); } else { mkdir(getRootPath(root)); } }); return setData(datas); } /*************************************************************************** * * 上传更新(单个) // TODO * * id 支持 更新对应的磁盘和 * *************************************************************************/ if (req.method === "PATCH" || req.method === "PUT") { return ; } /*************************************************************************** * * 复制(单个) TODO * * id 支持 * *************************************************************************/ /*************************************************************************** * * 删除(多个) * * id 支持 --支持嵌套删除文件夹和文件 * *************************************************************************/ if (req.method === "DELETE") { // 扩展参数 --支持自动刷新 dealParamData(req, res, () => { }); // 直接进行数据操作 dealResourcesData(req, res, () => { }); let isErrorDeal = isError(); // 缓存操作出现错误的时候 直接返回 if (isErrorDeal) return; let data = getData() data = Array.isArray(data) ? data : [data]; // 删除所有的子节点 let allChildIds = getAllChildIds(data,oDatas); // 设置父级children if (allChildIds.length) { req.body = { ids: allChildIds } req.CUS_PARAMS.RESOURCE_ID = null; // 清空上一个数据 dealParamData(req, res, () => { }); // 批量删除 dealResourcesData(req, res, () => { }); if (isError()) return; let childData = getData(); setData(data.concat(childData)); } data.map((item) => { let { complatePath } = getFilePath(streamConfig?.diskPath, item); let delPath = getRootPath(complatePath); // 设置父节点 let parentId = item.parentId; let child = oDatas[parentId].children; child = child.filter(v => v != item.id); oDatas[parentId].children = child; let dleteIndex = findById(existDatas, oDatas[parentId]?.id) existDatas[dleteIndex].children = child; if (item.isFile) { delFile(delPath) // 删除文件 } else { delDir(delPath); // 嵌套删除文件夹 } }) return; } // 不支持的操作 throw new AppError('NOT_SUPPORT', "stream", req.method); } } // 自定义发送数据 避免再次发送 const anyMiddlware = (req, res, next) => { let { setCode, getCode } = res.cusRes; let resolve; // 等待数据独缺完毕完毕 setCode(new Promise((res) => { resolve = res; })); const storage = multer.memoryStorage(); // 缓存存储数据 存储在req的files的buffer上 const upload = multer({ storage }); const _next = () => { next(); let code = getCode(); let isPromise = Frame.option.utils.type(code) == "promise"; resolve(isPromise ? 200 : code); // 返回状态 } upload.any()(req, res, _next); } const middleware = (middlewareConfig) => { // 获取配置的流服务器 const streamData = initStream(middlewareConfig); // 目前启动服务的时候进行初始化 initScanStream(streamData); // 按照配置的matchUrl匹配对应的资源 let middlewares = []; // 启用的流配置 let scanDatas = streamData.filter(v => v.isEnable); scanDatas.forEach(streamConfig => { let { matchUrl } = streamConfig; let matchUrls = Array.isArray(matchUrl) ? matchUrl : [matchUrl]; matchUrls = [...new Set(matchUrls)].filter(v => v); matchUrls.forEach(mathPath => { middlewares.push({ position: "center", alias: `${streamConfig.displayName}-[${mathPath}]`, method: 'use', matchPath: mathPath, middlewares: anyMiddlware, middleware: streamMiddleware({ middlewareConfig, streamConfig, mathPath }) }) }) }) return middlewares; } module.exports = { middleware }