Skip to content

Commit

Permalink
feat(files): #5 File Recycle Bin Function
Browse files Browse the repository at this point in the history
  • Loading branch information
CrazyMrYan committed Jul 6, 2024
1 parent 7a17dd4 commit c502166
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 26 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const redisClient = require("./redis");
const authenticateToken = require("./middleware/authenticateToken");
const cors = require("@koa/cors");
require("./models");
require("./schedules/fileRecover");
require("dotenv").config({ path: ".env.local" });

const app = new Koa();
Expand Down Expand Up @@ -59,5 +60,4 @@ app.listen(process.env.SERVER_PORT, async () => {
await redisClient.connect();
await sequelize.sync();
console.log(`Server is running on ${process.env.INTERNAL_NETWORK_DOMAIN}`);
console.log(`Server is running on ${process.env.PUBLIC_NETWORK_DOMAIN}`);
});
12 changes: 11 additions & 1 deletion models/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,20 @@ const Files = sequelize.define(
allowNull: true,
defaultValue: null,
},
is_delete: {
is_deleted: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
deleted_at: {
type: DataTypes.DATE,
allowNull: true,
},
deleted_by: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
},
real_file_location: {
type: DataTypes.STRING(255),
allowNull: true,
Expand All @@ -96,6 +105,7 @@ const Files = sequelize.define(
tableName: "files",
timestamps: false,
underscored: true,
paranoid: true,
charset: "utf8mb4",
collate: "utf8mb4_general_ci",
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"koa-static": "^5.0.0",
"koa2-cors": "^2.0.6",
"mysql2": "^3.10.1",
"node-cron": "^3.0.3",
"nodemon": "^3.1.4",
"path-to-regexp": "^7.0.0",
"pm2": "^5.4.0",
Expand Down
44 changes: 25 additions & 19 deletions routers/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ router.post(
is_public: isFilePublic,
thumb_location: thumbUrl,
is_thumb: shouldGenerateThumb,
is_delete: false,
is_deleted: false,
real_file_thumb_location: realThumbPath,
mime,
ext: fileExt,
Expand Down Expand Up @@ -200,7 +200,7 @@ router.get("/files", validateQuery(FILES_LIST_GET_QUERY), async (ctx) => {

const { rows, count } = await Files.findAndCountAll({
where: {
is_delete: false,
is_deleted: false,
[Op.or]: [
{ public_expiration: null, is_public: true },
{ public_expiration: { [Op.gt]: new Date() }, is_public: true },
Expand Down Expand Up @@ -250,7 +250,7 @@ router.get("/files/:id", validateParams(FILES_REST_ID), async (ctx) => {
const file = await Files.findOne({
where: {
id,
is_delete: false,
is_deleted: false,
[Op.or]: [
{ public_expiration: null, is_public: true },
{ public_expiration: { [Op.gt]: new Date() }, is_public: true },
Expand Down Expand Up @@ -301,7 +301,7 @@ router.put("/files/:id", validateParams(FILES_REST_ID), async (ctx) => {
const file = await Files.findOne({
where: {
id,
is_delete: false,
is_deleted: false,
created_by: ctx.state.user.id,
},
});
Expand Down Expand Up @@ -338,12 +338,14 @@ router.delete("/files/:id", validateParams(FILES_REST_ID), async (ctx) => {
const { id } = ctx.params;

try {
const deleted_by = ctx.state.user.id;
// 查找文件
const file = await Files.findOne({
where: {
id,
is_delete: false,
created_by: ctx.state.user.id,
is_deleted: false,
deleted_at: new Date(),
deleted_by,
},
});

Expand All @@ -353,11 +355,11 @@ router.delete("/files/:id", validateParams(FILES_REST_ID), async (ctx) => {
return;
}

// 执行软删除,将 is_delete 字段设置为 true
// 执行软删除,将 is_deleted 字段设置为 true
await file.update({
is_delete: true,
updated_at: new Date(), // 更新更新时间
updated_by: ctx.query.updated_by || "anonymous", // 可以通过查询参数传递更新者
is_deleted: true,
deleted_at: new Date(), // 更新更新时间
deleted_by, // 可以通过查询参数传递更新者
});

// 返回删除成功的信息
Expand All @@ -372,7 +374,7 @@ router.delete("/files/:id", validateParams(FILES_REST_ID), async (ctx) => {
// 文件批量删除接口
router.delete("/files", validateBody(FILES_BODY_BATCH_IDS), async (ctx) => {
const { ids } = ctx.request.body;
const updated_by = ctx.state.user.id;
const deleted_by = ctx.state.user.id;

if (!ids || !Array.isArray(ids) || ids.length === 0) {
ctx.status = 400;
Expand All @@ -384,17 +386,21 @@ router.delete("/files", validateBody(FILES_BODY_BATCH_IDS), async (ctx) => {
// 查找并更新指定的文件
const [numberOfAffectedRows] = await Files.update(
{
is_delete: true,
updated_by: updated_by,
updated_at: new Date(),
is_deleted: true,
deleted_by,
deleted_at: new Date(),
},
{
where: {
id: {
[Op.in]: ids,
},
created_by: ctx.state.user.id,
is_delete: false,
is_deleted: false,
[Op.or]: [
{
created_by: deleted_by,
},
],
},
}
);
Expand Down Expand Up @@ -422,7 +428,7 @@ router.get("/files/:id/preview", validateParams(FILES_REST_ID), async (ctx) => {
const file = await Files.findOne({
where: {
id,
is_delete: false,
is_deleted: false,
[Op.or]: [
{ public_expiration: null, is_public: true },
{ public_expiration: { [Op.gt]: new Date() }, is_public: true },
Expand Down Expand Up @@ -487,7 +493,7 @@ router.get(
const file = await Files.findOne({
where: {
id: id,
is_delete: false,
is_deleted: false,
[Op.or]: [
{ public_expiration: null, is_public: true },
{ public_expiration: { [Op.gt]: new Date() }, is_public: true },
Expand Down Expand Up @@ -550,7 +556,7 @@ router.post(
const files = await Files.findAll({
where: {
id: { [Op.in]: ids },
is_delete: false,
is_deleted: false,
[Op.or]: [
{ public_expiration: null, is_public: true },
{ public_expiration: { [Op.gt]: new Date() }, is_public: true },
Expand Down
92 changes: 92 additions & 0 deletions schedules/fileRecover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const cron = require("node-cron");
const { Op } = require("sequelize");
const fs = require("fs").promises;
const Files = require("../models/files");
const Users = require("../models/users");

// 定时任务每天22:52分运行一次
cron.schedule("52 23 * * *", async () => {
const sevenDaysAgo = new Date(new Date() - 7 * 24 * 60 * 60 * 1000);

try {
// 查找需要删除的记录
const records = await Files.findAll({
where: {
is_deleted: true,
deleted_at: {
[Op.lt]: sevenDaysAgo, // 查找 deletedAt 字段小于七天前的记录
},
},
});

// 收集所有文件删除和用户容量更新的Promise
const fileDeletePromises = [];
const userUpdates = {};

for (const record of records) {
const {
real_file_location,
real_file_thumb_location,
created_by,
file_size,
} = record;

if (real_file_location) {
fileDeletePromises.push(
fs.unlink(real_file_location).catch((err) => {
console.error(`Failed to delete file: ${real_file_location}`, err);
})
);
}

if (real_file_thumb_location) {
fileDeletePromises.push(
fs.unlink(real_file_thumb_location).catch((err) => {
console.error(
`Failed to delete thumbnail: ${real_file_thumb_location}`,
err
);
})
);
}

if (userUpdates[created_by]) {
userUpdates[created_by] -= file_size;
} else {
userUpdates[created_by] =
(
await Users.findOne({
where: { id: created_by },
})
).used_capacity - file_size;
}
}

// 批量删除文件
await Promise.all(fileDeletePromises);

// 批量更新用户容量
const userUpdatePromises = [];
for (const [userId, newCapacity] of Object.entries(userUpdates)) {
userUpdatePromises.push(
Users.update({ used_capacity: newCapacity }, { where: { id: userId } })
);
}
await Promise.all(userUpdatePromises);

// 真实删除数据库记录
await Files.destroy({
where: {
is_deleted: true,
deleted_at: {
[Op.lt]: sevenDaysAgo,
},
},
force: true, // 真实删除
});

console.log("Old records and associated files deleted successfully.");
} catch (error) {
console.error("Error deleting old records and associated files:", error);
}
});
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2589,6 +2589,13 @@ node-addon-api@^6.1.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76"
integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==

node-cron@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2"
integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==
dependencies:
uuid "8.3.2"

node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
Expand Down Expand Up @@ -3735,16 +3742,16 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==

uuid@8.3.2, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

uuid@^10.0.0:
version "10.0.0"
resolved "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==

uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

validate-npm-package-license@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
Expand Down

0 comments on commit c502166

Please sign in to comment.