前言:前端传模型ID,后台根据ID去阿里OSS存储下载对应文件(不知文件内部层级结构,且OSS只能单个文件下载),打包成zip字节流形式返回给前端下载。
需求分析:
- 生成OSS文件关系树
- Node做文件下载存储
- 打包返回给前端
一、前期准备
OK! 现在我们做完了需求调研,当然也要确认技术基本架构,因为这个需求是针对模型资源的,后期会继续累加对应需求(上传、拷贝、预览、引用、编辑等),领导要求分模块构建项目,故新构建一个后台项目,如下:
- dependencies
- Koa2(body、router、cors) + mysql2(sequelize) + adm-zip + ali-oss
- devDependencies
- dotenv + nodemon
二、完成业务需求
1.生成OSS文件关系树
我们开发是对接的ali-oss开发文档,会调用内置文件操作api,代码中会写明。
代码遵循单一原则。
// 获取目录和文件地址
async function getUrl(url, modelId) {
// modelOSS.listV2, 列举文件层级最深值为100
return await modelOSS.listV2({
prefix: url ? url : "models/" + modelId + '/',
// 设置正斜线(/)为文件夹的分隔符。
delimiter: "/",
});
}
// 获取文件夹数组集合 和 文件数组集合
// 思路:
// 判断是否是最后一层,
// 使用递归把每一层的文件夹或文件地址存储到数组
// 运用闭包做数据持久化
async function getPathArr(modelId){
const prefixesArr = []; // 文件夹数组集合
const objectsArr = []; // 文件数组集合
let isFirst = true;
const resultPaths = async (url) => {
const resultList = await getUrl(url, modelId); // 获取目录
if(resultList.prefixes) {
// 获取文件夹
resultList.prefixes.forEach(async (subDir) => {
prefixesArr.push(subDir);
// console.log("SubDir: %s", subDir);
});
}
if(resultList.objects) {
// 获取文件
resultList.objects.forEach(async (obj) => {
objectsArr.push(obj.name);
// console.log("Object: %s", obj.name);
});
}
if(url) isFirst = false;
}
if(isFirst) await resultPaths();
for(let i = 0; i < prefixesArr.length; i++){
if(prefixesArr[i].substr(-1) === '/'){
await resultPaths(prefixesArr[i])
}
}
return {
prefixesArr,
objectsArr
}
}
返回数据如下,每个模型ID返回的对应文件都可能不同
2.Node做文件下载存储
-
fs中间件不支持逐级创建文件路径,故手写一个公共方法:
// 通过文件地址,逐级创建文件夹 function newFolders(folderPath) { const arr = folderPath.split('/') // 分割字符串 let path = '' arr.forEach((value, i) => { path += value + '/' if (!fs.existsSync(path)) { //判断是否存在该目录 fs.mkdirSync(path) } }) }
-
type判断传过来的路径类型(文件或文件夹),分类创建或下载
// 通过远端模型文件层级生成文件夹,下载文件 async function downloadModelFile(targeDirList, type){ try{ if(type){ const result = await modelOSS.getStream(targeDirList); // 下载文件 // 创建文件并且写入 const writeStream = fs.createWriteStream(`src/examplefile/${targeDirList}`); result.stream.pipe(writeStream); } else { // 查看是否有此目录,如果没有则创建 if (!fs.existsSync(`src/examplefile/${targeDirList}`)){ newFolders(`src/examplefile/${targeDirList}`); } } }catch(e){ console.log(e) } }
-
本地与远端文件一致时,打包文件
async function downFile(targeDirList, type, modelId) { // targeDirList => 文件或文件夹远端地址 // type => 如果传入代表是文件 // modelId => 模型ID // 如果是文件夹的话直接创建,是文件的话直接下载 try { for (let i = 0; i < targeDirList.length; i++) { downloadModelFile(targeDirList[i], type); // 文件下载 let loopNum = i + 1; if (loopNum === targeDirList.length) { var zip = new AdmZip(); // 压缩文件夹 zip.addLocalFolder('src/examplefile/models/' + modelId); zip.writeZip('src/examplefile/models/' + modelId + '.zip'); } } } catch (e) { console.log(e); return e.status; } }
生成如下:
3.返回给前端
// 抛出模型下载接口
async function exportModel(ctx, next) {
try {
const { modelId } = ctx.request.body;
// 获取文件夹数组集合 和 文件数组集合
const { prefixesArr, objectsArr } = await getPathArr(modelId);
// 目录生成
await downFile(prefixesArr);
// 文件下载到对应目录中
await downFile(objectsArr, 'prefixes', modelId);
// 前端请求返回
let userfilepath = `src/examplefile/models/${modelId}.zip`;
let resultData;
let stat = fs.readFileSync(`src/examplefile/models/${modelId}.zip`);
if(fs.existsSync(userfilepath)) {
resultData = stat.toString('base64');
} else {
resultData = { code: 500,message: "模型文件不存在"};
}
ctx.body = resultData;
} catch (e) {
console.log(e);
}
}
总结
时间过得总是好快,一转眼就过了午休的时间,一转眼也快走过了三年,这篇文章只是对需求的分析和初步的代码实现,大家看着图一乐就好,今天上午我们新的领导给我们开会了,这里与大家共勉:
1.找重点的事来做。
2.让自己有时间思考。
3.自身为核心。
最后
水平有限,还不能写到尽善尽美,希望大家多多交流,跟春野一同进步!!!