Skip to content

Commit f3dc513

Browse files
committed
feat: init pull version
1 parent 4c2d386 commit f3dc513

11 files changed

Lines changed: 2195 additions & 0 deletions

File tree

.gitignore

100644100755
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ build/Release
4242
node_modules/
4343
jspm_packages/
4444

45+
#
46+
public/files/*
47+
provisional/*
48+
4549
# Snowpack dependency directory (https://snowpack.dev/)
4650
web_modules/
4751

.user.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
open_basedir=/www/wwwroot/source.giao.club/:/tmp/

LICENSE

100644100755
File mode changed.

README.md

100644100755
File mode changed.

index.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// index.js
2+
const Koa = require('koa');
3+
const Router = require('koa-router');
4+
const { koaBody } = require('koa-body');
5+
const tinify = require('tinify');
6+
const path = require('path');
7+
const fs = require('fs');
8+
const mime = require('mime-types');
9+
const { checkAndCreateTable } = require('./utils/checkAndCreateTable');
10+
const pool = require('./utils/db');
11+
require('dotenv').config({ path: '.env.local' });
12+
13+
const app = new Koa();
14+
const router = new Router();
15+
16+
// TinyPNG API 密钥
17+
tinify.key = process.env.TINIFY_KEY;
18+
19+
// Tinify 支持的文件格式
20+
const tinifySupportedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
21+
22+
// 所有图片格式
23+
const imageMimeTypes = [
24+
'image/jpeg',
25+
'image/png',
26+
'image/webp',
27+
'image/gif',
28+
'image/bmp',
29+
'image/tiff',
30+
'image/x-icon',
31+
'image/svg+xml'
32+
];
33+
34+
// 设置静态文件服务目录
35+
app.use(require('koa-static')(path.join(__dirname, 'public')));
36+
37+
// 创建必要的目录
38+
const createDirectories = () => {
39+
const dirs = [
40+
path.join(__dirname, 'provisional'),
41+
path.join(__dirname, 'public', 'files')
42+
];
43+
dirs.forEach((dir) => {
44+
if (!fs.existsSync(dir)) {
45+
fs.mkdirSync(dir, { recursive: true });
46+
}
47+
});
48+
};
49+
50+
createDirectories();
51+
52+
// 配置 koaBody 中间件处理文件上传
53+
app.use(
54+
koaBody({
55+
multipart: true,
56+
formidable: {
57+
uploadDir: path.join(__dirname, 'provisional'), // 临时上传目录
58+
keepExtensions: true // 保留文件扩展名
59+
}
60+
})
61+
);
62+
63+
// 文件上传和压缩的路由
64+
router.post('/upload', async (ctx) => {
65+
const connection = await pool.getConnection();
66+
try {
67+
// 获取上传的文件
68+
const files = ctx.request.files.file;
69+
const fileList = Array.isArray(files) ? files : [files];
70+
const responses = [];
71+
72+
// 检查是否需要压缩
73+
const compress = ctx.query.compress !== 'false'; // 默认压缩
74+
// 检查是否保留临时文件
75+
const keepTemp = ctx.query.keepTemp === 'true'; // 默认不保留临时文件
76+
// 获取请求的返回类型
77+
const responseType = ctx.query.type;
78+
79+
for (const file of fileList) {
80+
// 获取文件的 MIME 类型
81+
const mimeType = mime.lookup(file.filepath);
82+
83+
// 生成输出文件路径
84+
const outputFilePath = path.join(
85+
__dirname,
86+
'public',
87+
'files',
88+
path.basename(file.filepath)
89+
);
90+
91+
if (compress && tinifySupportedMimeTypes.includes(mimeType)) {
92+
// 如果文件类型受 Tinify 支持且要求压缩,使用 TinyPNG API 压缩文件
93+
await tinify.fromFile(file.filepath).toFile(outputFilePath);
94+
} else {
95+
// 如果不支持压缩或者不要求压缩,保留临时文件则复制文件,否则移动文件
96+
if (keepTemp) {
97+
fs.copyFileSync(file.filepath, outputFilePath);
98+
} else {
99+
fs.renameSync(file.filepath, outputFilePath);
100+
}
101+
}
102+
103+
// 构建文件 URL
104+
const fileUrl = `${process.env.PUBLIC_NETWORK_DOMAIN}/files/${path.basename(
105+
outputFilePath
106+
)}`;
107+
108+
// 插入文件信息到数据库
109+
await connection.execute(
110+
`INSERT INTO files (
111+
filename, filesize, filelocation, created_by, is_public
112+
) VALUES (?, ?, ?, ?, ?)`,
113+
[
114+
path.basename(outputFilePath),
115+
fs.statSync(outputFilePath).size,
116+
fileUrl,
117+
ctx.query.createdBy || 'anonymous',
118+
ctx.query.isPublic === 'true'
119+
]
120+
);
121+
122+
if (responseType === 'md' && imageMimeTypes.includes(mimeType)) {
123+
// 如果文件是图片格式且请求类型是 'md',返回 Markdown 格式
124+
responses.push({
125+
filepath: `![${path.basename(outputFilePath)}](${fileUrl})`
126+
});
127+
} else {
128+
// 否则返回普通 URL
129+
responses.push({ filepath: fileUrl });
130+
}
131+
132+
if (!keepTemp && fs.existsSync(file.filepath)) {
133+
// 删除临时上传文件
134+
fs.unlinkSync(file.filepath);
135+
}
136+
}
137+
138+
// 返回响应
139+
ctx.body = fileList.length > 1 ? responses : responses[0];
140+
} catch (error) {
141+
ctx.status = 500;
142+
ctx.body = 'Error processing your request: ' + error.message;
143+
} finally {
144+
connection.release();
145+
}
146+
});
147+
148+
// 获取文件信息的路由
149+
router.get('/files', async (ctx) => {
150+
const connection = await pool.getConnection();
151+
try {
152+
// 获取分页参数
153+
const limit = parseInt(ctx.query.limit, 10) || 10; // 每页数量,默认为 10
154+
const offset = parseInt(ctx.query.offset, 10) || 0; // 偏移量,默认为 0
155+
156+
// 执行查询
157+
const [rows] = await connection.execute(
158+
`SELECT * FROM files LIMIT ? OFFSET ?`,
159+
[limit, offset]
160+
);
161+
162+
ctx.body = rows;
163+
} catch (error) {
164+
ctx.status = 500;
165+
ctx.body = 'Error retrieving files: ' + error.message;
166+
} finally {
167+
connection.release();
168+
}
169+
});
170+
171+
// 启动服务器
172+
app.use(router.routes()).use(router.allowedMethods());
173+
174+
app.listen(process.env.SERVER_PORT, async () => {
175+
console.log(`Server is running on ${process.env.INTERNAL_NETWORK_DOMAIN}`);
176+
console.log(`Server is running on ${process.env.PUBLIC_NETWORK_DOMAIN}`);
177+
await checkAndCreateTable();
178+
});

package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "upload-file",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"engines": {
7+
"node": ">=14.0.0"
8+
},
9+
"scripts": {
10+
"dev": "pm2 start index.js --name upload-file-service --watch",
11+
"stop": "pm2 delete upload-file-service"
12+
},
13+
"dependencies": {
14+
"@koa/cors": "^5.0.0",
15+
"dotenv": "^16.4.5",
16+
"koa": "^2.15.3",
17+
"koa-body": "^6.0.1",
18+
"koa-router": "^12.0.1",
19+
"koa-static": "^5.0.0",
20+
"koa2-cors": "^2.0.6",
21+
"mime-types": "^2.1.35",
22+
"mysql2": "^3.10.1",
23+
"pm2": "^5.4.0",
24+
"tinify": "^1.7.1",
25+
"uuid": "^10.0.0"
26+
}
27+
}

0 commit comments

Comments
 (0)