Skip to content

Commit e75939f

Browse files
committed
feat: 支持各种格式上传&文件默认缩略图
1 parent c5435fc commit e75939f

15 files changed

Lines changed: 347 additions & 207 deletions

constants/file.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const tinifySupportedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
2+
3+
const imageMimeTypes = [
4+
'image/jpeg',
5+
'image/png',
6+
'image/webp',
7+
'image/gif',
8+
'image/bmp',
9+
'image/tiff',
10+
'image/x-icon',
11+
'image/svg+xml'
12+
];
13+
14+
module.exports = {
15+
imageMimeTypes,
16+
tinifySupportedMimeTypes
17+
}

index.js

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,20 @@ const { koaBody } = require('koa-body');
44
const tinify = require('tinify');
55
const path = require('path');
66
const fs = require('fs');
7-
const mime = require('mime-types');
87
const sharp = require('sharp');
98
const { checkAndCreateTable } = require('./utils/checkAndCreateTable');
109
const pool = require('./utils/db');
1110
const { appendSuffixToFilename } = require('./utils/appendSuffixToFilename');
1211
const { v4: uuidv4 } = require('uuid');
12+
const { detectFileType } = require('./utils/detectFileType');
13+
const { imageMimeTypes, tinifySupportedMimeTypes} = require('./constants/file')
1314
require('dotenv').config({ path: '.env.local' });
1415

1516
const app = new Koa();
1617
const router = new Router();
1718

1819
tinify.key = process.env.TINIFY_KEY;
1920

20-
const tinifySupportedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
21-
const imageMimeTypes = [
22-
'image/jpeg',
23-
'image/png',
24-
'image/webp',
25-
'image/gif',
26-
'image/bmp',
27-
'image/tiff',
28-
'image/x-icon',
29-
'image/svg+xml'
30-
];
31-
32-
function getMimeType(filePath) {
33-
const ext = path.extname(filePath).toLowerCase();
34-
switch (ext) {
35-
case '.jpg':
36-
case '.jpeg':
37-
return 'image/jpeg';
38-
case '.png':
39-
return 'image/png';
40-
case '.gif':
41-
return 'image/gif';
42-
case '.webp':
43-
return 'image/webp';
44-
default:
45-
return 'application/octet-stream';
46-
}
47-
}
48-
4921
app.use(require('koa-static')(path.join(__dirname, 'public')));
5022

5123
const createDirectories = () => {
@@ -86,7 +58,6 @@ router.post('/upload', async (ctx) => {
8658
const responseType = ctx.query.type;
8759

8860
for (const file of fileList) {
89-
const mimeType = mime.lookup(file.filepath);
9061
const fileId = uuidv4(); // 生成文件唯一ID
9162

9263
const outputFilePath = path.join(
@@ -96,8 +67,10 @@ router.post('/upload', async (ctx) => {
9667
fileId + path.extname(file.filepath) // 使用UUID作为文件名称
9768
);
9869

70+
const { mime, ext } = await detectFileType(file.filepath, file);
71+
9972
let outputFileThumbPath = null;
100-
if (isThumb && imageMimeTypes.includes(mimeType)) {
73+
if (isThumb && imageMimeTypes.includes(mime)) {
10174
const fileThumbName = `${fileId}_thumb${path.extname(file.filepath)}`; // 缩略图文件名称
10275

10376
outputFileThumbPath = path.join(
@@ -110,9 +83,22 @@ router.post('/upload', async (ctx) => {
11083
await sharp(file.filepath)
11184
.resize(200, 200) // 调整图像大小为200x200像素
11285
.toFile(outputFileThumbPath);
86+
} else if(isThumb) {
87+
const back_thumbs = {
88+
video: path.join(__dirname, 'public', 'icons', 'video.png'),
89+
sheet: path.join(__dirname, 'public', 'icons', 'xlsx.png'),
90+
pdf: path.join(__dirname, 'public', 'icons', 'pdf.png'),
91+
document: path.join(__dirname, 'public', 'icons', 'doc.png'),
92+
}
93+
94+
const unknown = path.join(__dirname, 'public', 'icons', 'unknown_file_types.png');
95+
96+
const thumb = Object.keys(back_thumbs).find(key => mime.includes(key));
97+
98+
outputFileThumbPath = back_thumbs[thumb] ?? unknown;
11399
}
114100

115-
if (compress && tinifySupportedMimeTypes.includes(mimeType)) {
101+
if (compress && tinifySupportedMimeTypes.includes(mime)) {
116102
await tinify.fromFile(file.filepath).toFile(outputFilePath);
117103
} else {
118104
// 如果不支持压缩或者不要求压缩,保留临时文件则复制文件,否则移动文件
@@ -128,8 +114,20 @@ router.post('/upload', async (ctx) => {
128114

129115
await connection.execute(
130116
`INSERT INTO files (
131-
id, filename, filesize, filelocation, real_file_location, created_by, is_public, thumb_location, is_thumb, is_delete, real_file_thumb_location
132-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
117+
id,
118+
filename,
119+
filesize,
120+
filelocation,
121+
real_file_location,
122+
created_by,
123+
is_public,
124+
thumb_location,
125+
is_thumb,
126+
is_delete,
127+
real_file_thumb_location,
128+
mime,
129+
ext
130+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
133131
[
134132
fileId, // 使用UUID作为ID
135133
path.basename(outputFilePath),
@@ -141,11 +139,13 @@ router.post('/upload', async (ctx) => {
141139
thumb_location,
142140
isThumb,
143141
0,
144-
outputFileThumbPath
142+
outputFileThumbPath,
143+
mime,
144+
ext
145145
]
146146
);
147147

148-
if (responseType === 'md' && imageMimeTypes.includes(mimeType)) {
148+
if (responseType === 'md' && imageMimeTypes.includes(mime)) {
149149
responses.push({
150150
filepath: `![${path.basename(outputFilePath)}](${fileUrl})`
151151
});
@@ -173,11 +173,53 @@ router.get('/files', async (ctx) => {
173173
try {
174174
const limit = parseInt(ctx.query.limit, 10) || 10; // 每页数量,默认为 10
175175
const offset = parseInt(ctx.query.offset, 10) || 0; // 偏移量,默认为 0
176+
const type = ctx.query.type ?? ''; // 获取查询参数中的类型
176177

178+
const types = {
179+
image: 'image',
180+
video: 'video',
181+
all: '',
182+
}
183+
184+
const excludedTypes = ['image', 'video']; // 要排除的类型
185+
186+
let mimeCondition = ''; // 初始化mime条件
187+
188+
// 构建 mime 条件
189+
if (type === 'file') {
190+
mimeCondition = excludedTypes.map(t => `mime NOT LIKE '%${t}%'`).join(' AND ');
191+
} else if (types[type]) {
192+
mimeCondition = `mime LIKE '%${types[type]}%'`;
193+
}
194+
195+
// 构建完整的 SQL 语句
196+
const sql = `
197+
SELECT
198+
created_by,
199+
created_at,
200+
public_by,
201+
public_expiration,
202+
updated_at,
203+
updated_by,
204+
filesize,
205+
filename,
206+
filelocation,
207+
thumb_location,
208+
is_public
209+
FROM
210+
files
211+
WHERE
212+
is_delete = 0
213+
AND is_public = 1
214+
${mimeCondition ? `AND ${mimeCondition}` : ''}
215+
LIMIT ? OFFSET ?`;
216+
217+
// 执行查询
177218
const [rows] = await connection.execute(
178-
`SELECT created_by, created_at, public_by, public_expiration, updated_at, updated_by, filesize, filename, filelocation, thumb_location, is_public FROM files WHERE is_delete = 0 AND is_public = 1 LIMIT ? OFFSET ?`,
219+
sql,
179220
[String(limit), String(offset)]
180221
);
222+
181223

182224
ctx.body = rows;
183225
} catch (error) {
@@ -196,12 +238,22 @@ router.get('/files/:id', async (ctx) => {
196238
try {
197239
// 查询文件数据,只获取必要字段
198240
const [rows] = await connection.execute(
199-
`SELECT filename, is_delete, is_public, public_expiration, real_file_location, real_file_thumb_location, is_thumb
200-
FROM files
201-
WHERE id = ?
202-
AND is_delete = 0
203-
AND (is_public = 1 AND (public_expiration IS NULL OR public_expiration > NOW()))`,
204-
[id]
241+
`
242+
SELECT
243+
filename,
244+
is_delete,
245+
is_public,
246+
public_expiration,
247+
real_file_location,
248+
real_file_thumb_location,
249+
is_thumb,
250+
mime,
251+
ext
252+
FROM files
253+
WHERE id = ?
254+
AND is_delete = 0
255+
AND (is_public = 1 AND (public_expiration IS NULL OR public_expiration > NOW()))`,
256+
[id]
205257
);
206258

207259
if (rows.length === 0) {
@@ -224,9 +276,9 @@ router.get('/files/:id', async (ctx) => {
224276
ctx.body = { message: 'File not found' };
225277
return;
226278
}
227-
279+
const { mime } = await detectFileType(fileLocation);
228280
// 设置响应头
229-
ctx.set('Content-Type', getMimeType(fileLocation));
281+
ctx.set('Content-Type', mime);
230282
ctx.set('Content-Disposition', `inline; filename="${file.filename}"`);
231283

232284
// 返回文件流

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
"@koa/cors": "^5.0.0",
1515
"axios": "^1.7.2",
1616
"dotenv": "^16.4.5",
17+
"file-type": "^19.0.0",
1718
"koa": "^2.15.3",
1819
"koa-body": "^6.0.1",
1920
"koa-router": "^12.0.1",
2021
"koa-static": "^5.0.0",
2122
"koa2-cors": "^2.0.6",
22-
"mime-types": "^2.1.35",
2323
"mysql2": "^3.10.1",
2424
"pm2": "^5.4.0",
2525
"sharp": "0.31.0",

public/icons/doc.png

3.57 KB
Loading

public/icons/document.png

2.42 KB
Loading

public/icons/folders.png

3.19 KB
Loading

public/icons/pdf.png

3.3 KB
Loading

public/icons/psd.png

3.54 KB
Loading

public/icons/shared_folders.png

3.13 KB
Loading
2.87 KB
Loading

0 commit comments

Comments
 (0)