前言:前端传模型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, 列举文件层级最深值为100return 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.自身为核心。
最后
水平有限,还不能写到尽善尽美,希望大家多多交流,跟春野一同进步!!!