feat: 图片上传改用my_oss服务
This commit is contained in:
11
PROJECT.md
11
PROJECT.md
@@ -141,16 +141,11 @@ npm run dev
|
|||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
npm start
|
npm start
|
||||||
|
# 默认端口3000,可通过 PORT=3001 npm start 指定端口
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8.3 远程访问
|
### 8.3 图片服务(规划中)
|
||||||
SSH反向隧道将远程30000端口映射到本地3000:
|
计划将图片上传独立出来,新建 `smalltown-upload` 项目处理图片存储和压缩。
|
||||||
```bash
|
|
||||||
./port_forwarding.sh start # 启动
|
|
||||||
./port_forwarding.sh stop # 停止
|
|
||||||
./port_forwarding.sh status # 状态
|
|
||||||
# 访问 http://47.120.74.73:30000
|
|
||||||
```
|
|
||||||
|
|
||||||
## 九、注意事项
|
## 九、注意事项
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import fs from 'fs/promises';
|
|
||||||
import path from 'path';
|
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
|
||||||
const UPLOAD_DIR = path.join(process.cwd(), 'public/uploads');
|
|
||||||
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
|
const OSS_URL = 'http://localhost:9000';
|
||||||
|
const API_KEY = '7cf93760ea49b750c96e6078b364e5f0';
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
let imageBuffer: Buffer;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const file = formData.get('file') as File | null;
|
const file = formData.get('file') as File | null;
|
||||||
@@ -20,29 +21,22 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: '仅支持 JPG、PNG、GIF、WebP 格式' }, { status: 400 });
|
return NextResponse.json({ error: '仅支持 JPG、PNG、GIF、WebP 格式' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ext = 'jpg';
|
|
||||||
const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`;
|
|
||||||
const filepath = path.join(UPLOAD_DIR, filename);
|
|
||||||
|
|
||||||
const buffer = await file.arrayBuffer();
|
const buffer = await file.arrayBuffer();
|
||||||
let imageBuffer = Buffer.from(buffer);
|
imageBuffer = Buffer.from(buffer);
|
||||||
|
|
||||||
// 获取图片尺寸
|
// 压缩图片
|
||||||
const image = sharp(imageBuffer);
|
const image = sharp(imageBuffer);
|
||||||
const metadata = await image.metadata();
|
const metadata = await image.metadata();
|
||||||
|
|
||||||
// 如果超过5MB,进行压缩
|
|
||||||
if (imageBuffer.length > MAX_SIZE) {
|
if (imageBuffer.length > MAX_SIZE) {
|
||||||
let quality = 85;
|
let quality = 85;
|
||||||
|
|
||||||
// 先尝试缩小尺寸
|
|
||||||
if (metadata.width && metadata.width > 1920) {
|
if (metadata.width && metadata.width > 1920) {
|
||||||
imageBuffer = await image
|
imageBuffer = await image
|
||||||
.resize(1920, null, { withoutEnlargement: true })
|
.resize(1920, null, { withoutEnlargement: true })
|
||||||
.toBuffer() as any;
|
.toBuffer() as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 循环压缩直到小于5MB
|
|
||||||
while (imageBuffer.length > MAX_SIZE && quality > 30) {
|
while (imageBuffer.length > MAX_SIZE && quality > 30) {
|
||||||
imageBuffer = await sharp(imageBuffer)
|
imageBuffer = await sharp(imageBuffer)
|
||||||
.jpeg({ quality, progressive: true })
|
.jpeg({ quality, progressive: true })
|
||||||
@@ -50,7 +44,6 @@ export async function POST(request: NextRequest) {
|
|||||||
quality -= 10;
|
quality -= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果还是太大,继续缩小尺寸
|
|
||||||
if (imageBuffer.length > MAX_SIZE && metadata.width && metadata.width > 800) {
|
if (imageBuffer.length > MAX_SIZE && metadata.width && metadata.width > 800) {
|
||||||
imageBuffer = await sharp(imageBuffer)
|
imageBuffer = await sharp(imageBuffer)
|
||||||
.resize(800, null, { withoutEnlargement: true })
|
.resize(800, null, { withoutEnlargement: true })
|
||||||
@@ -58,17 +51,36 @@ export async function POST(request: NextRequest) {
|
|||||||
.toBuffer() as any;
|
.toBuffer() as any;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 转换为jpeg并优化
|
|
||||||
imageBuffer = await sharp(imageBuffer)
|
imageBuffer = await sharp(imageBuffer)
|
||||||
.jpeg({ quality: 85, progressive: true })
|
.jpeg({ quality: 85, progressive: true })
|
||||||
.toBuffer() as any;
|
.toBuffer() as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(filepath, imageBuffer);
|
// 上传到 my_oss
|
||||||
|
const formData2 = new FormData();
|
||||||
|
const uint8Array = new Uint8Array(imageBuffer);
|
||||||
|
const blob = new Blob([uint8Array], { type: 'image/jpeg' });
|
||||||
|
formData2.append('file', blob, 'image.jpg');
|
||||||
|
|
||||||
// 返回相对路径
|
const uploadRes = await fetch(`${OSS_URL}/files`, {
|
||||||
const url = `/uploads/${filename}`;
|
method: 'POST',
|
||||||
return NextResponse.json({ url });
|
headers: {
|
||||||
|
'x-api-key': API_KEY,
|
||||||
|
},
|
||||||
|
body: formData2,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!uploadRes.ok) {
|
||||||
|
const err = await uploadRes.text();
|
||||||
|
console.error('OSS upload failed:', err);
|
||||||
|
return NextResponse.json({ error: '上传失败' }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileData = await uploadRes.json();
|
||||||
|
|
||||||
|
// 返回 my_oss 的访问URL
|
||||||
|
const url = `${OSS_URL}/files/${fileData.fileKey}/preview`;
|
||||||
|
return NextResponse.json({ url, fileKey: fileData.fileKey });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload error:', error);
|
console.error('Upload error:', error);
|
||||||
return NextResponse.json({ error: '上传失败' }, { status: 500 });
|
return NextResponse.json({ error: '上传失败' }, { status: 500 });
|
||||||
@@ -78,16 +90,24 @@ export async function POST(request: NextRequest) {
|
|||||||
export async function DELETE(request: NextRequest) {
|
export async function DELETE(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const filename = searchParams.get('filename');
|
const fileKey = searchParams.get('fileKey');
|
||||||
|
|
||||||
if (!filename) {
|
if (!fileKey) {
|
||||||
return NextResponse.json({ error: '缺少文件名' }, { status: 400 });
|
return NextResponse.json({ error: '缺少文件标识' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const filepath = path.join(UPLOAD_DIR, path.basename(filename));
|
const res = await fetch(`${OSS_URL}/files/${fileKey}`, {
|
||||||
await fs.unlink(filepath);
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'x-api-key': API_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
if (res.ok) {
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ error: '删除失败' }, { status: 500 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete error:', error);
|
console.error('Delete error:', error);
|
||||||
return NextResponse.json({ error: '删除失败' }, { status: 500 });
|
return NextResponse.json({ error: '删除失败' }, { status: 500 });
|
||||||
|
|||||||
Reference in New Issue
Block a user