feat: server.js v2.0 · CORS支持 · 模板列表API · 生成API · 公开页面静态服务 · 健康检查
This commit is contained in:
parent
e4a529160b
commit
b27cfe195f
@ -1,104 +1,134 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 铸渊图片工作室 · Web 服务
|
||||
* 铸渊封面工作室 · Web 服务 · v2.0
|
||||
* ═══════════════════════════════════════════════════
|
||||
*
|
||||
* 部署到服务器,冰朔可以在浏览器预览已生成的图片。
|
||||
* 我在对话里生成图片后,冰朔可以直接访问链接下载。
|
||||
* 公开页面 + 模板列表 API + 生成 API + 下载端点
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import { generate } from './generate.js'
|
||||
import cors from 'cors'
|
||||
import { generate, listTemplates } from './generate.js'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { readdirSync, existsSync } from 'fs'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const OUTPUT_DIR = join(__dirname, 'output')
|
||||
const PUBLIC_DIR = join(__dirname, 'public')
|
||||
const PORT = process.env.PORT || 3912
|
||||
|
||||
// 确保输出目录存在
|
||||
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true })
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
|
||||
/* ── 静态文件服务 ── */
|
||||
// ── 中间件 ──
|
||||
app.use(cors())
|
||||
app.use(express.json({ limit: '1mb' }))
|
||||
|
||||
// 静态文件:输出图片
|
||||
app.use('/output', express.static(OUTPUT_DIR))
|
||||
// 静态文件:前端页面(默认首页)
|
||||
app.use(express.static(PUBLIC_DIR))
|
||||
|
||||
/* ── 首页 · 预览画廊 ── */
|
||||
app.get('/', (req, res) => {
|
||||
let files = []
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* API
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* GET /api/templates
|
||||
* 获取所有可用模板列表
|
||||
*/
|
||||
app.get('/api/templates', (req, res) => {
|
||||
try {
|
||||
if (existsSync(OUTPUT_DIR)) {
|
||||
files = readdirSync(OUTPUT_DIR)
|
||||
.filter(f => /\.(png|webp|jpg)$/i.test(f))
|
||||
.sort()
|
||||
.reverse()
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const galleryHtml = files.length > 0
|
||||
? files.map(f =>
|
||||
`<a href="/output/${f}" target="_blank"><img src="/output/${f}" loading="lazy"></a>`
|
||||
).join('\n')
|
||||
: '<div class="empty">还没有生成的图片<br>在对话里告诉铸渊你想做什么图</div>'
|
||||
|
||||
res.send(`<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>铸渊图片工作室</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, 'PingFang SC', 'Noto Sans SC', sans-serif;
|
||||
background: #faf8f5; color: #1a1a2e; padding: 40px;
|
||||
max-width: 1000px; margin: 0 auto;
|
||||
const templates = listTemplates()
|
||||
res.json(templates)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: '无法加载模板列表' })
|
||||
}
|
||||
h1 { font-size: 28px; margin-bottom: 4px; }
|
||||
.sub { color: #6b7280; margin-bottom: 24px; font-size: 14px; }
|
||||
.gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.gallery a {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e5e7eb;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.gallery a:hover { transform: scale(1.02); }
|
||||
.gallery img { width: 100%; display: block; }
|
||||
.empty {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center; padding: 80px 20px;
|
||||
color: #9ca3af; font-size: 16px; line-height: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎨 铸渊图片工作室</h1>
|
||||
<p class="sub">冰朔 · 你的配图我包了 · 在对话里告诉我内容就行</p>
|
||||
<div class="gallery">${galleryHtml}</div>
|
||||
</body>
|
||||
</html>`)
|
||||
})
|
||||
|
||||
/* ── 生成 API ── */
|
||||
|
||||
/**
|
||||
* POST /api/generate
|
||||
* 生成封面图片
|
||||
*
|
||||
* Body:
|
||||
* templateId - 模板ID(默认第一个)
|
||||
* presetId - 预设风格ID
|
||||
* title - 标题
|
||||
* body - 正文
|
||||
* subtitle - 副标题(可选)
|
||||
* tag - 标签(可选)
|
||||
* layout - 布局(default/hero/quote)
|
||||
*/
|
||||
app.post('/api/generate', async (req, res) => {
|
||||
try {
|
||||
const result = await generate(req.body)
|
||||
const {
|
||||
templateId,
|
||||
presetId = '',
|
||||
title = '',
|
||||
body = '',
|
||||
subtitle = '',
|
||||
tag = '',
|
||||
layout = 'default',
|
||||
} = req.body
|
||||
|
||||
if (!title && !body) {
|
||||
return res.status(400).json({ error: '请至少提供标题或正文内容' })
|
||||
}
|
||||
|
||||
const result = await generate({
|
||||
templateId,
|
||||
presetId,
|
||||
title,
|
||||
body,
|
||||
subtitle,
|
||||
tag,
|
||||
layout,
|
||||
})
|
||||
|
||||
// 构造可访问的 URL
|
||||
const filename = result.files[0].split('/').pop()
|
||||
const url = `/output/${filename}`
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
url,
|
||||
files: result.files,
|
||||
urls: result.files.map(f => `/output/${f.split('/').pop()}`),
|
||||
templateId: result.templateId,
|
||||
templateName: result.templateName,
|
||||
presetId: result.presetId,
|
||||
layout: result.layout,
|
||||
filename,
|
||||
})
|
||||
} catch (err) {
|
||||
res.status(400).json({ success: false, error: err.message })
|
||||
console.error('生成失败:', err.message)
|
||||
res.status(500).json({ error: err.message || '生成失败' })
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🎨 铸渊图片工作室运行在 http://localhost:${PORT}`)
|
||||
|
||||
/**
|
||||
* GET /api/health
|
||||
* 健康检查
|
||||
*/
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
||||
})
|
||||
|
||||
|
||||
// ── 兜底:SPA 路由 ──
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(join(PUBLIC_DIR, 'index.html'))
|
||||
})
|
||||
|
||||
|
||||
// ── 启动 ──
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🎨 铸渊封面工作室 v2.0 运行在 http://localhost:${PORT}`)
|
||||
console.log(` 📋 模板API: http://localhost:${PORT}/api/templates`)
|
||||
console.log(` 🎯 生成API: http://localhost:${PORT}/api/generate`)
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user