151 lines
6.2 KiB
JavaScript
151 lines
6.2 KiB
JavaScript
|
|
// 演示数据种子:登录 admin → 建双语演示相册 + 分类 → sharp 生成 12 张真实照片 →
|
|||
|
|
// 全链上传(presign PUT → complete)→ 等 worker 发布 → 建 embed slot 绑相册。
|
|||
|
|
// 产出:观众页 URL + 嵌入宿主页 URL。可重复运行(相册名带时间戳)。
|
|||
|
|
import { execSync } from 'node:child_process';
|
|||
|
|
import { createRequire } from 'node:module';
|
|||
|
|
|
|||
|
|
const require = createRequire(import.meta.url);
|
|||
|
|
const sharp = require(
|
|||
|
|
require('path').join(process.cwd(), 'services/api/node_modules/sharp'),
|
|||
|
|
);
|
|||
|
|
const CN = 'http://127.0.0.1:8080';
|
|||
|
|
const stamp = new Date().toISOString().slice(11, 19).replace(/:/g, '');
|
|||
|
|
|
|||
|
|
const j = (r) => r.json();
|
|||
|
|
const api = async (path, opts = {}) => {
|
|||
|
|
const r = await fetch(CN + path, {
|
|||
|
|
...opts,
|
|||
|
|
headers: { 'content-type': 'application/json', ...(opts.headers || {}) },
|
|||
|
|
});
|
|||
|
|
if (!r.ok && r.status !== 201 && r.status !== 202 && r.status !== 204)
|
|||
|
|
throw new Error(`${path} → ${r.status}: ${await r.text()}`);
|
|||
|
|
return r.status === 204 ? null : j(r);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 1. 登录
|
|||
|
|
const login = await api('/v1/auth/password', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
account: process.env.ADMIN_ACCOUNT || 'admin',
|
|||
|
|
password: process.env.ADMIN_PASSWORD || 'admin-secret-change-me',
|
|||
|
|
}),
|
|||
|
|
});
|
|||
|
|
const auth = { Authorization: `Bearer ${login.sessionToken}` };
|
|||
|
|
console.log('✓ admin 登录');
|
|||
|
|
|
|||
|
|
// 2. 双语相册 + 分类
|
|||
|
|
const album = await api('/v1/albums', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: auth,
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
translations: [
|
|||
|
|
{ locale: 'zh-CN', title: '演示相册 · 现场直击', description: '这是本地演示环境生成的相册,照片由脚本真实上传并经 worker 处理发布。', venue: '广州·广交会展馆' },
|
|||
|
|
{ locale: 'en', title: 'Demo Album · Live', description: 'Generated by the local demo seed; photos really uploaded and processed.', venue: 'Canton Fair Complex' },
|
|||
|
|
],
|
|||
|
|
defaultLocale: 'zh-CN',
|
|||
|
|
startAt: new Date().toISOString(),
|
|||
|
|
}),
|
|||
|
|
});
|
|||
|
|
const cat = await api(`/v1/albums/${album.id}/categories`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: auth,
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
sortOrder: 1,
|
|||
|
|
translations: [
|
|||
|
|
{ locale: 'zh-CN', name: '开幕现场' },
|
|||
|
|
{ locale: 'en', name: 'Opening' },
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
});
|
|||
|
|
console.log(`✓ 相册 ${album.publicToken} + 分类`);
|
|||
|
|
|
|||
|
|
// 3. 12 张真实照片(不同色块/比例,横竖混排)
|
|||
|
|
const palettes = [
|
|||
|
|
['#e74c3c', '#c0392b'], ['#3498db', '#2980b9'], ['#2ecc71', '#27ae60'],
|
|||
|
|
['#f39c12', '#e67e22'], ['#9b59b6', '#8e44ad'], ['#1abc9c', '#16a085'],
|
|||
|
|
['#e91e63', '#ad1457'], ['#3f51b5', '#303f9f'], ['#00bcd4', '#0097a7'],
|
|||
|
|
['#ff5722', '#e64a19'], ['#607d8b', '#455a64'], ['#8bc34a', '#689f38'],
|
|||
|
|
];
|
|||
|
|
const dims = [[1600, 1067], [1200, 1600], [1600, 900], [1080, 1350]];
|
|||
|
|
const ids = [];
|
|||
|
|
for (let i = 0; i < palettes.length; i++) {
|
|||
|
|
const [w, h] = dims[i % dims.length];
|
|||
|
|
const [a, b] = palettes[i];
|
|||
|
|
const svg = `<svg width="${w}" height="${h}"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${a}"/><stop offset="1" stop-color="${b}"/></linearGradient></defs><rect width="${w}" height="${h}" fill="url(#g)"/><text x="50%" y="50%" font-family="sans-serif" font-size="${Math.round(Math.min(w, h) / 6)}" fill="rgba(255,255,255,.85)" text-anchor="middle" dominant-baseline="middle">IMG ${String(i + 1).padStart(2, '0')}</text></svg>`;
|
|||
|
|
const buf = await sharp(Buffer.from(svg)).jpeg({ quality: 88 }).toBuffer();
|
|||
|
|
|
|||
|
|
const up = await api('/v1/uploads', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: auth,
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
albumId: album.id,
|
|||
|
|
categoryId: i < 6 ? cat.id : undefined,
|
|||
|
|
clientTaskId: `demo-${stamp}-${i}`,
|
|||
|
|
filename: `demo-${i + 1}.jpg`,
|
|||
|
|
bytes: buf.length,
|
|||
|
|
contentType: 'image/jpeg',
|
|||
|
|
sourceDeviceId: 'demo-seed-000000000000001',
|
|||
|
|
}),
|
|||
|
|
});
|
|||
|
|
const put = await fetch(up.credential.url, {
|
|||
|
|
method: 'PUT',
|
|||
|
|
headers: up.credential.headers,
|
|||
|
|
body: buf,
|
|||
|
|
});
|
|||
|
|
if (!put.ok) throw new Error(`PUT ${i}: ${put.status}`);
|
|||
|
|
await api(`/v1/uploads/${up.uploadId}/complete`, { method: 'POST', headers: auth });
|
|||
|
|
ids.push(up.photoId);
|
|||
|
|
}
|
|||
|
|
console.log(`✓ ${ids.length} 张照片上传完成,等待 worker 发布...`);
|
|||
|
|
|
|||
|
|
// 4. 等 worker 发布(poll 公开列表)
|
|||
|
|
let seen = 0;
|
|||
|
|
for (let t = 0; t < 40 && seen < ids.length; t++) {
|
|||
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|||
|
|
const list = await fetch(
|
|||
|
|
`${CN}/v1/public/albums/${album.publicToken}/photos?limit=60`,
|
|||
|
|
).then(j);
|
|||
|
|
seen = list.items.length;
|
|||
|
|
}
|
|||
|
|
console.log(`✓ 公开列表可见 ${seen}/${ids.length}(worker+sync 已处理)`);
|
|||
|
|
|
|||
|
|
// 5. embed slot
|
|||
|
|
const slot = await api('/v1/admin/embed-slots', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: auth,
|
|||
|
|
body: JSON.stringify({ name: `官网演示位 ${stamp}` }),
|
|||
|
|
});
|
|||
|
|
await api(`/v1/admin/embed-slots/${slot.id}`, {
|
|||
|
|
method: 'PATCH',
|
|||
|
|
headers: auth,
|
|||
|
|
body: JSON.stringify({ albumId: album.id }),
|
|||
|
|
});
|
|||
|
|
console.log(`✓ 展示位 ${slot.publicToken} 已绑定相册`);
|
|||
|
|
|
|||
|
|
// 6. 点几个赞(让热门排序/气泡有内容)
|
|||
|
|
const ov = 'http://127.0.0.1:8081';
|
|||
|
|
for (const [idx, photoId] of ids.slice(0, 4).entries()) {
|
|||
|
|
for (let k = 0; k < (5 - idx); k++) {
|
|||
|
|
await fetch(`${ov}/v1/public/albums/${album.publicToken}/photos/${photoId}/likes`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'content-type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ sessionId: `demo-user-${idx}`, batchId: `b${idx}-${k}`, delta: 1 }),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 浏览上报
|
|||
|
|
for (let v = 0; v < 5; v++) {
|
|||
|
|
await fetch(`${ov}/v1/public/albums/${album.publicToken}/visits`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'content-type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ visitorId: `demo-visitor-${v}${v}${v}${v}`, requestId: `demo-req-${v}-${stamp}` }),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
console.log('✓ 点赞 + 浏览上报完成');
|
|||
|
|
|
|||
|
|
console.log('\n════════ 演示入口 ════════');
|
|||
|
|
console.log(`观众页(中文): http://localhost:5175/a/${album.publicToken}`);
|
|||
|
|
console.log(`观众页(英文): http://localhost:5175/a/${album.publicToken}?lang=en`);
|
|||
|
|
console.log(`嵌入组件宿主页: http://localhost:5175/__test__/embed-host.html?slot=${slot.publicToken}`);
|
|||
|
|
console.log(`管理后台: http://localhost:5176 (admin / ${process.env.ADMIN_PASSWORD || 'admin-secret-change-me'},相册列表第一个即本相册)`);
|