一、工具专栏的设计思路
博客解决「读」,工具解决「用」。统一放在 /tools 下,但实现上分两类:
| 类型 | 示例 | 实现方式 |
|---|---|---|
| 纯前端 | 地图定位 | 浏览器直连 Mapbox,Token 用 NEXT_PUBLIC_* |
| 需服务端 | 声音复刻、抠图 | Next.js API 转发,密钥不落前端 |
共同约定:
- 在
src/config/tools.ts注册元数据; - 页面壳:
PageHeader+ToolsSubnav+glass-card; - 交互组件放
src/components/tools/*,业务逻辑放src/lib/*。
二、地图定位(Mapbox + 中文标注)
路径:/tools/map,组件 MapLocatorTool.tsx。
2.1 能力
- 输入经纬度或点击地图选点;
- 标注 Marker、飞行定位;
- 深浅色下切换 Mapbox 样式;
- 使用
@mapbox/mapbox-gl-language将标注改为 简体中文。
typescript
import MapboxLanguage from "@mapbox/mapbox-gl-language";
map.addControl(
new MapboxLanguage({ defaultLanguage: "zh-Hans" }),
);
2.2 环境变量
bash
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=pk.xxx
2.3 经纬度工具函数
src/lib/geo.ts 提供 DMS 与十进制度互转、范围校验,表单与地图联动,减少无效坐标。
三、声音复刻合成
路径:/tools/voice-clone,API:POST /api/tools/voice/synthesize。
3.1 双服务商架构
用户在前端选择:
- 阿里云百炼(默认) :DashScope 声音复刻 +
qwen3-tts-vc合成; - MiniMax 国内:上传参考音频 + 文本合成。
typescript
// route.ts 伪代码
if (provider === "aliyun") {
const { buffer, contentType } = await aliyunClone(common);
return new NextResponse(buffer, { headers: { "Content-Type": contentType } });
}
const buffer = await minimaxClone({ ...common, groupId });
3.2 BYOK(用户自带密钥)
密钥由用户在页面输入,可选 localStorage 记忆;不落服务端磁盘 。也支持服务端 .env 默认 Key(个人部署用)。
src/lib/voice/storage.ts 统一管理多服务商凭证结构。
3.3 错误处理
自定义 VoiceServiceError,将厂商业务码与 HTTP 状态分离。例如 MiniMax 2038(无复刻权限)映射为 403 + 中文说明,避免误返回 502。
3.4 阿里云音频时长
百炼要求参考音频 5~60 秒 。前端 audio-utils.ts:
- 上传后检测时长;
- 超过 60 秒自动截取前 30 秒并转 WAV;
- 提交时确保
FormData上传的是处理后文件。
避免接口返回 Audio duration exceeds maximum allowed limit。
四、rembg 一键抠图
路径:/tools/rembg,API:POST /api/tools/rembg/remove。
4.1 为何用 Python 服务
rembg 基于 ONNX 模型,生态在 Python。Next.js 侧采用 HTTP 代理:
浏览器 → /api/tools/rembg/remove → rembg s (127.0.0.1:7000) → 透明 PNG
.env.local:
bash
REMBG_SERVER_URL=http://127.0.0.1:7000
启动命令:
bash
pip install "rembg[cpu,cli]"
rembg s --host 127.0.0.1 --port 7000 --no-ui
# 或 npm run rembg:server
# 或 docker compose up rembg -d
访问
http://127.0.0.1:7000/返回{"detail":"Not Found"}是正常的,API 在/api/remove,文档在/api。
4.2 服务端逻辑
src/lib/rembg/remove.ts:
- 优先
fetchrembg HTTP 服务; - 连接失败时 降级为
rembg iCLI(若本机已安装); - 超时默认 10 分钟 (
REMBG_TIMEOUT_MS),首次会下载模型; - 路由设置
export const maxDuration = 600。
4.3 前端优化
- 上传前
prepareImageForRembg():最长边缩至 1920px,减轻 CPU 压力; - 模型默认 u2netp(轻量);
- 提交前调用
/api/tools/rembg/status检测服务是否就绪。
4.4 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 503 无法连接 | rembg 未启动 | npm run rembg:server |
| 504 超时 | 大图 / 首次下模型 | 缩小图片、选 u2netp、耐心等待 |
| 根路径 404 | 无首页路由 | 忽略,用网站 /tools/rembg |
五、API 路由通用模式
工具类 API 统一约定:
typescript
export const runtime = "nodejs";
export async function POST(request: Request) {
try {
const form = await request.formData();
// 校验大小、MIME、字段
const result = await libCall(...);
return new NextResponse(result, {
headers: { "Content-Type": "...", "Cache-Control": "no-store" },
});
} catch (err) {
if (err instanceof XxxServiceError) {
return NextResponse.json(
{ error: err.message, code: err.code },
{ status: mapStatus(err) },
);
}
return NextResponse.json({ error: "服务器处理失败" }, { status: 500 });
}
}
好处:前端只需 fetch + blob() / json(),错误信息对用户友好。
六、静态导出与工具共存
| 命令 | 博客 | 工具 API |
|---|---|---|
npm run build |
✓ | ✓ |
npm run build:static |
✓ (out/) | ×(API 被移走) |
若部署到 Gitee Pages,工具页 UI 仍在,但抠图/语音需另备 Node 服务或外链 API。
七、系列回顾
- (一) 架构、主题、配置、双模式构建;Next.js 架构与深浅色主题设计
- (二) CSDN 迁移、Markdown、SSG、阅读体验;CSDN 批量迁移与 Markdown 渲染管线
- (三)本文 地图、语音、抠图三大工具的实现与踩坑。