用于两类任务:
核心原则:
https://docs-pd.mingdao.com/version 总表实时标记为唯一准则出现以下任一情形时,必须使用本 skill:
AMD64 或 ARM64如果任务不是 HAP 私有部署升级,而是一般部署、故障排查、功能使用说明,不使用本 skill。
根据用户意图,在以下两种输出之间选择:
如果用户明确要求"生成文档""给我一份操作手册""整理成升级指南",按升级指南处理。否则默认先给咨询答复;当信息齐全时,也可以直接生成文档。
生成升级文档时,产物文件名(不含扩展名)统一使用以下中文命名格式:
HAP升级指南-v{当前版本}-to-v{目标版本}-{模式}
其中 {模式} 为:
单机集群示例:
HAP升级指南-v7.1.1-to-v7.3.2-集群.mdHAP升级指南-v7.1.1-to-v7.3.2-集群.htmlHAP升级指南-v6.5.0-to-v7.3.2-单机.md禁止使用旧的纯英文命名格式(如 upgrade-guide-v7.1.1-to-v7.3.2-cluster)。
严格按以下顺序执行,不要跳步。
重要:在未收集完以下 5 项关键信息之前,不得开始获取网页或生成文档。
如果用户未提供完整信息,先补齐,再继续:
v7.0.4v7.2.0单机模式 或 集群模式AMD64 或 ARM64信息补齐方式:
ask_followup_question 工具提供选项供用户选择ask_followup_question 中提供多个问题,分别列出对应选项这 5 项不完整时,不得生成最终升级文档,也不得开始获取网页。
在后续所有步骤中,同时维护两种版本号形态:
v,用于命令、镜像标签、脚本参数、配置文件v,用于标题、正文说明规范化规则:
v7.1 时,规范化为应用版本 7.1.0,显示版本 v7.1.07.2.0 时,应用版本保持 7.2.0,显示版本补成 v7.2.0v开始抓取官网前,按需读取这些资源:
references/site-structure.mdreferences/merge-rules.mdreferences/command-library.mdassets/upgrade-guide-template-standalone.md 或 assets/upgrade-guide-template-cluster.md必须抓取:
https://docs-pd.mingdao.com/version从总表中识别:
仅对"含附加操作"的版本,再抓取其详情页:
https://docs-pd.mingdao.com/upgrade/{应用版本}/如果升级详情页中的附加操作只是一个超链接、跳转入口或"详见某文档"的简写,不能停在该页面文本。必须继续打开对应链接,提取实际执行步骤、命令、依赖资源和注意事项,再整理进最终文档。
架构校验是最高优先级。只以总表中的实时标记为准:
✅ 才表示支持❌、缺失、无法确认,都视为不支持如果目标版本不支持用户指定架构,立即停止生成文档,并直接回复:
抱歉,目标版本 {显示版本} 官方尚未发布 {架构} 镜像(以发布历史总表为准),升级文档生成任务已中止。
仅从"含附加操作"的版本详情页提取实际需要的操作,分成两类:
可能遇到的升级前操作示例:
service.yaml 新增服务配置可能遇到的升级后操作示例:
这些只是示例,不是完整列表。不要根据"示例类型"虚构步骤,也不要把未列出的线上实际操作忽略掉。详情页没有的,不生成;详情页实际出现但此处未列出的,也必须纳入处理。
如果跨越多个含附加操作的版本,必须读取并遵循 references/merge-rules.md。要把它理解为"合并原则文档",不是"允许操作类型清单"。
特别注意:
#### 1. 进入 config Pod 执行脚本、#### 2. 来自 v7.2.0:MongoDB 新增索引 等(使用阿拉伯数字,更易辨识)#### 1. 进入微服务容器执行脚本,后续每个版本一个 h4 标题(如 #### 2. 来自 v7.2.0:MySQL 新增索引)#### 1. 进入 config Pod 执行脚本,后续每个版本一个 h4 标题(如 #### 2. 来自 v7.2.0:MongoDB 新增索引){主步骤号}.{子序号} 格式,如 4.1 更新预置文件、4.2 MongoDB 新增索引--- 分隔各服务的 Deployment/Service 定义,禁止拆成多个独立代码块、禁止为每个服务单独添加子标题(如"新增 platformapi 服务:"加一个代码块,再"新增 openauthorization 服务:"加另一个代码块)default 作为默认命名空间,提示语改为"若未使用默认命名空间,请将命令中的 default 替换为实际的命名空间(namespace)"MongoDB 预置数据更新的文档结构要求:
此操作必须独立成步骤,且在"第一阶段:HAP 微服务升级前操作"中单独列出:
```bash
bash -c "$(curl -fsSL https://pdpublic.mingdao.com/private-deployment/data/preset_mongodb_docker.sh)" -s 7.2.0
```
集群模式 fileInit 规范:
在集群模式升级后操作中,每当出现"更新预置文件"步骤时,必须单独列为一个子步骤,包含存储类型判断说明和三种执行情况。
存储类型判断方式(必须在文档中提供给执行者):
通过 mingdaoyun-file 镜像版本号判断当前的文件存储模式。
> 注意:mingdaoyun-file 服务以 Docker Swarm 方式部署,需登录到 Docker Swarm 控制节点执行以下命令查看版本:
# 登录到 file 服务器后执行,查看 file 服务当前镜像版本
docker service ls | grep file
# 或查看更详细的镜像信息
docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' $(docker service ls --filter name=file -q)
s3fileInit 命令 → 选择情况 2三种情况展示格式(必须完整展示):
**{主步骤号}.1 更新预置文件**
根据您的文件存储类型,**三选一**执行(判断方式见上方说明):
**情况 1:内置文件存储(file v1,mingdaoyun-file 1.x.x)**
source /entrypoint-cluster.sh && fileInit
**情况 2:外部 MinIO / S3 标准对象存储(file v2,mingdaoyun-file 2.x.x)**
source /entrypoint-cluster.sh && s3fileInit
**情况 3:外部文件对象存储(S3 标准协议,如阿里云 OSS、AWS S3 等,file v2,mingdaoyun-file 2.x.x)**
> 此情况需手动下载预置文件包并上传到对象存储 bucket 中,请参考官方文档操作:
> [https://docs-pd.mingdao.com/faq/oss](https://docs-pd.mingdao.com/faq/oss)
禁止:
fileInit 命令而省略三种情况的说明特殊操作识别规则:
在提取和合并附加操作时,必须识别以下特殊操作,并按对应章节规范编写:
特殊升级操作的文档结构要求(v5.1.0 镜像拆分和 v5.5.0 MongoDB 升级)
以下两个特殊升级操作必须按照官网文档详细编写,禁止简化或概括:
a. 重要警告:明确说明这是"重大的结构性变更",必须严格按照官网文档步骤调整 docker-compose.yaml 配置
b. 镜像拆分说明:
mingdaoyun-community 镜像被拆分为两个独立镜像mingdaoyun-sc:1.0.0:负责存储组件相关服务mingdaoyun-command:node1018-python36:负责扩展代码块执行环境docker-compose.yaml 中新增 sc 和 command 两个服务配置c. 单机模式调整步骤(严格按官网文档执行):
/data/mingdao/script/docker-compose.yaml:app 服务中追加环境变量引用(提供完整的 YAML 配置示例)sc 和 command 服务(提供完整的 YAML 配置示例)127.0.0.1,将其改为 sc(提供修改前后对比)app 移动到 sc(提供完整 YAML 示例)app 服务中映射了存储组件端口,将端口映射从 app 移动到 sc(提供完整 YAML 示例)app 移动到 command(提供完整 YAML 示例)d. 重要注意事项(必须列出所有关键点):
127.0.0.1,必须改为 scsc 服务,代码依赖挂载需移至 command 服务app 移至 sc1.17.1.510,参考官网 Flink 升级指南a. 重要警告:明确说明这是"重大版本升级",涉及数据格式变更,必须严格按照以下步骤操作
b. MongoDB 升级说明:
mdwsrows 库下所有自建索引c. 升级前重要提示(必须在升级步骤前单独列出):
find /data/mingdao/script/volume/data/mongodb/ -name 'collection' | wc -l/data/mingdao/script/volume/data/mongodb,如自定义请相应调整d. 升级步骤(严格按照官网文档执行):
```bash
# 在管理器根目录执行(通常在 /usr/local/MDPrivateDeployment/)
bash ./service.sh stopall
```
```bash
# 检查磁盘空间
du -sh /data/mingdao/script/volume/data/mongodb
# 创建备份(备份目录可自定义)
mkdir -p /backup && tar -zcvf /backup/mongodb3.4_$(date +%Y%m%d%H%M%S).tar.gz /data/mingdao/script/volume/data/mongodb
```
```bash
docker pull registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-sc-upgrade:1.0.0
```
```bash
# 3.4 到 3.6
docker run -i --rm -v /data/mingdao/script/volume/data/mongodb:/data/mongodb registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-sc-upgrade:1.0.0 <<< 'upgradeMongodb.sh 3.4 3.6'
# 3.6 到 4.0
docker run -i --rm -v /data/mingdao/script/volume/data/mongodb:/data/mongodb registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-sc-upgrade:1.0.0 <<< 'upgradeMongodb.sh 3.6 4.0'
# 4.0 到 4.2
docker run -i --rm -v /data/mingdao/script/volume/data/mongodb:/data/mongodb registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-sc-upgrade:1.0.0 <<< 'upgradeMongodb.sh 4.0 4.2'
# 4.2 到 4.4
docker run -i --rm -v /data/mingdao/script/volume/data/mongodb:/data/mongodb registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-sc-upgrade:1.0.0 <<< 'upgradeMongodb.sh 4.2 4.4'
```
newRunVersion: 4.4 时表示升级成功newRunVersion: 目标版本exit upgrade```bash
# 如果升级过程中出现版本不匹配错误,需要重复执行上一个成功的升级命令
# 多次尝试仍失败可查看日志
cat /data/mingdao/script/volume/data/mongodb/upgrade-xxxx.log
```
e. 重建 mdwsrows 库的自建索引(必须详细展开,不能一笔带过):
mdwsrows 数据库索引:```bash
docker exec -it $(docker ps | grep mingdaoyun-sc | awk '{print $1}') bash
```
reIndex.js(提供完整的 JavaScript 脚本内容,不能省略)```javascript
var targetDbName = "mdwsrows";
// 集合白名单,白名单中的集合不重建索引
var collectionWhitelist = ["discussion", "rowrelations", "workSheetRowTopic", "workSheetTopic", "wslogs"];
// 索引白名单,白名单中的索引不重建
var indexWhitelist = ["_id_", "idx_ctime", "idx_utime", "uk_rowid", "idx_tp_status", "idx_thirdprimary"];
// 格式化时间函数
function formatDateTime() {
var now = new Date();
var utc8Time = new Date(now.getTime() + (8 60 60 * 1000));
return utc8Time.toISOString().replace('Z', '+08:00');
}
// 格式化输出函数
function printHeader(text) {
print("\n" + "=".repeat(100));
print(text);
print("=".repeat(100));
}
function printSection(text) {
print("\n" + "-".repeat(80));
print(text);
print("-".repeat(80));
}
function printTimedAction(time, action) {
print(\n[${time}] ${action});
}
// 格式化 JSON,保持一致的缩进
function formatJSON(obj, indent = 5) {
return JSON.stringify(obj, null, 2).split('\n').map((line, i) => i === 0 ? line : ' '.repeat(indent) + line).join('\n');
}
// 格式化 createIndex 命令
function formatCreateIndexCommand(collName, key, options) {
return db.${collName}.createIndex(${formatJSON(key)},\n ${formatJSON(options)});
}
function printCommand(command) {
print(" └─ Execute:");
print(" " + command);
}
function printCompletion(seconds) {
print( └─ ✓ Completed in ${seconds.toFixed(3)} seconds\n);
}
// 连接到指定数据库
var targetDb = db.getSiblingDB(targetDbName);
var startTime = formatDateTime();
printHeader("MongoDB Index Rebuild Process");
print(\n• Start Time: ${startTime});
print(• Target Database: ${targetDb.getName()});
// 获取所有要重建所有的集合(过滤集合白名单)
var collections = targetDb.getCollectionNames().filter(function(collName) {
return !collectionWhitelist.includes(collName) && !collName.startsWith('system.');
});
print(• Total Collections: ${collections.length});
collections.forEach(function(collName, index) {
var coll = targetDb.getCollection(collName);
var stats = coll.stats();
// 输出集合信息和进度
printSection(Processing Collection [${index + 1}/${collections.length}]: ${collName});
print(\n• Document Count: ${stats.count});
print(• Storage Size: ${stats.storageSize} bytes);
// 获取需要重建的索引(过滤索引白名单)
var indexes = coll.getIndexes();
var rebuildIndexes = indexes.filter(function(idx) {
return !indexWhitelist.includes(idx.name);
});
if (rebuildIndexes.length === 0) {
print("\n✓ No indexes need to be rebuilt.");
return;
}
// 输出索引重建计划
print(\n• Indexes to Rebuild (${rebuildIndexes.length}):);
rebuildIndexes.forEach(function(idx) {
print( ├─ Name: ${idx.name.padEnd(20)});
print( │ Key: ${JSON.stringify(idx.key)});
});
// 重建每个索引
rebuildIndexes.forEach(function(idx) {
var key = idx.key;
var options = {};
// 复制索引配置(排除系统属性)
for (var prop in idx) {
if (!["v", "ns", "background"].includes(prop)) {
options[prop] = idx[prop];
}
}
options.background = true; // 后台构建索引
try {
// 删除旧索引
var dropTime = formatDateTime();
printTimedAction(dropTime, Dropping Index: ${idx.name});
printCommand(db.${collName}.dropIndex("${idx.name}"));
var dropStart = new Date();
coll.dropIndex(idx.name);
var dropEnd = new Date();
printCompletion((dropEnd - dropStart) / 1000);
// 创建新索引
var createTime = formatDateTime();
printTimedAction(createTime, Creating Index: ${idx.name});
printCommand(formatCreateIndexCommand(collName, key, options));
var createStart = new Date();
coll.createIndex(key, options);
var createEnd = new Date();
printCompletion((createEnd - createStart) / 1000);
} catch (e) {
print( └─ ✗ Error: ${e.message});
print(" Skipping this index...\n");
}
});
});
var endTime = formatDateTime();
printHeader("Process Completed");
print(\n• End Time: ${endTime});
```
```bash
# MongoDB 无认证
nohup mongo mongodb://127.0.0.1:27017/admin --quiet reIndex.js >> reIndex_output.log 2>&1 &
# MongoDB 有认证(替换 root:password 为实际用户名和密码)
nohup mongo mongodb://root:password@127.0.0.1:27017/admin --quiet reIndex.js >> reIndex_output.log 2>&1 &
```
```bash
# 实时查看日志输出
tail -f reIndex_output.log
# 等待脚本执行完成(日志结尾会输出 "Process Complete" 与 "End Time")
```
reIndexWithCmd.js(提供完整的 JavaScript 脚本内容,不能省略)```javascript
// ====================================================================
// CONFIGURATION
// ====================================================================
// 在这里配置您需要重建索引的数据库及其白名单集合
// 格式: "数据库名": ["要跳过的集合1", "要跳过的集合2..."]
// 如果某个库下所有集合都需要重建索引,请使用空数组 []
var targetDatabases = {
"mdpost": [],
"MDHistory": []
};
// ====================================================================
// 格式化输出函数 (无需修改)
function printHeader(text) {
print("\n" + "=".repeat(100));
print(text);
print("=".repeat(100));
}
function printSection(text) {
print("\n" + "-".repeat(80));
print(text);
print("-".repeat(80));
}
function formatDateTime() {
var now = new Date();
var utc8Time = new Date(now.getTime() + (8 60 60 * 1000));
return utc8Time.toISOString().replace('Z', '+08:00');
}
function formatJSON(obj, indent = 5) {
if (obj === undefined || obj === null) {
return "Not available";
}
return JSON.stringify(obj, null, 2).split('\n').map((line, i) => i === 0 ? line : ' '.repeat(indent) + line).join('\n');
}
function formatFileSize(bytes) {
if (bytes === undefined || bytes === null) return "N/A";
return (bytes / 1024 / 1024).toFixed(2) + " MB";
}
function printTimedAction(time, action) {
print(\n[${time}] ${action});
}
function printCompletion(seconds) {
print( └─ ✓ Completed in ${seconds.toFixed(3)} seconds\n);
}
// ====================================================================
// SCRIPT EXECUTION
// ====================================================================
var overallStartTime = formatDateTime();
printHeader("MongoDB Multi-Database Index Rebuild Process Started");
print(\n• Overall Start Time: ${overallStartTime});
print(• Databases to Process: ${Object.keys(targetDatabases).join(', ')});
// 遍历配置中的所有数据库
for (var dbName in targetDatabases) {
if (targetDatabases.hasOwnProperty(dbName)) {
printHeader(Processing Database: [ ${dbName} ]);
// 获取目标数据库连接
var currentDb = db.getSiblingDB(dbName);
// 获取当前数据库的集合黑名单(即需要跳过的集合列表)
var excludedCollections = targetDatabases[dbName];
print(\n• Target Database: ${currentDb.getName()});
print(• Excluded Collections: ${formatJSON(excludedCollections)});
// 获取所有集合
var collections = currentDb.getCollectionNames();
// 过滤掉黑名单中的集合和系统集合
var validCollections = collections.filter(collection =>
!excludedCollections.includes(collection) && !collection.startsWith('system.')
);
print(• Total Collections to Process in this DB: ${validCollections.length});
if (validCollections.length === 0) {
print("\nNo collections to process in this database. Moving to the next one.");
continue;
}
validCollections.forEach(function(collection, index) {
try {
// 获取集合统计信息
var stats = currentDb[collection].stats();
// 输出集合信息和进度
printSection(Processing Collection [${index + 1}/${validCollections.length}]: ${collection});
print(\n• Collection Statistics:);
print( ├─ Storage Size: ${formatFileSize(stats.storageSize)});
print( └─ Document Count: ${(stats.count || 0).toLocaleString()});
// 获取当前索引信息
var indexes = currentDb[collection].getIndexes();
print(\n• Current Indexes (${indexes.length}):);
indexes.forEach(function(idx, i) {
const isLast = i === indexes.length - 1;
print(${isLast ? '└' : '├'}─ Name: ${idx.name.padEnd(20)});
print(${isLast ? ' ' : '│'} Key: ${formatJSON(idx.key)});
});
// 执行 reIndex
var execTime = formatDateTime();
printTimedAction(execTime, "Executing reIndex()");
print(" └─ Execute:");
print( db.getSiblingDB('${dbName}').getCollection('${collection}').reIndex());
var startExec = new Date();
var result = currentDb[collection].reIndex();
var endExec = new Date();
if (result.ok === 1) {
printCompletion((endExec - startExec) / 1000);
print("• Operation Results:");
print( ├─ Previous Index Count: ${result.nIndexesWas});
print( ├─ Current Index Count: ${result.nIndexes});
if (result.operationTime !== undefined) {
print( ├─ Operation Time: ${formatJSON(result.operationTime)});
} else {
print( ├─ Operation Time: Not available (non-replica set deployment));
}
if (result.$clusterTime !== undefined) {
print( └─ Cluster Time: ${formatJSON(result.$clusterTime)});
} else {
print( └─ Cluster Time: Not available (non-replica set deployment));
}
} else {
print( └─ ✗ ReIndex failed: ${formatJSON(result)});
}
} catch (e) {
print( └─ ✗ Error: ${e.message});
print(" Skipping this collection...\n");
}
});
}
}
var overallEndTime = formatDateTime();
printHeader("Process Completed");
print(\n• Overall End Time: ${overallEndTime});
```
```bash
# MongoDB 无认证
nohup mongo mongodb://127.0.0.1:27017/admin --quiet reIndexWithCmd.js >> reIndexWithCmd_output.log 2>&1 &
# MongoDB 有认证(替换 root:password 为实际用户名和密码)
nohup mongo mongodb://root:password@127.0.0.1:27017/admin --quiet reIndexWithCmd.js >> reIndexWithCmd_output.log 2>&1 &
```
```bash
# 实时查看日志输出
tail -f reIndexWithCmd_output.log
# 等待脚本执行完成(日志结尾会输出 "Process Complete" 与 "End Time")
```
f. 建议和注意事项:
mongo 命令没加入 PATH 变量,需指定绝对路径在生成升级文档时,必须严格遵守以下镜像名称映射规则,禁止根据官网显示名称推断实际镜像名称:
| 官网显示名称 | 实际镜像名称 | 说明 |
|---|---|---|
| ------------- | ------------- | ------ |
| mingdaoyun-doc-preview | mingdaoyun-doc | 文档预览服务主镜像 |
| mingdaoyun-doc-preview-extension | mingdaoyun-ldoc | 文档预览扩展服务镜像 |
重要:
其他镜像名称(如 mingdaoyun-hap、mingdaoyun-sc、mingdaoyun-command 等)以官方文档为准,按官网显示使用。
在生成升级文档的"提前准备"章节时,必须遵循以下规则:
当升级路径跨越多个版本,涉及需要提前拉取的镜像时:
"提前准备"章节必须列出:
https://pdpublic.mingdao.com/private-deployment/source/{版本号}/file_init.tar.gz禁止:
在离线环境升级指南的"提前准备"章节中,必须给出文档预览服务的实际 tar.gz 文件下载链接,不得只写文字说明或跳转到离线资源页面。
AMD64 架构实际下载地址:
| 服务 | 镜像名称 | 版本 | 实际下载链接 |
|---|---|---|---|
| ------ | --------- | ------ | ------------ |
| 文档预览服务 | mingdaoyun-doc | 2.0.0 | https://pdpublic.mingdao.com/private-deployment/offline/mingdaoyun-doc-linux-amd64-2.0.0.tar.gz |
| 文档预览扩展服务(ldoc,可选) | mingdaoyun-ldoc | 2.0.2 | https://pdpublic.mingdao.com/private-deployment/offline/mingdaoyun-ldoc-linux-amd64-2.0.2.tar.gz |
ARM64 架构下载地址格式(将 amd64 替换为 arm64):
https://pdpublic.mingdao.com/private-deployment/offline/mingdaoyun-doc-linux-arm64-2.0.0.tar.gzhttps://pdpublic.mingdao.com/private-deployment/offline/mingdaoyun-ldoc-linux-arm64-2.0.2.tar.gz禁止:
https:// 开头的 tar.gz 文件链接根据部署模式读取 Markdown 模板:
assets/upgrade-guide-template-standalone.mdassets/upgrade-guide-template-cluster.md禁止脱离模板凭记忆重写整个结构。HTML 无需读取模板,由脚本从 MD 自动生成(见 2.3 节)。
Markdown 是唯一事实来源,HTML 从它自动生成,因此 Markdown 内容必须完整、正确。
文档必须包含以下章节(按顺序):
# HAP 升级指南(单机/集群模式))单机模式还包含"异常情况排查"章节;集群模式不包含此章节。
填充 Markdown 文档时,遵守以下规则:
```markdown
| 项目 | 内容 |
|------|------|
| 升级路径 | v4.8.1 → v7.2.1 |
| 部署模式 | 单机模式(Docker Compose) |
| 服务器架构 | AMD64 |
| 服务器网络 | 可访问互联网 |
| 文档生成日期 | 2026-03-30 |
```
docker-compose.yaml 中的 HAP 镜像版本号ENV_APP_VERSION 环境变量的值与微服务镜像版本号保持一致(例如:微服务版本是 7.1.1,ENV_APP_VERSION 的值也应设为 7.1.1)```yaml
# 修改前
image: registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-hap:4.8.1
environment:
ENV_APP_VERSION: "4.8.1"
# 修改后
image: registry.cn-hangzhou.aliyuncs.com/mdpublic/mingdaoyun-hap:6.2.0
environment:
ENV_APP_VERSION: "6.2.0"
```
update.sh update hap {目标版本号} 命令会自动更新镜像版本号ENV_APP_VERSION 环境变量也会通过 update.sh 自动更新为相同的版本号第一项:授权有效期检查
授权有效期检查> ⚠️ 引用块```
> ⚠️ 重要提示:请确保您的授权密钥仍在"升级服务"有效期内。若目标主版本的发布日期晚于授权到期日,强行升级将触发系统受限提示,并导致授权自动降级为免费版。建议在升级前确认版本发布日期与授权期限的匹配情况。
```
第二项:前端二次开发注意事项
前端二次开发注意事项```
> ⚠️ 注意:如有前端二次开发,请联系前端二开负责同事确认此操作已完成,否则可能导致升级后前端功能异常。
```
MD 是唯一事实来源,HTML 完全由 AI 直接生成。不依赖任何本地 Python 脚本,也不需要安装任何依赖。
生成 Markdown 文档后,AI 直接读取 MD 文件内容,按以下规范转换为 HTML,并用 write_to_file 写出 .html 文件。
HTML 模板结构(严格遵守):
<!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>
/* 见下方 CSS 规范 */
</style>
</head>
<body>
<button class="sidebar-toggle">◀ 收起目录</button>
<div class="sidebar-overlay"></div>
<div class="layout">
<nav class="sidebar">
<button class="sidebar-close" title="关闭目录">✕</button>
<div class="sidebar-title">文档目录</div>
<ul class="toc" id="toc"></ul>
</nav>
<div class="main">
<div class="content">
{正文 HTML 内容}
</div>
</div>
</div>
<script>
/* 见下方 JS 规范 */
</script>
</body>
</html>
CSS 规范(完整内嵌,不省略):
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
font-size: 16.5px; line-height: 1.75; color: #24292f; background: #f6f8fa;
}
/* ========= 侧边栏折叠按钮 ========= */
.sidebar-toggle {
display: none;
position: fixed; top: 12px; left: 12px; z-index: 200;
background: #0969da; color: #fff; border: none; border-radius: 6px;
padding: 6px 12px; font-size: 14px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,.2);
}
.sidebar-toggle:hover { background: #0752b3; }
/* ========= 布局 ========= */
.layout { display: flex; min-height: 100vh; }
.sidebar {
width: 260px; flex-shrink: 0; background: #fff;
border-right: 1px solid #d0d7de; position: fixed; top: 0; left: 0;
height: 100vh; overflow-y: auto; padding: 24px 0;
transition: transform .25s ease; z-index: 100;
}
.sidebar.collapsed { transform: translateX(-260px); }
.sidebar-title {
font-size: 13px; font-weight: 700; text-transform: uppercase;
letter-spacing: .6px; color: #57606a; padding: 0 20px 10px;
border-bottom: 1px solid #d0d7de; margin-bottom: 12px;
}
.toc { list-style: none; padding: 0; }
.toc li a {
display: block; padding: 4px 20px; font-size: 14px; color: #24292f;
text-decoration: none; white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; border-left: 3px solid transparent;
}
.toc li a:hover { background: #f6f8fa; color: #0969da; }
.toc li a.active { border-left-color: #0969da; color: #0969da; font-weight: 600; background: #f0f6ff; }
.toc li.h3 a { padding-left: 34px; font-size: 14px; color: #57606a; }
.toc li.h4 a { padding-left: 48px; font-size: 13px; color: #57606a; }
.toc li.h5 a { padding-left: 62px; font-size: 12.5px; color: #57606a; }
/* ========= 目录折叠 ========= */
.toc-section > .toc-header {
display: flex; align-items: center; gap: 2px;
}
.toc-toggle {
background: none; border: none; font-size: 11px; cursor: pointer;
padding: 0; width: 16px; height: 16px; color: #57606a;
flex-shrink: 0; transition: transform .15s;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 3px; line-height: 1;
}
.toc-toggle:hover { background: #f6f8fa; }
.toc-section.collapsed > .toc-header .toc-toggle { transform: rotate(-90deg); }
.toc-children { list-style: none; padding: 0; }
.toc-section.collapsed > .toc-children { display: none; }
.main { flex: 1; margin-left: 260px; background: #f6f8fa; min-width: 0; transition: margin-left .25s ease; }
.main.expanded { margin-left: 0; }
.content {
max-width: 980px; margin: 0 auto; padding: 36px 48px 60px;
background: #fff; border-left: 1px solid #d0d7de;
border-right: 1px solid #d0d7de; min-height: 100vh;
}
/* ========= 遮罩(移动端点击侧边栏外关闭) ========= */
.sidebar-overlay {
display: none; position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 99;
}
.sidebar-overlay.active { display: block; }
h1 { font-size: 31px; font-weight: 700; margin: 0 0 16px; color: #1f2328; }
h2 { font-size: 24px; font-weight: 600; margin: 36px 0 14px; padding-bottom: 8px; border-bottom: 1px solid #d0d7de; color: #1f2328; }
h3 { font-size: 20px; font-weight: 600; margin: 28px 0 10px; color: #1f2328; }
h4 { font-size: 17.5px; font-weight: 600; margin: 22px 0 8px; color: #1f2328; }
h5 { font-size: 15.5px; font-weight: 600; margin: 18px 0 6px; color: #1f2328; }
p { margin-bottom: 14px; color: #24292f; }
ul, ol { margin: 8px 0 14px 24px; color: #24292f; }
li { margin-bottom: 5px; line-height: 1.7; }
li > ul, li > ol { margin: 4px 0 4px 20px; }
blockquote { border-left: 4px solid #d0d7de; margin: 16px 0; padding: 4px 16px; color: #57606a; }
blockquote p { margin-bottom: 4px; }
/* 特别注意高亮样式 */
blockquote.attention {
border-left: 4px solid #cf222e; background: #fff1f0; padding: 12px 16px; color: #1f2328; border-radius: 0 6px 6px 0;
}
blockquote.attention p { color: #1f2328; margin-bottom: 4px; }
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 15.5px; }
th, td { border: 1px solid #d0d7de; padding: 8px 12px; text-align: left; }
th { background: #f6f8fa; font-weight: 600; }
tr:nth-child(even) td { background: #f6f8fa; }
/* 版本信息表格(文档第一个表格)特殊样式 */
table.meta-block { border-radius: 8px; overflow: hidden; margin-bottom: 28px; }
table.meta-block th { color: #57606a; font-size: 14px; text-transform: uppercase; letter-spacing: .4px; width: 140px; }
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 93%; background: #eef0f3; border: 1px solid #d0d7de;
border-radius: 4px; padding: 2px 5px; color: #cf222e;
}
.code-block {
position: relative; margin: 16px 0; border-radius: 6px;
overflow: hidden; border: 1px solid #d0d7de;
}
.code-block pre {
background: #f6f8fa; color: #24292f; margin: 0;
padding: 16px; overflow-x: auto;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 15px; line-height: 1.5;
}
.code-block pre code { background: none; border: none; padding: 0; color: inherit; font-size: inherit; border-radius: 0; }
.code-block::before {
content: attr(data-lang); position: absolute; top: 8px; left: 12px;
font-size: 12px; font-family: "SFMono-Regular", Consolas, monospace;
color: #57606a; text-transform: uppercase; letter-spacing: .5px; pointer-events: none;
}
.code-block[data-lang]:not([data-lang=""]) pre { padding-top: 32px; }
.copy-btn {
position: absolute; top: 6px; right: 8px; padding: 3px 10px;
background: rgba(175,184,193,.8); color: #24292f;
border: 1px solid rgba(27,31,35,.15); border-radius: 4px; cursor: pointer;
font-size: 13px; font-weight: 500; transition: background .15s, color .15s; z-index: 2;
}
.copy-btn:hover { background: rgba(175,184,193,1); }
.copy-btn.copied { background: #2ea44f; color: #fff; border-color: transparent; }
/* ========= 表格内 URL 复制按钮 ========= */
td.url-cell {
position: relative; max-width: 480px; white-space: nowrap;
}
td.url-cell .url-text-wrap {
display: block; overflow-x: auto; white-space: nowrap;
padding-right: 52px; padding-top: 1px;
}
td.url-cell .url-text-wrap::-webkit-scrollbar { height: 3px; }
td.url-cell .url-text-wrap::-webkit-scrollbar-thumb { background: #d0d7de; border-radius: 2px; }
.url-copy-btn {
position: absolute; top: 7px; right: 8px;
padding: 2px 8px;
background: rgba(175,184,193,.8); color: #24292f;
border: 1px solid rgba(27,31,35,.15); border-radius: 4px; cursor: pointer;
font-size: 12px; font-weight: 500; transition: background .15s, color .15s; z-index: 2;
}
.url-copy-btn:hover { background: rgba(175,184,193,1); }
.url-copy-btn.copied { background: #2ea44f; color: #fff; border-color: transparent; }
/* ========= 侧边栏关闭按钮(桌面端和移动端均显示) ========= */
.sidebar-close {
display: block; position: absolute; top: 10px; right: 10px;
background: #fff; border: 1px solid #d0d7de; font-size: 20px; color: #57606a;
cursor: pointer; padding: 2px 6px; border-radius: 4px;
box-shadow: 0 1px 3px rgba(27,31,35,.1);
width: 28px; height: 28px; line-height: 1;
display: flex; align-items: center; justify-content: center;
}
.sidebar-close:hover { background: #f6f8fa; color: #cf222e; border-color: #cf222e; }
hr { border: none; border-top: 1px solid #d0d7de; margin: 28px 0; }
a { color: #0969da; text-decoration: none; }
a:hover { text-decoration: underline; }
/* ========= 响应式:平板及移动端 ========= */
@media (max-width: 768px) {
.sidebar-toggle { display: block; }
.sidebar {
transform: translateX(-260px);
box-shadow: 2px 0 16px rgba(0,0,0,.15);
}
.sidebar.open {
transform: translateX(0);
}
.main { margin-left: 0; }
.main.expanded { margin-left: 0; }
.content {
padding: 52px 16px 40px;
border-left: none; border-right: none;
}
h1 { font-size: 24px; }
h2 { font-size: 20px; }
h3 { font-size: 17.5px; }
table { font-size: 14px; display: block; overflow-x: auto; }
td.url-cell { max-width: 280px; }
.code-block pre { font-size: 14px; }
}
JS 规范(完整内嵌,不省略):
// 代码块复制按钮
document.querySelectorAll('.copy-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var pre = btn.closest('.code-block').querySelector('pre');
navigator.clipboard.writeText(pre.innerText).then(function() {
btn.textContent = '已复制';
btn.classList.add('copied');
setTimeout(function() { btn.textContent = '复制'; btn.classList.remove('copied'); }, 1500);
});
});
});
// 表格内 URL 复制按钮(自动为包含 https:// 的单元格添加,按钮固定右上角)
(function() {
document.querySelectorAll('table td').forEach(function(td) {
var text = td.textContent;
if (text.indexOf('https://') === -1 && text.indexOf('http://') === -1) return;
td.classList.add('url-cell');
// 将已有内容包裹在可滚动容器中,使按钮不被卷走
var wrap = document.createElement('span');
wrap.className = 'url-text-wrap';
while (td.firstChild) wrap.appendChild(td.firstChild);
td.appendChild(wrap);
var btn = document.createElement('button');
btn.className = 'url-copy-btn';
btn.textContent = '复制';
btn.addEventListener('click', function(e) {
e.stopPropagation();
var url = wrap.textContent.trim();
navigator.clipboard.writeText(url).then(function() {
btn.textContent = '已复制';
btn.classList.add('copied');
setTimeout(function() { btn.textContent = '复制'; btn.classList.remove('copied'); }, 1500);
});
});
td.appendChild(btn);
});
})();
// 侧边栏折叠(桌面端按钮 + 移动端遮罩 + 关闭按钮)
(function() {
var sidebar = document.querySelector('.sidebar');
var main = document.querySelector('.main');
var toggle = document.querySelector('.sidebar-toggle');
var overlay = document.querySelector('.sidebar-overlay');
var closeBtn = document.querySelector('.sidebar-close');
if (!sidebar || !toggle) return;
var isMobile = function() { return window.innerWidth <= 768; };
function closeSidebar() {
if (isMobile()) {
sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
toggle.textContent = '☰ 目录';
} else {
sidebar.classList.add('collapsed');
if (main) main.classList.add('expanded');
toggle.textContent = '▶ 展开目录';
toggle.style.display = 'block'; // 折叠后显示 toggle,方便重新打开
}
}
toggle.addEventListener('click', function() {
if (isMobile()) {
var isOpen = sidebar.classList.contains('open');
if (isOpen) {
closeSidebar();
} else {
sidebar.classList.add('open');
if (overlay) overlay.classList.add('active');
toggle.textContent = '✕ 关闭';
}
} else {
var isCollapsed = sidebar.classList.contains('collapsed');
if (isCollapsed) {
sidebar.classList.remove('collapsed');
if (main) main.classList.remove('expanded');
toggle.textContent = '◀ 收起目录';
toggle.style.display = 'none'; // 展开后隐藏 toggle,避免与正文内容重叠
} else {
closeSidebar();
}
}
});
// 侧边栏内关闭按钮
if (closeBtn) {
closeBtn.addEventListener('click', closeSidebar);
}
// 点击遮罩关闭侧边栏(移动端)
if (overlay) {
overlay.addEventListener('click', closeSidebar);
}
// 初始化:桌面端 sidebar 展开时隐藏 toggle,移动端始终显示
if (!isMobile() && !sidebar.classList.contains('collapsed')) {
toggle.style.display = 'none';
}
toggle.textContent = isMobile() ? '☰ 目录' : '◀ 收起目录';
// 窗口尺寸变化时重置状态
window.addEventListener('resize', function() {
if (!isMobile()) {
sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
// 桌面端:sidebar 展开则隐藏 toggle,折叠则显示 toggle
toggle.style.display = sidebar.classList.contains('collapsed') ? 'block' : 'none';
} else {
sidebar.classList.remove('collapsed');
if (main) main.classList.remove('expanded');
toggle.style.display = 'block';
}
toggle.textContent = isMobile() ? '☰ 目录' : '◀ 收起目录';
});
})();
// 目录生成(含折叠按钮) & 滚动高亮
(function() {
var headings = document.querySelectorAll('.content h2, .content h3, .content h4, .content h5');
var toc = document.getElementById('toc');
if (!toc || headings.length === 0) return;
headings.forEach(function(h, idx) {
if (!h.id) h.id = 'heading-' + idx;
});
var currentSection = null;
var currentChildren = null;
headings.forEach(function(h) {
var level = h.tagName.toLowerCase();
var a = document.createElement('a');
a.href = '#' + h.id;
a.textContent = h.textContent;
if (level === 'h2') {
currentSection = document.createElement('li');
currentSection.className = 'toc-section';
var header = document.createElement('div');
header.className = 'toc-header';
var toggle = document.createElement('button');
toggle.className = 'toc-toggle';
toggle.textContent = '▾';
header.appendChild(toggle);
header.appendChild(a);
currentSection.appendChild(header);
currentChildren = document.createElement('ul');
currentChildren.className = 'toc-children';
currentSection.appendChild(currentChildren);
toc.appendChild(currentSection);
} else {
if (currentChildren) {
var li = document.createElement('li');
li.className = level;
li.appendChild(a);
currentChildren.appendChild(li);
}
}
});
// 折叠按钮事件
document.querySelectorAll('.toc-toggle').forEach(function(toggle) {
toggle.addEventListener('click', function(e) {
e.stopPropagation();
toggle.closest('.toc-section').classList.toggle('collapsed');
});
});
// 滚动高亮
var allLinks = toc.querySelectorAll('a');
function onScroll() {
var scrollY = window.scrollY + 80;
var active = null;
headings.forEach(function(h) { if (h.offsetTop <= scrollY) active = h.id; });
allLinks.forEach(function(a) { a.classList.toggle('active', a.getAttribute('href') === '#' + active); });
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
// 点击目录链接后瞬间跳转到目标位置,不自动隐藏侧边栏
allLinks.forEach(function(a) {
a.addEventListener('click', function(e) {
e.preventDefault();
var target = document.getElementById(a.getAttribute('href').substring(1));
if (target) {
target.scrollIntoView({ behavior: 'auto' });
}
});
});
})();
Markdown → HTML 转换规则(AI 执行):
| Markdown 语法 | HTML 输出 | ||||||
|---|---|---|---|---|---|---|---|
| --------------- | ----------- | ||||||
# 标题 | | ||||||
## 标题 | (id 由标题文字生成,用于目录锚点) | ||||||
### / #### | (同上) | ||||||
` ` 行内代码 `` | | ||||||
`lang\n代码\n` | | ||||||
粗体 | 粗体 | ||||||
- 列表项 | | ||||||
1. 列表项 | | ||||||
> 引用 | | ||||||
> ⚠️ 特别注意:... / > ⚠️ 注意:... | (醒目红底样式) | ||||||
| 表格 | |