feat: 汉字介绍待续
This commit is contained in:
@@ -57,7 +57,6 @@ CREATE TABLE chars (
|
|||||||
traditional VARCHAR(50) COMMENT '繁体字写法,可能有多个,用逗号分隔',
|
traditional VARCHAR(50) COMMENT '繁体字写法,可能有多个,用逗号分隔',
|
||||||
stroke_data JSONB COMMENT '笔画数据,JSON格式,存储笔画顺序和路径信息',
|
stroke_data JSONB COMMENT '笔画数据,JSON格式,存储笔画顺序和路径信息',
|
||||||
grade INT COMMENT '年级: 0-9,NULL表示未设置',
|
grade INT COMMENT '年级: 0-9,NULL表示未设置',
|
||||||
audios TEXT[] COMMENT '音频文件路径数组',
|
|
||||||
description TEXT COMMENT '描述信息',
|
description TEXT COMMENT '描述信息',
|
||||||
status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active(启用), inactive(禁用)',
|
status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active(启用), inactive(禁用)',
|
||||||
|
|
||||||
@@ -86,16 +85,53 @@ CREATE INDEX idx_char_fulltext ON chars USING GIN (to_tsvector('simple', char));
|
|||||||
- `traditional`: 繁体字写法,可能有多个,用逗号分隔,如"乾幹"
|
- `traditional`: 繁体字写法,可能有多个,用逗号分隔,如"乾幹"
|
||||||
- `stroke_data`: JSONB 类型,存储笔画数据,包含笔画顺序和路径信息(原 svg_data 字段)
|
- `stroke_data`: JSONB 类型,存储笔画数据,包含笔画顺序和路径信息(原 svg_data 字段)
|
||||||
- `grade`: 年级,0-9年级,NULL 表示未设置
|
- `grade`: 年级,0-9年级,NULL 表示未设置
|
||||||
- `audios`: 音频文件路径数组,存储音频文件路径
|
|
||||||
- `status`: 启用/禁用状态,支持软删除
|
- `status`: 启用/禁用状态,支持软删除
|
||||||
|
|
||||||
|
**说明:** 音频文件已迁移至 `char_readings` 表,按音义项分别存储。
|
||||||
|
|
||||||
**结构代码说明:**
|
**结构代码说明:**
|
||||||
- 结构代码定义见 `structure_codes.json` 文件
|
- 结构代码定义见 `structure_codes.json` 文件
|
||||||
- 常见结构代码:D0(独体结构)、D1(镶嵌结构)、B1-B4(上下结构)、H1-H3(左右结构)、R1-R6(半包围结构)等
|
- 常见结构代码:D0(独体结构)、D1(镶嵌结构)、B1-B4(上下结构)、H1-H3(左右结构)、R1-R6(半包围结构)等
|
||||||
|
|
||||||
### 3.2 汉字图片表 (char_images)
|
### 3.2 汉字音义项表 (char_readings)
|
||||||
|
|
||||||
存储汉字关联的图片信息。
|
存储每个汉字在不同读音/含义下的信息,支持多音字、多义字。单音字一条记录,多音字/同音多义字多条记录。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE char_readings (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
char_id BIGINT NOT NULL,
|
||||||
|
pinyin VARCHAR(20) NOT NULL COMMENT '读音,如 zhōng、zhòng',
|
||||||
|
meaning VARCHAR(200) COMMENT '义项说明,同音多义时用于区分,如"中间;中心"、"击中;符合"',
|
||||||
|
introduce TEXT NOT NULL COMMENT '该音义项下的提示语',
|
||||||
|
audio_file_ref VARCHAR(500) COMMENT '音频相对路径,与配置中的 base URL 拼接,如 audio/chars/中/zhōng.mp3',
|
||||||
|
image_file_ref VARCHAR(500) COMMENT '图片相对路径,可选,幼小低年级字才有释义图',
|
||||||
|
sort_order INT DEFAULT 0 COMMENT '排序,常用音义可放前面',
|
||||||
|
|
||||||
|
FOREIGN KEY (char_id) REFERENCES chars(id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_char_id (char_id),
|
||||||
|
INDEX idx_pinyin (pinyin),
|
||||||
|
INDEX idx_char_pinyin (char_id, pinyin)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明:**
|
||||||
|
- `char_id`: 关联的汉字ID
|
||||||
|
- `pinyin`: 该音义项的读音
|
||||||
|
- `meaning`: 义项说明,同音多义时用于区分
|
||||||
|
- `introduce`: 该音义项下的提示语(如"中,中间的中")
|
||||||
|
- `audio_file_ref`: 音频相对路径,采用 relative 方案,与配置中的 base URL 拼接成完整地址
|
||||||
|
- `image_file_ref`: 图片相对路径,可选,仅部分字(如幼小低年级需掌握的字)有此字段
|
||||||
|
- `sort_order`: 排序,常用音义优先展示
|
||||||
|
|
||||||
|
**路径约定:**
|
||||||
|
- 音频、图片均使用 relative 方案,数据库仅存相对路径
|
||||||
|
- base URL 在应用配置中维护,迁移 CDN/域名时无需更新数据库
|
||||||
|
- 示例:`audio_file_ref = "audio/chars/中/zhōng.mp3"`,完整地址 = `{base_url}/{audio_file_ref}`
|
||||||
|
|
||||||
|
### 3.3 汉字图片表 (char_images)
|
||||||
|
|
||||||
|
存储汉字字形相关图片(原图、标准图等,与读音无关)。
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE char_images (
|
CREATE TABLE char_images (
|
||||||
@@ -125,7 +161,7 @@ CREATE TABLE char_images (
|
|||||||
- `is_primary`: 标记主图,用于列表展示
|
- `is_primary`: 标记主图,用于列表展示
|
||||||
- `sort_order`: 排序字段,支持多图排序
|
- `sort_order`: 排序字段,支持多图排序
|
||||||
|
|
||||||
### 3.3 汉字关联表 (char_relations)
|
### 3.4 汉字关联表 (char_relations)
|
||||||
|
|
||||||
存储汉字之间的关联关系(如形近字、同音字等)。
|
存储汉字之间的关联关系(如形近字、同音字等)。
|
||||||
|
|
||||||
@@ -152,7 +188,7 @@ CREATE TABLE char_relations (
|
|||||||
- `same_radical`: 同部首,如"中"和"串"
|
- `same_radical`: 同部首,如"中"和"串"
|
||||||
- `similar_structure`: 同结构,如都是左右结构
|
- `similar_structure`: 同结构,如都是左右结构
|
||||||
|
|
||||||
### 3.4 句子表 (sentences)
|
### 3.5 句子表 (sentences)
|
||||||
|
|
||||||
存储字词关联的例句。
|
存储字词关联的例句。
|
||||||
|
|
||||||
@@ -170,7 +206,7 @@ CREATE TABLE sentences (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.5 汉字-句子关联表 (char_sentences)
|
### 3.6 汉字-句子关联表 (char_sentences)
|
||||||
|
|
||||||
存储汉字和句子的关联关系。
|
存储汉字和句子的关联关系。
|
||||||
|
|
||||||
@@ -189,7 +225,7 @@ CREATE TABLE char_sentences (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.6 用户表 (users)
|
### 3.7 用户表 (users)
|
||||||
|
|
||||||
存储管理员用户信息。
|
存储管理员用户信息。
|
||||||
|
|
||||||
@@ -215,7 +251,7 @@ CREATE TABLE users (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.7 操作日志表 (operation_logs)
|
### 3.8 操作日志表 (operation_logs)
|
||||||
|
|
||||||
记录用户操作日志,用于审计。
|
记录用户操作日志,用于审计。
|
||||||
|
|
||||||
@@ -335,11 +371,15 @@ CREATE TABLE word_sentences (
|
|||||||
|
|
||||||
```
|
```
|
||||||
chars (汉字主表)
|
chars (汉字主表)
|
||||||
├── char_images (图片表) - 一对多
|
├── char_readings (音义项表) - 一对多,支持多音字、多义字
|
||||||
|
├── char_images (字形图片表) - 一对多
|
||||||
├── char_relations (汉字关联) - 自关联(多对多)
|
├── char_relations (汉字关联) - 自关联(多对多)
|
||||||
├── char_sentences (汉字-句子关联) - 多对多
|
├── char_sentences (汉字-句子关联) - 多对多
|
||||||
└── operation_logs (操作日志) - 关联资源
|
└── operation_logs (操作日志) - 关联资源
|
||||||
|
|
||||||
|
char_readings (音义项表)
|
||||||
|
└── 存储 introduce、audio_file_ref、image_file_ref 等按读音/义项区分的数据
|
||||||
|
|
||||||
sentences (句子表)
|
sentences (句子表)
|
||||||
└── char_sentences (汉字-句子关联) - 多对多
|
└── char_sentences (汉字-句子关联) - 多对多
|
||||||
```
|
```
|
||||||
@@ -364,7 +404,7 @@ users (用户表)
|
|||||||
└── operation_logs (操作日志) - 一对多
|
└── operation_logs (操作日志) - 一对多
|
||||||
|
|
||||||
sentences (句子表)
|
sentences (句子表)
|
||||||
├── character_sentences (汉字-句子关联) - 多对多
|
├── char_sentences (汉字-句子关联) - 多对多
|
||||||
└── word_sentences (词语-句子关联) - 多对多
|
└── word_sentences (词语-句子关联) - 多对多
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -386,7 +426,7 @@ sentences (句子表)
|
|||||||
|
|
||||||
### 6.3 关联查询优化
|
### 6.3 关联查询优化
|
||||||
- 外键索引:所有外键字段建立索引
|
- 外键索引:所有外键字段建立索引
|
||||||
- 关联表索引:character_relations、character_sentences、word_characters、word_sentences 的关联字段索引
|
- 关联表索引:char_readings、char_relations、char_sentences、word_characters、word_sentences 的关联字段索引
|
||||||
|
|
||||||
## 7. 数据迁移策略
|
## 7. 数据迁移策略
|
||||||
|
|
||||||
@@ -397,15 +437,19 @@ sentences (句子表)
|
|||||||
|
|
||||||
### 7.2 数据导入
|
### 7.2 数据导入
|
||||||
- 支持批量导入汉字数据(参考 char_common_base.json 格式)
|
- 支持批量导入汉字数据(参考 char_common_base.json 格式)
|
||||||
|
- 支持批量导入音义项数据(参考 char_introduce.json,需按多音/多义拆分为多条 char_readings)
|
||||||
- 支持批量导入词语数据
|
- 支持批量导入词语数据
|
||||||
- 支持 CSV/Excel 格式导入
|
- 支持 CSV/Excel 格式导入
|
||||||
|
|
||||||
### 7.3 数据迁移注意事项
|
### 7.3 数据迁移注意事项
|
||||||
- 从旧的 words 表迁移到 chinese_characters 表时,需要:
|
- 从旧的 words 表迁移到 chars 表时,需要:
|
||||||
- 将 `content` 字段映射到 `char` 字段
|
- 将 `content` 字段映射到 `char` 字段
|
||||||
- 将 `svg_data` 字段重命名为 `stroke_data`
|
- 将 `svg_data` 字段重命名为 `stroke_data`
|
||||||
- 补充 `strokes`、`pinyin`、`radicals`、`frequency`、`structure`、`traditional` 字段
|
- 补充 `strokes`、`pinyin`、`radicals`、`frequency`、`structure`、`traditional` 字段
|
||||||
- 过滤出 `type = 'chinese_char'` 的数据
|
- 过滤出 `type = 'chinese_char'` 的数据
|
||||||
|
- 从 char_introduce.json 迁移到 char_readings 时,需要:
|
||||||
|
- 单音字:每条 JSON 对应一条 char_readings 记录
|
||||||
|
- 多音字/多义字:需人工或脚本标注拆分,每个音义项一条 char_readings 记录
|
||||||
|
|
||||||
## 8. 数据备份策略
|
## 8. 数据备份策略
|
||||||
|
|
||||||
@@ -428,11 +472,12 @@ sentences (句子表)
|
|||||||
|
|
||||||
### 9.2 数据量预估
|
### 9.2 数据量预估
|
||||||
- 汉字数据:预计 8,000+ 条(通用规范汉字表)
|
- 汉字数据:预计 8,000+ 条(通用规范汉字表)
|
||||||
|
- 音义项数据:预计 10,000~12,000 条(多音字对应多条记录)
|
||||||
- 词语数据:预计 10,000+ 条
|
- 词语数据:预计 10,000+ 条
|
||||||
- 图片数据:预计 50,000+ 条
|
- 图片数据:预计 50,000+ 条
|
||||||
- 关联关系:预计 100,000+ 条
|
- 关联关系:预计 100,000+ 条
|
||||||
|
|
||||||
### 9.3 分表策略(如需要)
|
### 9.3 分表策略(如需要)
|
||||||
- 当 chinese_characters 表数据量超过 100 万时,考虑按频率分表
|
- 当 chars 表数据量超过 100 万时,考虑按频率分表
|
||||||
- 当 chinese_words 表数据量超过 100 万时,考虑按年级分表
|
- 当 chinese_words 表数据量超过 100 万时,考虑按年级分表
|
||||||
- 使用 PostgreSQL 分区功能
|
- 使用 PostgreSQL 分区功能
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,201 @@
|
|||||||
|
# 使用 1Panel + OpenResty 部署 pipi-assets
|
||||||
|
|
||||||
|
将 voice-production 的静态资源(voice、char、后续 image)通过 1Panel 安装的 OpenResty 对外提供,并绑定二级域名。OpenResty 与 Nginx 配置兼容,可直接使用本项目生成的配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、前置条件
|
||||||
|
|
||||||
|
- 服务器已安装 [1Panel](https://1panel.cn/)
|
||||||
|
- 已通过 1Panel **应用商店** 安装 **OpenResty**
|
||||||
|
- 项目代码在服务器上(如 `/home/joey/sites/platform-pipi`),或能在本机执行部署脚本并同步到服务器
|
||||||
|
|
||||||
|
1Panel 默认安装目录为 `/opt/1panel`,下文中 OpenResty 相关路径均以此为准;若你的 1Panel 安装在其他目录,请替换为实际路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、1Panel 中 OpenResty 的目录约定
|
||||||
|
|
||||||
|
OpenResty 在 1Panel 中以容器方式运行,宿主机与容器路径对应关系如下:
|
||||||
|
|
||||||
|
| 用途 | 宿主机路径(示例) | 容器内路径 |
|
||||||
|
|--------------|--------------------|------------|
|
||||||
|
| 站点配置片段 | `.../openresty/conf/conf.d/` | `/usr/local/openresty/nginx/conf/conf.d/` |
|
||||||
|
| 网站根目录 | `.../openresty/www/` | `/www` |
|
||||||
|
|
||||||
|
完整宿主机路径(1Panel 默认):
|
||||||
|
|
||||||
|
- 配置目录:`/opt/1panel/apps/openresty/openresty/conf/conf.d/`
|
||||||
|
- 网站目录:`/opt/1panel/apps/openresty/openresty/www/`
|
||||||
|
|
||||||
|
我们将把 pipi-assets 放在网站目录下,例如:`www/pipi-assets`,容器内访问为 `/www/pipi-assets`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、部署静态资源到 OpenResty 可访问的目录
|
||||||
|
|
||||||
|
### 3.1 确定目标目录
|
||||||
|
|
||||||
|
在**宿主机**上,使用 1Panel OpenResty 的网站目录下的子目录作为发布目录(请按你的 1Panel 安装路径调整):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1Panel 默认安装目录为 /opt/1panel,若不同请修改
|
||||||
|
OPENRESTY_WWW="/opt/1panel/apps/openresty/openresty/www"
|
||||||
|
TARGET="${OPENRESTY_WWW}/pipi-assets"
|
||||||
|
sudo mkdir -p "$TARGET"
|
||||||
|
sudo chown -R $USER:$USER "$TARGET"
|
||||||
|
```
|
||||||
|
|
||||||
|
记下 `TARGET` 的取值(下文中称为「pipi-assets 宿主机路径」),后续部署脚本会用到。
|
||||||
|
|
||||||
|
### 3.2 方式 A:在服务器上执行部署脚本(推荐)
|
||||||
|
|
||||||
|
项目已在服务器上时(如 `/home/joey/sites/platform-pipi`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/joey/sites/platform-pipi/packages/voice-production
|
||||||
|
pnpm install
|
||||||
|
# 将 data/ 拷贝到 OpenResty 的 www/pipi-assets
|
||||||
|
node scripts/deploy-assets.js --target /opt/1panel/apps/openresty/openresty/www/pipi-assets
|
||||||
|
```
|
||||||
|
|
||||||
|
请将 `--target` 改为你在 3.1 中使用的实际路径。
|
||||||
|
|
||||||
|
### 3.3 方式 B:本机生成后同步到服务器
|
||||||
|
|
||||||
|
在本机执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd platform-pipi/packages/voice-production
|
||||||
|
pnpm run deploy-assets:local
|
||||||
|
rsync -avz --delete ./pipi-assets/ 用户@服务器IP:/opt/1panel/apps/openresty/openresty/www/pipi-assets/
|
||||||
|
```
|
||||||
|
|
||||||
|
若服务器上该目录需要 root,可先同步到用户目录,再 SSH 到服务器用 root 拷贝到上述路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、添加 OpenResty 站点配置
|
||||||
|
|
||||||
|
OpenResty 与 Nginx 配置兼容。有两种方式添加 pipi-assets 的 server 配置。
|
||||||
|
|
||||||
|
### 4.1 方式一:在 1Panel 中创建网站(推荐)
|
||||||
|
|
||||||
|
1. 登录 1Panel → **网站** → 点击 **创建网站**。
|
||||||
|
2. 选择 **静态网站**(或仅需“运行目录”的站点类型)。
|
||||||
|
3. 填写:
|
||||||
|
- **主域名**:你的二级域名(如 `assets.yourdomain.com`)。
|
||||||
|
- **运行目录**:容器内路径,填 **`/www/pipi-assets`**(对应宿主机 `.../www/pipi-assets`)。
|
||||||
|
- 若有「网站路径」「根目录」等,同样填 **`/www/pipi-assets`**。
|
||||||
|
4. 创建完成后,在网站列表中找到该站点,进入 **配置** / **配置文件**,确认 `server` 块中:
|
||||||
|
- `root` 或 `alias` 指向 **`/www/pipi-assets`**;
|
||||||
|
- 若有 `location /`,可保持或按需增加下面「推荐 location」中的缓存与跨域头。
|
||||||
|
|
||||||
|
若 1Panel 生成的默认配置已能正确访问 `/www/pipi-assets` 下的文件,可不必改;否则可把下面 4.2 的配置内容合并进去。
|
||||||
|
|
||||||
|
### 4.2 方式二:手动添加 conf.d 配置片段
|
||||||
|
|
||||||
|
在**宿主机**上创建新的配置文件(路径按 1Panel 实际安装目录调整):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /opt/1panel/apps/openresty/openresty/conf/conf.d/pipi-assets.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
写入以下内容(将 `server_name` 改为你的二级域名):
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# pipi-assets 静态资源(voice / char / image)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name assets.yourdomain.com;
|
||||||
|
|
||||||
|
root /www/pipi-assets;
|
||||||
|
autoindex off;
|
||||||
|
|
||||||
|
location /voice/ {
|
||||||
|
alias /www/pipi-assets/voice/;
|
||||||
|
add_header Cache-Control "public, max-age=86400";
|
||||||
|
}
|
||||||
|
location /char/ {
|
||||||
|
alias /www/pipi-assets/char/;
|
||||||
|
add_header Cache-Control "public, max-age=3600";
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
}
|
||||||
|
location /image/ {
|
||||||
|
alias /www/pipi-assets/image/;
|
||||||
|
add_header Cache-Control "public, max-age=86400";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /health {
|
||||||
|
return 200 "ok";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:这里所有路径均为**容器内路径**(`/www/pipi-assets`),因为 OpenResty 在容器中运行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、重载 OpenResty
|
||||||
|
|
||||||
|
- **1Panel 界面**:网站 → OpenResty 工具栏 → 点击 **重载**,使新配置生效且不中断服务。
|
||||||
|
- **命令行**(若你有权限进入容器或宿主机上能执行 1Panel 管理的 OpenResty):
|
||||||
|
```bash
|
||||||
|
# 仅当 1Panel 使用 systemd 管理 OpenResty 时可能可用,否则以 1Panel 重载为准
|
||||||
|
# openresty -s reload
|
||||||
|
```
|
||||||
|
建议以 1Panel 的 **重载** 为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、DNS 与 HTTPS
|
||||||
|
|
||||||
|
1. **DNS**:在域名服务商为二级域名(如 `assets.yourdomain.com`)添加 **A 记录**,指向当前服务器公网 IP。
|
||||||
|
2. **HTTPS**:
|
||||||
|
- 1Panel 网站列表中,进入该站点 → **SSL** / **证书**,选择 1Panel 提供的 **Let's Encrypt** 或上传自有证书。
|
||||||
|
- 开启 HTTPS 后,OpenResty 会监听 443 并指向同一 `root`(/www/pipi-assets),无需改文档中的路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、使用本项目生成的 Nginx 配置(可选)
|
||||||
|
|
||||||
|
本项目 `deploy-assets.js` 生成的配置默认是给「系统 Nginx」用的,`root` 为宿主机路径。给 OpenResty 用时需把 `root` 改为容器内路径:
|
||||||
|
|
||||||
|
- 生成配置时目标仍用宿主机路径(仅用于拷贝 data):
|
||||||
|
```bash
|
||||||
|
node scripts/deploy-assets.js --target /opt/1panel/apps/openresty/openresty/www/pipi-assets
|
||||||
|
```
|
||||||
|
- 打开生成的 `scripts/nginx-pipi-assets.conf`,将其中**所有** `root` / `alias` 的路径从「宿主机路径」改为 **`/www/pipi-assets`**(或 `/www/pipi-assets/voice/` 等),再复制到 `conf.d/pipi-assets.conf` 使用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、验证
|
||||||
|
|
||||||
|
- 健康检查:`http://你的二级域名/health` 或 `https://...` → 应返回 `ok`。
|
||||||
|
- 角色 JSON:`http://你的二级域名/char/char_introduce.json` → 应返回 JSON。
|
||||||
|
- 语音:`http://你的二级域名/voice/xxx.mp3` → 可播放或下载。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、后续更新资源
|
||||||
|
|
||||||
|
- **方式 A**:在服务器项目目录执行
|
||||||
|
`node scripts/deploy-assets.js --target /opt/1panel/apps/openresty/openresty/www/pipi-assets`
|
||||||
|
覆盖更新 `pipi-assets` 目录内容。
|
||||||
|
- **方式 B**:本机执行 `pnpm run deploy-assets:local` 后,用 rsync 同步到服务器上述 `pipi-assets` 路径。
|
||||||
|
|
||||||
|
更新后无需重启 OpenResty,直接刷新页面即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、路径与注意事项小结
|
||||||
|
|
||||||
|
| 项目 | 说明 |
|
||||||
|
|----------------|------|
|
||||||
|
| 1Panel 安装目录 | 常见为 `/opt/1panel`,以实际为准。 |
|
||||||
|
| 宿主机 pipi-assets | `.../openresty/openresty/www/pipi-assets`,部署脚本和 rsync 写这里。 |
|
||||||
|
| 容器内 root | 配置里统一用 **`/www/pipi-assets`**。 |
|
||||||
|
| 重载 | 在 1Panel 网站页对 OpenResty 使用 **重载**,不要直接改容器内文件。 |
|
||||||
|
|
||||||
|
若你使用非默认 1Panel 安装路径,将文中 `/opt/1panel` 全部替换为你的安装目录即可。
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# voice-production 部署到 Ubuntu 云服务器
|
||||||
|
|
||||||
|
将 `voice-production` 的静态资源(voice、char、后续图片)部署到 Ubuntu,用 Nginx 提供访问并绑定二级域名。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、服务器准备
|
||||||
|
|
||||||
|
### 1. 安装 Nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install nginx -y
|
||||||
|
sudo systemctl enable nginx
|
||||||
|
sudo systemctl start nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 安装 Node.js 与 pnpm(用于在服务器上执行部署脚本)
|
||||||
|
|
||||||
|
若你打算在服务器上跑 `deploy-assets` 和后续的生成脚本,需要 Node:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Node 20 LTS(推荐)
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
sudo npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
若你只打算从本机把已生成好的 `pipi-assets` 同步到服务器,可跳过 pnpm,但至少保留 Node 以便将来在服务器上跑脚本。
|
||||||
|
|
||||||
|
### 3. 创建资源目录(若用脚本部署会自动创建,也可先建)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /var/www/pipi-assets
|
||||||
|
sudo chown $USER:$USER /var/www/pipi-assets
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、部署方式(二选一)
|
||||||
|
|
||||||
|
### 方式 A:在服务器上拉代码并执行部署脚本(推荐)
|
||||||
|
|
||||||
|
适合:代码已在 Git 仓库,服务器能访问仓库。
|
||||||
|
|
||||||
|
**1. 在服务器上克隆/拉取项目**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入你放项目的目录,例如
|
||||||
|
cd /var/www
|
||||||
|
sudo git clone <你的仓库地址> pipi-learn
|
||||||
|
cd pipi-learn/platform-pipi/packages/voice-production
|
||||||
|
# 若已存在目录则只拉取
|
||||||
|
# git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 安装依赖并执行部署**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm run deploy-assets:server
|
||||||
|
```
|
||||||
|
|
||||||
|
会把 `data/` 下内容拷贝到 `/var/www/pipi-assets/`,并在当前目录生成 `scripts/nginx-pipi-assets.conf`。
|
||||||
|
|
||||||
|
**3. 把 Nginx 配置放到系统目录并启用**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 若需改二级域名,先编辑 scripts/nginx-pipi-assets.conf 里的 server_name
|
||||||
|
sudo cp scripts/nginx-pipi-assets.conf /etc/nginx/sites-available/pipi-assets
|
||||||
|
sudo ln -sf /etc/nginx/sites-available/pipi-assets /etc/nginx/sites-enabled
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 配置 DNS**
|
||||||
|
|
||||||
|
在域名服务商处为二级域名(如 `assets.yourdomain.com`)添加 **A 记录**,指向该云服务器的公网 IP。
|
||||||
|
|
||||||
|
**5.(可选)HTTPS**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install certbot python3-certbot-nginx -y
|
||||||
|
sudo certbot --nginx -d assets.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
按提示选择即可,证书会自动写入 Nginx 配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方式 B:本机生成后同步到服务器
|
||||||
|
|
||||||
|
适合:在本地生成 voice/char,只把静态文件上传到服务器。
|
||||||
|
|
||||||
|
**1. 本机生成并拷贝到“服务器目标结构”**
|
||||||
|
|
||||||
|
在 Mac 上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd platform-pipi/packages/voice-production
|
||||||
|
pnpm run deploy-assets:local # 生成 pipi-assets/
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 把 pipi-assets 同步到服务器**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -avz --delete ./pipi-assets/ 用户名@服务器IP:/var/www/pipi-assets/
|
||||||
|
```
|
||||||
|
|
||||||
|
若服务器上 `/var/www/pipi-assets` 需要 sudo,可先同步到用户目录再在服务器上移动:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -avz --delete ./pipi-assets/ 用户名@服务器IP:~/pipi-assets/
|
||||||
|
# 登录服务器后
|
||||||
|
ssh 用户名@服务器IP
|
||||||
|
sudo rm -rf /var/www/pipi-assets/* && sudo cp -r ~/pipi-assets/* /var/www/pipi-assets/
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 在服务器上准备 Nginx 配置**
|
||||||
|
|
||||||
|
- 要么在服务器上拉取一遍 voice-production 代码,执行一次 `pnpm run deploy-assets:server`,只为了生成 `scripts/nginx-pipi-assets.conf`,再按方式 A 第 3 步拷贝并启用;
|
||||||
|
- 要么在本机生成配置后上传。本机执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/deploy-assets.js --target /var/www/pipi-assets --nginx ./scripts/nginx-pipi-assets.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
把生成的 `scripts/nginx-pipi-assets.conf` 用 scp 传到服务器,再在服务器上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp nginx-pipi-assets.conf /etc/nginx/sites-available/pipi-assets
|
||||||
|
sudo ln -sf /etc/nginx/sites-available/pipi-assets /etc/nginx/sites-enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 修改配置里的 server_name(若尚未改)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/sites-available/pipi-assets
|
||||||
|
# 将 server_name assets.yourdomain.com; 改为你的二级域名
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. 测试并重载 Nginx**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
DNS 与 HTTPS 步骤同方式 A 的 4、5。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、修改二级域名
|
||||||
|
|
||||||
|
生成的 `nginx-pipi-assets.conf` 里默认是 `server_name assets.yourdomain.com;`。
|
||||||
|
|
||||||
|
- **方式 A**:在服务器上编辑 `scripts/nginx-pipi-assets.conf` 再拷贝到 `/etc/nginx/sites-available/`,或直接编辑 `/etc/nginx/sites-available/pipi-assets`。
|
||||||
|
- **方式 B**:本机生成配置后编辑再上传,或在服务器上编辑 `/etc/nginx/sites-available/pipi-assets`。
|
||||||
|
|
||||||
|
改完后执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、后续更新资源
|
||||||
|
|
||||||
|
- **方式 A**:在服务器上进入 `voice-production` 目录,执行 `git pull`,再执行 `pnpm run deploy-assets:server`;若在服务器上跑生成脚本,先 `pnpm run generate-voice` 等再执行 `deploy-assets:server`。
|
||||||
|
- **方式 B**:本机重新生成/拷贝后,再执行一次 `rsync` 同步到服务器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、常用命令速查
|
||||||
|
|
||||||
|
| 说明 | 命令 |
|
||||||
|
| --------------- | ---------------------------------------- |
|
||||||
|
| 测试 Nginx 配置 | `sudo nginx -t` |
|
||||||
|
| 重载 Nginx | `sudo systemctl reload nginx` |
|
||||||
|
| 查看 Nginx 状态 | `sudo systemctl status nginx` |
|
||||||
|
| 查看站点日志 | `sudo tail -f /var/log/nginx/access.log` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、验证
|
||||||
|
|
||||||
|
- 健康检查:`https://你的二级域名/health` 应返回 `ok`。
|
||||||
|
- 角色 JSON:`https://你的二级域名/char/char_introduce.json` 应返回 JSON。
|
||||||
|
- 语音:`https://你的二级域名/voice/xxx.mp3` 应能播放或下载。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Read char_common_base.json (each line is a JSON object)
|
||||||
|
const basePath = '/Users/joey-xd/sites/pipi-learn/platform-pipi/apps/api/src/data/char_common_base.json';
|
||||||
|
const baseText = fs.readFileSync(basePath, 'utf8').trim();
|
||||||
|
const baseLines = baseText.split('\n').filter(l => l.trim());
|
||||||
|
const chars = baseLines.map(l => {
|
||||||
|
let cleaned = l.trim().replace(/,$/, '');
|
||||||
|
return JSON.parse(cleaned);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`总字数: ${chars.length}`);
|
||||||
|
|
||||||
|
const singlePinyin = chars.filter(c => c.pinyin.length === 1);
|
||||||
|
const multiPinyin = chars.filter(c => c.pinyin.length > 1);
|
||||||
|
|
||||||
|
console.log(`单音字: ${singlePinyin.length}`);
|
||||||
|
console.log(`多音字: ${multiPinyin.length}`);
|
||||||
|
|
||||||
|
// Count by number of pinyins
|
||||||
|
const byCount = {};
|
||||||
|
multiPinyin.forEach(c => {
|
||||||
|
const n = c.pinyin.length;
|
||||||
|
if (!byCount[n]) byCount[n] = [];
|
||||||
|
byCount[n].push(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(byCount).sort().forEach(n => {
|
||||||
|
console.log(`\n=== ${n} 个读音 (${byCount[n].length} 字) ===`);
|
||||||
|
byCount[n].forEach(c => {
|
||||||
|
console.log(` ${c.char}: [${c.pinyin.join(', ')}]`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const inputPath =
|
||||||
|
'/Users/joey-xd/sites/pipi-learn/platform-pipi/packages/voice-production/data/char/char_introduce_v2.json';
|
||||||
|
const data = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
||||||
|
|
||||||
|
// ========== 修复词典 ==========
|
||||||
|
// 格式: char → 常用词/短语,用于生成 "char,word的char"
|
||||||
|
// 覆盖 "X字的X" 和 "X的X" 两类问题
|
||||||
|
const wordFixes = {
|
||||||
|
// --- "X字的X" 类 (294个) ---
|
||||||
|
迟: '迟到',
|
||||||
|
局: '局面',
|
||||||
|
改: '改变',
|
||||||
|
张: '张开',
|
||||||
|
忌: '禁忌',
|
||||||
|
际: '国际',
|
||||||
|
陆: '陆地',
|
||||||
|
阿: '阿谀',
|
||||||
|
陈: '陈列',
|
||||||
|
阻: '阻止',
|
||||||
|
附: '附近',
|
||||||
|
坠: '坠落',
|
||||||
|
妓: '歌妓',
|
||||||
|
妙: '美妙',
|
||||||
|
妖: '妖怪',
|
||||||
|
姊: '姊妹',
|
||||||
|
妨: '妨碍',
|
||||||
|
妒: '嫉妒',
|
||||||
|
努: '努力',
|
||||||
|
忍: '忍耐',
|
||||||
|
抽: '抽屉',
|
||||||
|
拐: '拐弯',
|
||||||
|
拖: '拖延',
|
||||||
|
者: '读者',
|
||||||
|
拍: '拍手',
|
||||||
|
顶: '头顶',
|
||||||
|
拆: '拆除',
|
||||||
|
拎: '拎包',
|
||||||
|
拥: '拥抱',
|
||||||
|
抵: '抵达',
|
||||||
|
拘: '拘留',
|
||||||
|
势: '势力',
|
||||||
|
抱: '拥抱',
|
||||||
|
拄: '拄杖',
|
||||||
|
垃: '垃圾',
|
||||||
|
拉: '拉开',
|
||||||
|
拦: '拦截',
|
||||||
|
幸: '幸福',
|
||||||
|
拌: '搅拌',
|
||||||
|
拧: '拧开',
|
||||||
|
歧: '分歧',
|
||||||
|
肯: '肯定',
|
||||||
|
齿: '牙齿',
|
||||||
|
些: '一些',
|
||||||
|
卓: '卓越',
|
||||||
|
虎: '老虎',
|
||||||
|
虏: '俘虏',
|
||||||
|
肾: '肾脏',
|
||||||
|
贤: '贤明',
|
||||||
|
尚: '高尚',
|
||||||
|
旺: '兴旺',
|
||||||
|
具: '工具',
|
||||||
|
味: '味道',
|
||||||
|
果: '水果',
|
||||||
|
昆: '昆虫',
|
||||||
|
国: '国家',
|
||||||
|
哎: '哎呀',
|
||||||
|
咕: '咕噜',
|
||||||
|
昌: '昌盛',
|
||||||
|
呵: '呵护',
|
||||||
|
疙: '疙瘩',
|
||||||
|
柑: '柑橘',
|
||||||
|
枯: '枯萎',
|
||||||
|
柄: '把柄',
|
||||||
|
栋: '栋梁',
|
||||||
|
相: '互相',
|
||||||
|
查: '检查',
|
||||||
|
柏: '柏树',
|
||||||
|
栅: '栅栏',
|
||||||
|
柳: '柳树',
|
||||||
|
柱: '柱子',
|
||||||
|
柿: '柿子',
|
||||||
|
栏: '栏杆',
|
||||||
|
柠: '柠檬',
|
||||||
|
树: '大树',
|
||||||
|
勃: '蓬勃',
|
||||||
|
要: '需要',
|
||||||
|
柬: '请柬',
|
||||||
|
咸: '咸淡',
|
||||||
|
威: '威力',
|
||||||
|
歪: '歪斜',
|
||||||
|
韭: '韭菜',
|
||||||
|
背: '背后',
|
||||||
|
战: '战争',
|
||||||
|
点: '点头',
|
||||||
|
虐: '虐待',
|
||||||
|
临: '临时',
|
||||||
|
览: '浏览',
|
||||||
|
竖: '竖立',
|
||||||
|
省: '省份',
|
||||||
|
削: '削苹果',
|
||||||
|
尝: '品尝',
|
||||||
|
昧: '愚昧',
|
||||||
|
盹: '打盹',
|
||||||
|
是: '是非',
|
||||||
|
盼: '盼望',
|
||||||
|
眨: '眨眼',
|
||||||
|
哇: '哇塞',
|
||||||
|
哄: '哄堂大笑',
|
||||||
|
哑: '哑巴',
|
||||||
|
显: '显然',
|
||||||
|
很: '很好',
|
||||||
|
猎: '打猎',
|
||||||
|
猫: '猫咪',
|
||||||
|
凰: '凤凰',
|
||||||
|
猖: '猖狂',
|
||||||
|
猛: '猛烈',
|
||||||
|
祭: '祭祀',
|
||||||
|
馅: '馅饼',
|
||||||
|
馆: '博物馆',
|
||||||
|
凑: '凑合',
|
||||||
|
减: '减少',
|
||||||
|
毫: '毫米',
|
||||||
|
烹: '烹饪',
|
||||||
|
庶: '庶民',
|
||||||
|
麻: '麻烦',
|
||||||
|
庵: '尼姑庵',
|
||||||
|
痊: '痊愈',
|
||||||
|
痒: '发痒',
|
||||||
|
痕: '痕迹',
|
||||||
|
廊: '走廊',
|
||||||
|
康: '健康',
|
||||||
|
谓: '所谓',
|
||||||
|
谚: '谚语',
|
||||||
|
谜: '谜语',
|
||||||
|
逮: '逮捕',
|
||||||
|
敢: '勇敢',
|
||||||
|
尉: '上尉',
|
||||||
|
屠: '屠宰',
|
||||||
|
弹: '子弹',
|
||||||
|
隋: '隋朝',
|
||||||
|
堕: '堕落',
|
||||||
|
随: '随便',
|
||||||
|
蛋: '鸡蛋',
|
||||||
|
隅: '角隅',
|
||||||
|
隆: '隆重',
|
||||||
|
隐: '隐藏',
|
||||||
|
婚: '结婚',
|
||||||
|
婶: '婶婶',
|
||||||
|
婉: '婉转',
|
||||||
|
颇: '颇多',
|
||||||
|
颈: '颈椎',
|
||||||
|
替: '替代',
|
||||||
|
揍: '揍人',
|
||||||
|
款: '款式',
|
||||||
|
堪: '堪称',
|
||||||
|
塔: '宝塔',
|
||||||
|
搭: '搭建',
|
||||||
|
堰: '堤堰',
|
||||||
|
揩: '揩油',
|
||||||
|
越: '超越',
|
||||||
|
趁: '趁机',
|
||||||
|
趋: '趋势',
|
||||||
|
超: '超级',
|
||||||
|
揽: '包揽',
|
||||||
|
堤: '堤坝',
|
||||||
|
提: '提高',
|
||||||
|
博: '博学',
|
||||||
|
揭: '揭开',
|
||||||
|
喜: '欢喜',
|
||||||
|
彭: '彭姓',
|
||||||
|
揣: '揣测',
|
||||||
|
装: '装扮',
|
||||||
|
蛮: '野蛮',
|
||||||
|
就: '成就',
|
||||||
|
敦: '敦厚',
|
||||||
|
斌: '文武双全',
|
||||||
|
痘: '痘痘',
|
||||||
|
痢: '痢疾',
|
||||||
|
痪: '瘫痪',
|
||||||
|
痛: '痛苦',
|
||||||
|
童: '儿童',
|
||||||
|
竣: '竣工',
|
||||||
|
阔: '辽阔',
|
||||||
|
善: '善良',
|
||||||
|
翔: '飞翔',
|
||||||
|
羡: '羡慕',
|
||||||
|
普: '普通',
|
||||||
|
粪: '粪便',
|
||||||
|
尊: '尊重',
|
||||||
|
奠: '奠基',
|
||||||
|
道: '道路',
|
||||||
|
榄: '橄榄',
|
||||||
|
想: '想象',
|
||||||
|
槐: '槐树',
|
||||||
|
榆: '榆树',
|
||||||
|
楼: '楼房',
|
||||||
|
概: '大概',
|
||||||
|
赖: '依赖',
|
||||||
|
酬: '报酬',
|
||||||
|
感: '感觉',
|
||||||
|
碍: '阻碍',
|
||||||
|
尴: '尴尬',
|
||||||
|
矮: '矮小',
|
||||||
|
辞: '辞职',
|
||||||
|
稚: '幼稚',
|
||||||
|
稠: '稠密',
|
||||||
|
颓: '颓废',
|
||||||
|
愁: '忧愁',
|
||||||
|
筹: '筹划',
|
||||||
|
简: '简单',
|
||||||
|
筷: '筷子',
|
||||||
|
毁: '毁灭',
|
||||||
|
舅: '舅舅',
|
||||||
|
鼠: '老鼠',
|
||||||
|
催: '催促',
|
||||||
|
傻: '傻瓜',
|
||||||
|
像: '好像',
|
||||||
|
躲: '躲藏',
|
||||||
|
魁: '魁梧',
|
||||||
|
衙: '衙门',
|
||||||
|
微: '微小',
|
||||||
|
馍: '馍馍',
|
||||||
|
叠: '重叠',
|
||||||
|
缠: '缠绕',
|
||||||
|
缤: '缤纷',
|
||||||
|
剿: '围剿',
|
||||||
|
静: '安静',
|
||||||
|
碧: '碧绿',
|
||||||
|
璃: '玻璃',
|
||||||
|
赘: '累赘',
|
||||||
|
熬: '熬夜',
|
||||||
|
墙: '围墙',
|
||||||
|
墟: '废墟',
|
||||||
|
嘉: '嘉奖',
|
||||||
|
摧: '摧毁',
|
||||||
|
赫: '显赫',
|
||||||
|
截: '截断',
|
||||||
|
誓: '发誓',
|
||||||
|
境: '环境',
|
||||||
|
摘: '采摘',
|
||||||
|
摔: '摔倒',
|
||||||
|
撇: '撇开',
|
||||||
|
聚: '聚集',
|
||||||
|
蜡: '蜡烛',
|
||||||
|
蝇: '苍蝇',
|
||||||
|
蜘: '蜘蛛',
|
||||||
|
蝉: '蝉鸣',
|
||||||
|
嘛: '干嘛',
|
||||||
|
嘀: '嘀咕',
|
||||||
|
赚: '赚钱',
|
||||||
|
锹: '铁锹',
|
||||||
|
锻: '锻炼',
|
||||||
|
镀: '镀金',
|
||||||
|
舞: '跳舞',
|
||||||
|
舔: '舔嘴',
|
||||||
|
稳: '稳定',
|
||||||
|
熏: '熏陶',
|
||||||
|
箕: '簸箕',
|
||||||
|
算: '计算',
|
||||||
|
箩: '箩筐',
|
||||||
|
管: '管理',
|
||||||
|
箫: '洞箫',
|
||||||
|
舆: '舆论',
|
||||||
|
薄: '薄饼',
|
||||||
|
颠: '颠倒',
|
||||||
|
翰: '翰墨',
|
||||||
|
噩: '噩梦',
|
||||||
|
橱: '橱柜',
|
||||||
|
橙: '橙子',
|
||||||
|
橘: '橘子',
|
||||||
|
整: '整齐',
|
||||||
|
融: '融化',
|
||||||
|
瓢: '瓢虫',
|
||||||
|
醒: '清醒',
|
||||||
|
霍: '霍然',
|
||||||
|
霎: '霎时',
|
||||||
|
辙: '车辙',
|
||||||
|
冀: '希冀',
|
||||||
|
餐: '餐厅',
|
||||||
|
嘴: '嘴巴',
|
||||||
|
踱: '踱步',
|
||||||
|
蹄: '马蹄',
|
||||||
|
蹂: '蹂躏',
|
||||||
|
靡: '奢靡',
|
||||||
|
癣: '皮癣',
|
||||||
|
瓣: '花瓣',
|
||||||
|
羹: '肉羹',
|
||||||
|
鳖: '甲鳖',
|
||||||
|
爆: '爆炸',
|
||||||
|
疆: '边疆',
|
||||||
|
鬓: '鬓角',
|
||||||
|
壤: '土壤',
|
||||||
|
馨: '温馨',
|
||||||
|
耀: '闪耀',
|
||||||
|
躁: '急躁',
|
||||||
|
蠕: '蠕动',
|
||||||
|
嚼: '咀嚼',
|
||||||
|
嚷: '叫嚷',
|
||||||
|
巍: '巍峨',
|
||||||
|
籍: '书籍',
|
||||||
|
鳞: '鱼鳞',
|
||||||
|
魔: '魔法',
|
||||||
|
糯: '糯米',
|
||||||
|
|
||||||
|
// --- "X的X" 类 (47个) ---
|
||||||
|
你: '你好',
|
||||||
|
系: '关系',
|
||||||
|
茎: '茎叶',
|
||||||
|
画: '画画',
|
||||||
|
鱼: '鲤鱼',
|
||||||
|
狗: '小狗',
|
||||||
|
底: '底部',
|
||||||
|
油: '石油',
|
||||||
|
泡: '泡泡',
|
||||||
|
空: '天空',
|
||||||
|
钠: '钠元素',
|
||||||
|
看: '看见',
|
||||||
|
剑: '宝剑',
|
||||||
|
总: '总是',
|
||||||
|
给: '给予',
|
||||||
|
起: '起来',
|
||||||
|
根: '树根',
|
||||||
|
党: '党员',
|
||||||
|
哦: '哦哦',
|
||||||
|
圆: '圆形',
|
||||||
|
钾: '钾元素',
|
||||||
|
铁: '铁路',
|
||||||
|
铅: '铅笔',
|
||||||
|
秤: '秤杆',
|
||||||
|
般: '一般',
|
||||||
|
爱: '爱心',
|
||||||
|
狼: '灰狼',
|
||||||
|
谁: '谁人',
|
||||||
|
球: '足球',
|
||||||
|
硅: '硅谷',
|
||||||
|
做: '做事',
|
||||||
|
您: '您好',
|
||||||
|
假: '真假',
|
||||||
|
率: '效率',
|
||||||
|
锅: '铁锅',
|
||||||
|
锌: '锌元素',
|
||||||
|
鹅: '白鹅',
|
||||||
|
然: '自然',
|
||||||
|
粥: '稀粥',
|
||||||
|
鼓: '打鼓',
|
||||||
|
搬: '搬家',
|
||||||
|
摇: '摇晃',
|
||||||
|
搞: '搞笑',
|
||||||
|
塘: '池塘',
|
||||||
|
摊: '摊贩',
|
||||||
|
聘: '聘请',
|
||||||
|
慢: '慢慢',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========== 修复逻辑 ==========
|
||||||
|
let fixCount = 0;
|
||||||
|
|
||||||
|
function fixIntroduce(ch, intro) {
|
||||||
|
// 检测 "X,X字的X" 模式
|
||||||
|
const m1 = intro.match(/^(.),(.)字的\1$/);
|
||||||
|
if (m1 && m1[1] === m1[2]) {
|
||||||
|
const okPairs = ['文字', '写字', '名字', '签字', '数字'];
|
||||||
|
if (!okPairs.includes(m1[2] + '字')) {
|
||||||
|
if (wordFixes[ch]) {
|
||||||
|
fixCount++;
|
||||||
|
return `${ch},${wordFixes[ch]}的${ch}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检测 "X,X的X" 自引用模式
|
||||||
|
const m2 = intro.match(/^(.),\1的\1$/);
|
||||||
|
if (m2) {
|
||||||
|
if (wordFixes[ch]) {
|
||||||
|
fixCount++;
|
||||||
|
return `${ch},${wordFixes[ch]}的${ch}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intro;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(data).forEach(([ch, val]) => {
|
||||||
|
if (ch.startsWith('_')) return;
|
||||||
|
|
||||||
|
if (typeof val[0] === 'string') {
|
||||||
|
// 单音字: [pinyin, meaning, introduce]
|
||||||
|
val[2] = fixIntroduce(ch, val[2]);
|
||||||
|
} else {
|
||||||
|
// 多音字: [[pinyin, meaning, introduce], ...]
|
||||||
|
val.forEach((r) => {
|
||||||
|
r[2] = fixIntroduce(ch, r[2]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 写入 ==========
|
||||||
|
fs.writeFileSync(inputPath, JSON.stringify(data, null, 2), 'utf8');
|
||||||
|
console.log(`修复完成: ${fixCount} 个 introduce 已更新`);
|
||||||
|
|
||||||
|
// ========== 验证 ==========
|
||||||
|
const dataCheck = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
||||||
|
let remaining = 0;
|
||||||
|
Object.entries(dataCheck).forEach(([ch, val]) => {
|
||||||
|
if (ch.startsWith('_')) return;
|
||||||
|
function check(intro) {
|
||||||
|
const m1 = intro.match(/^(.),(.)字的\1$/);
|
||||||
|
if (
|
||||||
|
m1 &&
|
||||||
|
m1[1] === m1[2] &&
|
||||||
|
!['文字', '写字', '名字', '签字', '数字'].includes(m1[2] + '字')
|
||||||
|
)
|
||||||
|
remaining++;
|
||||||
|
const m2 = intro.match(/^(.),\1的\1$/);
|
||||||
|
if (m2) remaining++;
|
||||||
|
}
|
||||||
|
if (typeof val[0] === 'string') check(val[2]);
|
||||||
|
else val.forEach((r) => check(r[2]));
|
||||||
|
});
|
||||||
|
console.log(`剩余问题: ${remaining}`);
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user