feat: 资源部署
This commit is contained in:
@@ -7,5 +7,11 @@ node_modules/
|
||||
|
||||
# voice
|
||||
data/voice/*
|
||||
|
||||
# 本地部署拷贝目录(由 deploy-assets 生成)
|
||||
pipi-assets/
|
||||
|
||||
# 生成的 nginx 配置(含机器相关路径,每次运行脚本会重新生成)
|
||||
scripts/nginx-pipi-assets.conf
|
||||
# 输出文件(可选,如果不想提交生成的数据)
|
||||
# data/char/char_introduce.json
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 生成资源
|
||||
|
||||
pnpm generate-introduce 生成简介。混元生成的简介质量不高,自己使用Cursor的tab自动补全,反而质量更高一些
|
||||
|
||||
pnpm generate-voice 根据 generate-introduce 生成的简介,来生成音频。
|
||||
|
||||
# 本地开发(拷贝到 pipi-assets,并生成 nginx 配置)
|
||||
|
||||
pnpm run deploy-assets
|
||||
|
||||
# 或
|
||||
|
||||
pnpm run deploy-assets:local
|
||||
|
||||
# 服务器部署前(拷贝到 /var/www/pipi-assets,并生成对应 nginx 配置)
|
||||
|
||||
pnpm run deploy-assets:server
|
||||
|
||||
# 自定义目标目录
|
||||
|
||||
node scripts/deploy-assets.js --target /path/to/dir
|
||||
|
||||
# 指定 nginx 配置输出路径
|
||||
|
||||
node scripts/deploy-assets.js --server --nginx /etc/nginx/sites-available/pipi-assets
|
||||
@@ -6,7 +6,10 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"generate-introduce": "cross-env node scripts/generate_char_introduce.js",
|
||||
"generate-voice": "node scripts/generate_char_voice.js"
|
||||
"generate-voice": "node scripts/generate_char_voice.js",
|
||||
"deploy-assets": "node scripts/deploy-assets.js",
|
||||
"deploy-assets:local": "node scripts/deploy-assets.js --local",
|
||||
"deploy-assets:server": "node scripts/deploy-assets.js --server"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# 用本机 Nginx 测试 pipi-assets 配置
|
||||
|
||||
按顺序做下面几步即可。
|
||||
|
||||
---
|
||||
|
||||
## 1. 检查是否已安装 Nginx
|
||||
|
||||
在终端执行:
|
||||
|
||||
```bash
|
||||
nginx -v
|
||||
```
|
||||
|
||||
- **有版本号**(如 `nginx version: nginx/1.27.5`)→ 已安装,跳到第 2 步。
|
||||
- **报错 command not found** → 需要先安装。Mac 推荐用 Homebrew:
|
||||
```bash
|
||||
brew install nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 确认资源目录存在
|
||||
|
||||
本配置会使用项目下的 `pipi-assets` 目录。若还没有,先执行一次:
|
||||
|
||||
```bash
|
||||
cd platform-pipi/packages/voice-production
|
||||
pnpm run deploy-assets:local
|
||||
```
|
||||
|
||||
这样会生成 `pipi-assets/`(内含 `char/`、`voice/` 等)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 让主 Nginx 加载本项目的配置
|
||||
|
||||
你的 Nginx 主配置在 **Homebrew** 安装时一般是:
|
||||
|
||||
- Apple Silicon:`/opt/homebrew/etc/nginx/nginx.conf`
|
||||
- Intel:`/usr/local/etc/nginx/nginx.conf`
|
||||
|
||||
任选一种方式,让主配置把「本项目里的 pipi-assets 配置」包含进去。
|
||||
|
||||
### 方式 A:在主配置里加一行 include(推荐)
|
||||
|
||||
1. 用编辑器打开主配置,例如:
|
||||
|
||||
```bash
|
||||
open -e /opt/homebrew/etc/nginx/nginx.conf
|
||||
```
|
||||
|
||||
(Intel 机器把路径改成 `/usr/local/etc/nginx/nginx.conf`)
|
||||
|
||||
2. 在 `http { ... }` 块里找合适位置(例如在 `include servers/*;` 那一行**上面**),加一行:
|
||||
|
||||
```nginx
|
||||
include /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/scripts/nginx-pipi-assets-local.conf;
|
||||
```
|
||||
|
||||
注意把路径改成你本机实际的项目路径。
|
||||
|
||||
3. 保存退出。
|
||||
|
||||
### 方式 B:放到 Nginx 的 servers 目录
|
||||
|
||||
1. 若没有 `servers` 目录,先建一个:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/homebrew/etc/nginx/servers
|
||||
```
|
||||
|
||||
(Intel 用 `/usr/local/etc/nginx/servers`)
|
||||
|
||||
2. 做一份软链,指向项目里的本地配置:
|
||||
```bash
|
||||
sudo ln -sf /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/scripts/nginx-pipi-assets-local.conf /opt/homebrew/etc/nginx/servers/pipi-assets.conf
|
||||
```
|
||||
主配置里已有 `include servers/*;` 时会自动加载。
|
||||
|
||||
---
|
||||
|
||||
## 4. 检查配置并重载 Nginx
|
||||
|
||||
```bash
|
||||
# 检查语法是否正确
|
||||
nginx -t
|
||||
|
||||
# 若无报错,重载 Nginx(不中断已有连接)
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
若你是用 `brew services start nginx` 启用的,可以改为:
|
||||
|
||||
```bash
|
||||
brew services restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 在浏览器或终端里测试
|
||||
|
||||
本地站点监听 **8081 端口**,用 `localhost` 即可访问(无需改 hosts)。
|
||||
|
||||
- 健康检查:
|
||||
**http://localhost:8081/health**
|
||||
应看到页面内容:`ok`
|
||||
|
||||
- 角色 JSON:
|
||||
**http://localhost:8081/char/char_introduce.json**
|
||||
应返回 JSON 内容
|
||||
|
||||
- 语音文件(示例):
|
||||
**http://localhost:8081/voice/一\_4e00.mp3**
|
||||
应能播放或下载
|
||||
|
||||
终端快速验证:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8081/health
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/char/char_introduce.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
|
||||
- **端口**:使用 8081 是为了不和 80 端口上已有的其他站点(如 simulation.data-platform.local 等)冲突。
|
||||
- **配置文件**:`nginx-pipi-assets.conf` 是给服务器部署用的(80 端口 + 二级域名);`nginx-pipi-assets-local.conf` 是给本机测试用的(8081 + localhost),路径已指向项目下的 `pipi-assets`。
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 将 data 目录内容拷贝到指定目录(本地开发或服务器),并生成对应 nginx 配置。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/deploy-assets.js # 默认:拷贝到本地 pipi-assets
|
||||
* node scripts/deploy-assets.js --local # 同上
|
||||
* node scripts/deploy-assets.js --server # 拷贝到 /var/www/pipi-assets/
|
||||
* node scripts/deploy-assets.js --target /path/to/dir # 自定义目标目录
|
||||
*
|
||||
* 每次运行都会生成 nginx 配置(root 为当前目标目录),默认输出到 scripts/nginx-pipi-assets.conf。
|
||||
* 使用 --nginx /path/to/file 可指定 nginx 配置输出路径。
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
||||
const DATA_DIR = path.join(PACKAGE_ROOT, 'data');
|
||||
const LOCAL_TARGET = path.join(PACKAGE_ROOT, 'pipi-assets');
|
||||
const SERVER_TARGET = '/var/www/pipi-assets';
|
||||
const DEFAULT_NGINX_OUTPUT = path.join(__dirname, 'nginx-pipi-assets.conf');
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
let target = null;
|
||||
let nginxOutput = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--local' || args[i] === '--server') {
|
||||
if (target) {
|
||||
console.warn('警告: 多次指定目标,以最后一次为准');
|
||||
}
|
||||
target = args[i] === '--local' ? LOCAL_TARGET : SERVER_TARGET;
|
||||
} else if (args[i] === '--target' && args[i + 1]) {
|
||||
target = path.resolve(args[++i]);
|
||||
} else if (args[i] === '--nginx' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
||||
nginxOutput = path.resolve(args[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (target === null) {
|
||||
target = LOCAL_TARGET;
|
||||
}
|
||||
return { target, nginxOutput };
|
||||
}
|
||||
|
||||
function copyDirRecursive(src, dest) {
|
||||
if (!fs.existsSync(src)) return;
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
copyDirRecursive(srcPath, destPath);
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deployDataToTarget(targetDir) {
|
||||
if (!fs.existsSync(DATA_DIR)) {
|
||||
console.error('错误: data 目录不存在:', DATA_DIR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(DATA_DIR, { withFileTypes: true });
|
||||
if (entries.length === 0) {
|
||||
console.log('data 目录为空,跳过拷贝');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(DATA_DIR, entry.name);
|
||||
const destPath = path.join(targetDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (fs.existsSync(destPath)) {
|
||||
fs.rmSync(destPath, { recursive: true });
|
||||
}
|
||||
copyDirRecursive(srcPath, destPath);
|
||||
console.log(' 已拷贝:', entry.name + '/');
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
console.log(' 已拷贝:', entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('资源已拷贝到:', targetDir);
|
||||
}
|
||||
|
||||
function generateNginxConfig(rootPath, serverName = 'assets.yourdomain.com') {
|
||||
return `# pipi-assets 静态资源站点
|
||||
# 生成后请将 server_name 改为你的二级域名,并视需放到 /etc/nginx/sites-available/ 后启用
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${serverName};
|
||||
|
||||
root ${rootPath};
|
||||
autoindex off;
|
||||
|
||||
location /voice/ {
|
||||
alias ${rootPath}/voice/;
|
||||
add_header Cache-Control "public, max-age=86400";
|
||||
}
|
||||
location /char/ {
|
||||
alias ${rootPath}/char/;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
location /image/ {
|
||||
alias ${rootPath}/image/;
|
||||
add_header Cache-Control "public, max-age=86400";
|
||||
}
|
||||
|
||||
location /health {
|
||||
return 200 "ok";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const { target, nginxOutput } = parseArgs();
|
||||
|
||||
console.log('目标目录:', target);
|
||||
deployDataToTarget(target);
|
||||
|
||||
const nginxPath = nginxOutput ?? DEFAULT_NGINX_OUTPUT;
|
||||
const config = generateNginxConfig(target);
|
||||
fs.writeFileSync(nginxPath, config, 'utf8');
|
||||
console.log('Nginx 配置已写入:', nginxPath);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -206,32 +206,41 @@ async function main() {
|
||||
const charString = JSON.parse(fs.readFileSync(charStringPath, 'utf-8'));
|
||||
|
||||
// 获取所有汉字
|
||||
const chars = charString['3500'].split('');
|
||||
console.log(`共有 ${chars.length} 个汉字需要处理`);
|
||||
const allChars = charString['3500'].split('');
|
||||
console.log(`共有 ${allChars.length} 个汉字需要处理`);
|
||||
|
||||
// 检查是否有已存在的进度文件
|
||||
const outputPath = path.join(__dirname, '../data/char/char_introduce.json');
|
||||
let results = [];
|
||||
let startIndex = 0;
|
||||
let existingResults = [];
|
||||
const generatedSet = new Set();
|
||||
|
||||
if (fs.existsSync(outputPath)) {
|
||||
try {
|
||||
results = JSON.parse(fs.readFileSync(outputPath, 'utf-8'));
|
||||
startIndex = results.length;
|
||||
console.log(`发现已有进度,从第 ${startIndex + 1} 个汉字继续`);
|
||||
existingResults = JSON.parse(fs.readFileSync(outputPath, 'utf-8'));
|
||||
existingResults.forEach((item) => generatedSet.add(item.char));
|
||||
console.log(`发现已有 ${existingResults.length} 个汉字介绍`);
|
||||
} catch (e) {
|
||||
console.log('已有文件解析失败,从头开始');
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤出未生成的汉字
|
||||
const charsToGenerate = allChars.filter((char) => !generatedSet.has(char));
|
||||
console.log(`其中 ${generatedSet.size} 个已生成,${charsToGenerate.length} 个待生成`);
|
||||
|
||||
if (charsToGenerate.length === 0) {
|
||||
console.log('所有汉字介绍已生成完毕,无需处理');
|
||||
return;
|
||||
}
|
||||
|
||||
// 批量处理配置
|
||||
const BATCH_SIZE = 20; // 每次 API 调用处理 20 个汉字
|
||||
const DELAY_BETWEEN_BATCHES = 1000; // 批次间延迟1秒
|
||||
|
||||
for (let i = startIndex; i < chars.length; i += BATCH_SIZE) {
|
||||
const batch = chars.slice(i, Math.min(i + BATCH_SIZE, chars.length));
|
||||
for (let i = 0; i < charsToGenerate.length; i += BATCH_SIZE) {
|
||||
const batch = charsToGenerate.slice(i, Math.min(i + BATCH_SIZE, charsToGenerate.length));
|
||||
console.log(
|
||||
`\n处理第 ${i + 1} - ${Math.min(i + BATCH_SIZE, chars.length)} 个汉字(共 ${batch.length} 个)...`
|
||||
`\n处理第 ${i + 1} - ${Math.min(i + BATCH_SIZE, charsToGenerate.length)} 个汉字(共 ${batch.length} 个)...`
|
||||
);
|
||||
console.log(`汉字列表: ${batch.join('')}`);
|
||||
|
||||
@@ -243,19 +252,21 @@ async function main() {
|
||||
console.log(` ${item.char}: ${item.introduce}`);
|
||||
}
|
||||
|
||||
results.push(...batchResults);
|
||||
// 合并到已有结果
|
||||
existingResults.push(...batchResults);
|
||||
batchResults.forEach((item) => generatedSet.add(item.char));
|
||||
|
||||
// 每批处理完后保存进度
|
||||
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2), 'utf-8');
|
||||
console.log(`已保存进度:${results.length}/${chars.length}`);
|
||||
fs.writeFileSync(outputPath, JSON.stringify(existingResults, null, 2), 'utf-8');
|
||||
console.log(`已保存进度:${existingResults.length}/${allChars.length}`);
|
||||
|
||||
// 批次间延迟,避免API限流
|
||||
if (i + BATCH_SIZE < chars.length) {
|
||||
if (i + BATCH_SIZE < charsToGenerate.length) {
|
||||
await delay(DELAY_BETWEEN_BATCHES);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n完成!共生成 ${results.length} 个汉字介绍`);
|
||||
console.log(`\n完成!共生成 ${existingResults.length} 个汉字介绍`);
|
||||
console.log(`结果已保存到: ${outputPath}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# pipi-assets 本地测试(端口 8081,避免与 80 端口其他站点冲突)
|
||||
# 使用方式见同目录下的 NGINX-本地测试说明.md
|
||||
|
||||
server {
|
||||
listen 8081;
|
||||
server_name localhost;
|
||||
|
||||
root /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/pipi-assets;
|
||||
autoindex off;
|
||||
|
||||
location /voice/ {
|
||||
alias /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/pipi-assets/voice/;
|
||||
add_header Cache-Control "public, max-age=86400";
|
||||
}
|
||||
location /char/ {
|
||||
alias /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/pipi-assets/char/;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
location /image/ {
|
||||
alias /Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/pipi-assets/image/;
|
||||
add_header Cache-Control "public, max-age=86400";
|
||||
}
|
||||
|
||||
location /health {
|
||||
return 200 "ok";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user