app-lib-mock-server-stream
Version:
流媒体文件-【app-lib-mock-server】
462 lines (379 loc) • 16.2 kB
JavaScript
/*************************************************************************************
*
* 文件操作
*
* 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 }