一、几种实现(readfile.js)
1. 同步版(阻塞执行)
// 导入模块
const path = require('path');
const fs = require('fs');
const pathName = './'; // 需遍历的文件夹路径
const jsonPath = './files.json'; // 生成 json 的文件路径
// 打开/创建文件并写入 JSON
function writeJSON(file, data) {
// 同步写入
fs.writeFileSync(file, JSON.stringify(data, '', '\t'));
}
// 同步操作
((p, j) => {
try {
// 用于存储年份
const years = [];
// 得到想要的数据,作为原材料(未过滤)
const lists = fs.readdirSync(p).map(file => {
// 初始化,加工排序后再给值
const id = null;
// 文件名
const fileName = file;
// 提取文件扩展名
const extension = file.substring(file.lastIndexOf('.') + 1, file.length);
// 将获取的stats对象,解构
let {size, atime, mtime, ctime, birthtime} = fs.statSync(path.join(p, file));
// 文件大小转为 kb 单位,向下取整,小于等于 1kb 均为 1kb
size = `${Math.floor(size / 1000) > 1 ? Math.floor(size / 1000) : 1} kb`;
// 返回需要的数据对象
return {
id,
extension, // 扩展名
fileName, // 文件名
size, // 文件大小
atime, // 最近访问文件时间
mtime, // 最近修改内容时间
ctime, // 最近改变状态时间
birthtime // 文件创建时间
}
});
// 加工1(先升序给id)
lists.sort((a, b) => {
// (根据情况,选取你要排序的一种时间)
return a.mtime - b.mtime;
}).forEach((item, i) => {
// 少于两位自动补零
item.id = `${i+1}`.padStart(2,'0');
});
// 加工2(再降序获取年份)
// 看情况,项目需要JSON倒序就加这步,否则就在上面写
lists.sort((a, b) => {
return b.mtime - a.mtime;
}).forEach(({atime, mtime, ctime, birthtime}) => {
const y = mtime.getFullYear();
years.indexOf(y) < 0 ? years.push(y) : '<-- 避免重复';
});
// 成品(年份分组)
const dirs = years.map(year => {
// 过滤
const files = lists.filter(item => {
// 根据情况,选取你要排序的一种时间
const {atime, mtime, ctime, birthtime} = item;
// 过滤出年份符合的项
if (mtime.getFullYear() === year) return item;
})
// 返回对象作为数组的元素项
return {year, files};
});
// 生成 json
writeJSON(j, dirs);
// 由上而下,错误会阻塞,成功就会执行到此步
console.log('操作成功!');
} catch (err) {
console.error(err);
}
})(pathName, jsonPath);
2. 异步版(回调地狱)
// 导入模块
const path = require('path');
const fs = require('fs');
const pathName = './'; // 需遍历的文件夹路径
const jsonPath = './files.json'; // 生成 json 的文件路径
// 打开/创建文件并写入 JSON
function writeJSON(file, data) {
fs.writeFile(file, JSON.stringify(data, '', '\t'), err => {
err ? console.error(err) : console.log('操作成功!');
});
}
// 异步操作(回调地狱)
((p, j) => {
// 只能在异步回调内读取,外部读取是同步操作,所以空值
const lists = []; // 存储原料
const years = []; // 用于存储年份
// 异步遍历目录
fs.readdir(p, (err, fileArr) => {
if (err) throw err;
// 遍历数组
fileArr.forEach(file => {
// 异步获取每个文件状态信息
fs.stat(path.join(p, file), (err, stats) => {
if (err) throw err;
if (stats.isFile()) {
// 初始化,加工排序后再给值
const id = null;
// 文件名
const fileName = file;
// 提取文件扩展名
const extension = file.substring(file.lastIndexOf('.') + 1, file.length);
// 解构
let {size, atime, mtime, ctime, birthtime} = stats;
// 文件大小转为 kb 单位,向下取整,小于等于 1kb 均为 1kb
size = `${Math.floor(size / 1000) > 1 ? Math.floor(size / 1000) : 1} kb`;
// 填充数组
lists.push({
id,
extension, // 扩展名
fileName, // 文件名
size, // 文件大小
atime, // 最近访问文件时间
mtime, // 最近修改内容时间
ctime, // 最近改变状态时间
birthtime // 文件创建时间
});
}
// 遍历结束,开始干活
if (lists.length === fileArr.length) {
// 加工1(先升序给id)
lists.sort((a, b) => {
// (根据情况,选取你要排序的一种时间)
return a.mtime - b.mtime;
}).forEach((item, i) => {
// 少于两位自动补零
item.id = `${i+1}`.padStart(2,'0');
});
// 加工2(再降序获取年份)
// 看情况,项目需要JSON倒序就加这步,否则就在上面写
lists.sort((a, b) => {
return b.mtime - a.mtime;
}).forEach(({atime, mtime, ctime, birthtime}) => {
const y = mtime.getFullYear();
years.indexOf(y) < 0 ? years.push(y) : '<-- 避免重复';
});
// 成品(年份分组)
const dirs = years.map(year => {
// 过滤
const files = lists.filter(item => {
// 根据情况,选取你要排序的一种时间
const {atime, mtime, ctime, birthtime} = item;
// 过滤出年份符合的项
if (mtime.getFullYear() === year) return item;
})
// 返回对象作为数组的元素项
return {year, files};
})
// 生成 json
writeJSON(j, dirs);
}
});
});
});
})(pathName, jsonPath);
3. promise 版(链式操作)
// 导入模块
const path = require('path');
const fs = require('fs/promises');
const pathName = './'; // 需遍历的文件夹路径
const jsonPath = './files.json'; // 生成 json 的文件路径
// 获取原料
function getLists(p, arr) {
// 通过 all 将结果集一并传递给下一个 then(),【即作为 resolve()】
return Promise.all(arr.map(file => {
// 每个文件的信息组成的数组
return fs.stat(path.join(p, file)).then(stats => {
if (stats.isFile) {
// 初始化,加工排序后再给值
const id = null;
// 文件名
const fileName = file;
// 提取文件扩展名
const extension = file.substring(file.lastIndexOf('.') + 1, file.length);
// 将获取的stats对象,解构
let {size, atime, mtime, ctime, birthtime} = stats;
// 文件大小转为 kb 单位,向下取整,小于等于 1kb 均为 1kb
size = `${Math.floor(size / 1000) > 1 ? Math.floor(size / 1000) : 1} kb`;
// 返回需要的数据对象
return {
id,
extension, // 扩展名
fileName, // 文件名
size, // 文件大小
atime, // 最近访问文件时间
mtime, // 最近修改内容时间
ctime, // 最近改变状态时间
birthtime // 文件创建时间
}
}
});
}));
}
// 原料加工,得到成品
function getDirs(arr) {
// 其实此处可以不用 promise 函数,直接 return 数组结果就行了
return new Promise((resolve, reject) => {
// 用于存储年份
const years = [];
// 加工1(先升序给id)
arr.sort((a, b) => {
// (根据情况,选取你要排序的一种时间)
return a.mtime - b.mtime;
}).forEach((item, i) => {
// 少于两位自动补零
item.id = `${i+1}`.padStart(2,'0');
});
// 加工2(再降序获取年份)
// 看情况,项目需要JSON倒序就加这步,否则就在上面写
arr.sort((a, b) => {
return b.mtime - a.mtime;
}).forEach(({atime, mtime, ctime, birthtime}) => {
const y = mtime.getFullYear();
years.indexOf(y) < 0 ? years.push(y) : '<-- 避免重复';
});
// 成品(年份分组)
resolve(years.map(year => {
// 过滤
const files = arr.filter(item => {
// 根据情况,选取你要排序的一种时间
const {atime, mtime, ctime, birthtime} = item;
// 过滤出年份符合的项
if (mtime.getFullYear() === year) return item;
})
// 返回对象作为数组的元素项
return {year, files};
}));
});
}
// 打开/创建文件并写入 JSON
function writeJSON(file, data) {
// 异步
return fs.writeFile(file, JSON.stringify(data, '', '\t'));
}
// promise
((p, j) => {
fs.readdir(p) // 遍历目录
.then(files => getLists(p, files)) // 获取原料
.then(lists => getDirs(lists)) // 加工完成
.then(data => writeJSON(j, data)) // 出产
.then(suc => console.log('操作成功!')) // 成功
.catch(err => console.error(err)); // 失败
})(pathName, jsonPath);
4. async / await 版(异步改同步)
// 导入模块
const path = require('path');
const fs = require('fs/promises');
const { stat } = require('fs');
const pathName = './'; // 需遍历的文件夹路径
const jsonPath = './files.json'; // 生成 json 的文件路径
// 打开/创建文件并写入 JSON
function writeJSON(file, data) {
// 异步写入(但 await 改为了同步,所以错误回调不需要了)
fs.writeFile(file, JSON.stringify(data, '', '\t'));
}
// async/await 将异步改为了同步写法
(async (p, j) => {
try {
// 用于存储年份
const years = [];
// 得到每个原材料的 promise 对象组成的数组【注意map回调需要加async关键字】
const promiseArr = (await fs.readdir(p)).map(async file => {
// 初始化,加工排序后再给值
const id = null;
// 文件名
const fileName = file;
// 提取文件扩展名
const extension = file.substring(file.lastIndexOf('.') + 1, file.length);
// 将获取的stats对象,解构
let {size, atime, mtime, ctime, birthtime} = await fs.stat(path.join(p, file));
// 文件大小转为 kb 单位,向下取整,小于等于 1kb 均为 1kb
size = `${Math.floor(size / 1000) > 1 ? Math.floor(size / 1000) : 1} kb`;
// 返回需要的数据对象
return {
id,
extension, // 扩展名
fileName, // 文件名
size, // 文件大小
atime, // 最近访问文件时间
mtime, // 最近修改内容时间
ctime, // 最近改变状态时间
birthtime // 文件创建时间
};
});
// 通过 Promise.all 将所有 promise 转为真正的数组
const lists = await Promise.all(promiseArr);
// 加工1(先升序给id)
lists.sort((a, b) => {
// (根据情况,选取你要排序的一种时间)
return a.mtime - b.mtime;
}).forEach((item, i) => {
// 少于两位自动补零
item.id = `${i+1}`.padStart(2,'0');
});
// 加工2(再降序获取年份)
// 看情况,项目需要JSON倒序就加这步,否则就在上面写
lists.sort((a, b) => {
return b.mtime - a.mtime;
}).forEach(({atime, mtime, ctime, birthtime}) => {
const y = mtime.getFullYear();
years.indexOf(y) < 0 ? years.push(y) : '<-- 避免重复';
});
// 成品(年份分组)
const dirs = years.map(year => {
// 过滤
const files = lists.filter(item => {
// 根据情况,选取你要排序的一种时间
const {atime, mtime, ctime, birthtime} = item;
// 过滤出年份符合的项
if (mtime.getFullYear() === year) return item;
})
// 返回对象作为数组的元素项
return {year, files};
});
// 生成 json
await writeJSON(j, dirs);
// 已为同步操作,所以若通畅无阻,即成功
console.log('操作成功!');
} catch (err) {
console.error(err);
}
})(pathName, jsonPath);
二、示例(遍历当前文件夹,并在当前文件夹生成json文件)
1. 文件夹列表
2. Node.js 遍历并生成 JSON 文件
终端cd
进入当前目录(readfile.js目录)输入:node readfile.js
3. JSON 结构(年份基于 mtime 倒序,id基于 mtime 正序)
4. 关于 Stats 对象属性
三、Tips(排序建议)
文件基于
mtime
(最后修改内容时间)进行分组,如果需要基于atime
(最近访问文件时间) /ctime
(最近更改状态时间)/birthtime
(文件创建时间),需在以下地方进行修改:
a. 同步版
b. 异步版
c. promise 版
4. async / await 版