# 发布脚本使用说明 ## 📋 概述 发布脚本 (`scripts/publish.js`) 用于自动化应用的构建和发布流程,包括: 1. 清理构建目录 2. 构建应用(NSIS 或便携版) 3. 调用接口创建版本记录 4. 上传压缩包到 OSS ## 🚀 快速开始 ### 安装依赖 确保已安装 `form-data` 依赖: ```bash npm install form-data --save-dev ``` ### 基本使用 ```bash # 发布 NSIS 安装包(默认) npm run publish # 或直接运行脚本 node scripts/publish.js ``` ## 📝 命令选项 ### 构建类型 ```bash # 发布 NSIS 安装包 npm run publish:nsis # 或 node scripts/publish.js --type nsis # 发布便携版 npm run publish:portable # 或 node scripts/publish.js --type portable ``` ### 发布说明 ```bash node scripts/publish.js --notes "修复了若干bug,优化了性能" ``` ### 强制更新 ```bash node scripts/publish.js --force ``` ### 跳过步骤 ```bash # 仅上传已构建的文件(跳过构建) node scripts/publish.js --skip-build # 仅构建不上传 node scripts/publish.js --skip-upload ``` ### 查看帮助 ```bash node scripts/publish.js --help ``` ## 🔧 配置要求 ### 1. 配置文件 确保 `config/appConfig.js` 中包含有效的配置: ```javascript module.exports = { api_urls: { dev: "http://work.light120.com/api", prod: "http://work.light120.com/api" }, token: "your-token-here", // 必须配置有效的 token // ... }; ``` ### 2. API 接口 脚本需要以下 API 接口: #### 接口 1:创建版本记录 - **接口地址**: `POST /api/version/create` - **请求头**: ``` Content-Type: application/json Authorization: ${token} ``` - **请求参数**: ```json { "version": "1.0.0", // 必填:版本号(x.y.z 格式) "platform": "win32", // 必填:平台类型(win32/darwin/linux) "arch": "x64", // 必填:架构类型(x64/ia32/arm64) "download_url": "https://...", // 必填:下载地址(上传后更新) "file_path": "/path/to/file", // 必填:服务器文件路径 "file_size": 12345678, // 可选:文件大小(字节),不提供会自动计算 "file_hash": "sha256-hash", // 可选:SHA256 哈希值,不提供会自动计算 "release_notes": "发布说明", // 可选:更新日志 "force_update": 0, // 可选:是否强制更新(1:是 0:否),默认 0 "status": 1 // 可选:状态(1:启用 0:禁用),默认 1 } ``` - **响应示例**: ```json { "code": 0, "message": "版本创建成功", "data": { "id": 123, "version": "1.0.0", "platform": "win32", "arch": "x64", "download_url": "https://oss.example.com/path/to/file.exe", "file_path": "/path/to/file.exe", "file_size": 12345678, "file_hash": "sha256-hash-value", "release_notes": "发布说明", "force_update": 0, "status": 1, "create_time": "2024-01-01 12:00:00" } } ``` - **错误响应**: ```json { "code": 400, "message": "版本号不能为空" // 或其他错误信息 } ``` - **注意事项**: - 版本号格式必须为 `x.y.z`(如:1.0.0) - 平台类型必须为:`win32`、`darwin` 或 `linux` - 架构类型必须为:`x64`、`ia32` 或 `arm64` - 如果版本已存在(相同 version + platform + arch),会返回错误 - 如果提供了 `file_path` 但未提供 `file_size` 或 `file_hash`,接口会自动计算 #### 接口 2:上传文件到 OSS - **接口地址**: `POST /api/file/upload_version` - **请求头**: ``` Content-Type: multipart/form-data Authorization: ${token} ``` - **请求参数**(Form Data): | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `file` | File | 是 | 文件内容(二进制) | | `version` | String | 是 | 版本号(如:1.0.0) | | `platform` | String | 是 | 平台类型(win32/darwin/linux) | | `arch` | String | 是 | 架构类型(x64/ia32/arm64) | | `file_hash` | String | 是 | SHA256 哈希值 | | `file_size` | Number | 是 | 文件大小(字节) | | `version_id` | Number | 否 | 版本记录 ID(如果已创建版本记录) | | `build_type` | String | 否 | 构建类型(nsis/portable) | - **请求示例**(使用 form-data): ```javascript const FormData = require('form-data'); const fs = require('fs'); const form = new FormData(); form.append('file', fs.createReadStream('./dist/app.exe')); form.append('version', '1.0.0'); form.append('platform', 'win32'); form.append('arch', 'x64'); form.append('file_hash', 'sha256-hash-value'); form.append('file_size', 12345678); form.append('version_id', 123); // 可选 // 发送请求 form.submit('http://api.example.com/api/file/upload_version', { headers: { 'Authorization': `${token}` } }, callback); ``` - **响应示例**: ```json { "code": 0, "message": "文件上传成功", "data": { "download_url": "https://oss.example.com/versions/win32/x64/app-1.0.0.exe", "file_path": "versions/win32/x64/app-1.0.0.exe", "oss_path": "https://oss.example.com/versions/win32/x64/app-1.0.0.exe", "file_size": 12345678, "file_hash": "sha256-hash-value" } } ``` - **错误响应**: ```json { "code": 400, "message": "文件上传失败:文件大小不匹配" // 或其他错误信息 } ``` - **注意事项**: - 文件会按照 `versions/{platform}/{arch}/{filename}` 的路径结构上传到 OSS - 上传前会验证文件哈希值,确保文件完整性 - 上传成功后,建议调用 `/version/update` 接口更新版本记录的下载地址 - 大文件上传建议设置较长的超时时间(建议 10 分钟以上) #### 接口 3:更新版本下载地址(可选) - **接口地址**: `POST /api/version/update` - **请求头**: ``` Content-Type: application/json Authorization: ${token} ``` - **请求参数**: ```json { "id": 123, // 必填:版本记录 ID "download_url": "https://...", // 可选:下载地址 "file_path": "/path/to/file", // 可选:文件路径 "file_hash": "sha256-hash", // 可选:文件哈希值 "file_size": 12345678, // 可选:文件大小 "release_notes": "更新说明", // 可选:更新日志 "force_update": 1, // 可选:是否强制更新 "status": 1 // 可选:状态 } ``` - **响应示例**: ```json { "code": 0, "message": "版本更新成功", "data": { "id": 123, "version": "1.0.0", "download_url": "https://oss.example.com/path/to/file.exe", // ... 其他字段 } } ``` - **使用场景**: - 文件上传成功后,更新版本记录的下载地址 - 修改版本的发布说明或强制更新标志 - 启用或禁用某个版本 ## 📦 发布流程 1. **清理构建目录** - 删除 `dist` 目录及其所有内容 2. **构建应用** - 根据构建类型执行 `electron-builder` - NSIS: `electron-builder --win nsis` - Portable: `electron-builder --win portable` 3. **查找构建产物** - 在 `dist` 目录中查找 `.exe` 文件 - 按文件大小排序,优先处理主安装包 4. **创建版本记录** - 调用 `POST /api/version/create` 接口 - 传递版本信息、平台、架构等 - 获取版本记录 ID(用于后续更新) 5. **上传文件到 OSS** - 计算文件 SHA256 哈希值 - 使用 `multipart/form-data` 格式调用 `POST /api/file/upload_version` 接口 - 传递文件、版本信息、哈希值等参数 - 获取上传后的下载地址 6. **更新版本记录** - 使用获取到的下载地址调用 `POST /api/version/update` 接口 - 更新版本记录的 `download_url` 和 `file_path` 字段 ## ⚠️ 注意事项 1. **Token 配置** - 确保 `config/appConfig.js` 中有有效的 `token` - Token 用于 API 认证,格式为 `${token}` - 所有接口请求都需要在请求头中包含 `Authorization: ${token}` 2. **接口路径** - 所有接口路径前缀为 `/api` - 完整路径示例: - 创建版本:`POST http://api.example.com/api/version/create` - 上传文件:`POST http://api.example.com/api/file/upload_version` - 更新版本:`POST http://api.example.com/api/version/update` 3. **文件大小** - 大文件上传可能需要较长时间 - 脚本默认超时时间为 10 分钟(600000ms) - 建议大文件(>100MB)增加超时时间到 30 分钟 4. **网络连接** - 确保能够访问 API 服务器和 OSS - 上传大文件时建议使用稳定的网络连接 - 建议在网络稳定的环境下执行发布流程 5. **版本号** - 版本号从 `package.json` 的 `version` 字段读取 - 发布前确保版本号已更新 - 版本号格式必须符合 `x.y.z` 格式(如:1.0.0) 6. **构建产物** - 脚本会自动查找 `dist` 目录中的 `.exe` 文件 - 排除 `builder` 和 `helper` 相关的辅助文件 - 确保构建产物文件名清晰,便于识别 7. **文件哈希验证** - 上传前必须计算文件的 SHA256 哈希值 - 上传接口会验证文件哈希,确保文件完整性 - 哈希值不匹配会导致上传失败 8. **接口调用顺序** - 建议先创建版本记录(获取 version_id) - 然后上传文件(可传递 version_id) - 最后更新版本记录的下载地址 - 也可以先上传文件,再创建版本记录并更新下载地址 ## 🔍 故障排查 ### 问题:上传失败 **可能原因**: - Token 无效或过期 - API 接口地址不正确 - 网络连接问题 - 文件过大,超时 **解决方法**: 1. 检查 `config/appConfig.js` 中的 `token` 是否有效 2. 检查 API 地址是否正确 3. 检查网络连接 4. 如果文件很大,可以增加超时时间 ### 问题:构建失败 **可能原因**: - 缺少依赖 - electron-builder 配置错误 - 磁盘空间不足 **解决方法**: 1. 运行 `npm install` 安装所有依赖 2. 检查 `package.json` 中的 `build` 配置 3. 确保有足够的磁盘空间 ### 问题:找不到构建产物 **可能原因**: - 构建未成功完成 - 构建产物在其他位置 **解决方法**: 1. 检查构建日志,确认构建成功 2. 手动检查 `dist` 目录 3. 使用 `--skip-build` 选项,手动指定文件路径(需要修改脚本) ## 📝 示例 ### 完整发布流程 ```bash # 1. 更新版本号(在 package.json 中) # "version": "1.0.1" # 2. 发布 npm run publish -- --notes "新功能:支持自动投递简历" --force ``` ### 仅上传已构建的文件 ```bash # 1. 手动构建 npm run build:nsis # 2. 仅上传 node scripts/publish.js --skip-build ``` ### 测试发布(不上传) ```bash # 构建并创建版本记录,但不上传文件 node scripts/publish.js --skip-upload ``` ## 💻 接口调用示例 ### Node.js 示例代码 #### 1. 创建版本记录 ```javascript const axios = require('axios'); const crypto = require('crypto'); const fs = require('fs'); async function createVersion() { const filePath = './dist/app-1.0.0.exe'; const stats = fs.statSync(filePath); const fileSize = stats.size; // 计算文件哈希 const fileBuffer = fs.readFileSync(filePath); const fileHash = crypto.createHash('sha256').update(fileBuffer).digest('hex'); const response = await axios.post('http://api.example.com/api/version/create', { version: '1.0.0', platform: 'win32', arch: 'x64', download_url: '', // 上传后更新 file_path: filePath, file_size: fileSize, file_hash: fileHash, release_notes: '修复了若干bug,优化了性能', force_update: 0, status: 1 }, { headers: { 'Authorization': `${token}`, 'Content-Type': 'application/json' } }); return response.data.data; // 返回版本记录,包含 id } ``` #### 2. 上传文件到 OSS ```javascript const FormData = require('form-data'); const axios = require('axios'); const fs = require('fs'); const crypto = require('crypto'); async function uploadVersionFile(versionId) { const filePath = './dist/app-1.0.0.exe'; const stats = fs.statSync(filePath); const fileSize = stats.size; // 计算文件哈希 const fileBuffer = fs.readFileSync(filePath); const fileHash = crypto.createHash('sha256').update(fileBuffer).digest('hex'); // 创建 FormData const form = new FormData(); form.append('file', fs.createReadStream(filePath)); form.append('version', '1.0.0'); form.append('platform', 'win32'); form.append('arch', 'x64'); form.append('file_hash', fileHash); form.append('file_size', fileSize); if (versionId) { form.append('version_id', versionId); } const response = await axios.post('http://api.example.com/api/file/upload_version', form, { headers: { 'Authorization': `${token}`, ...form.getHeaders() }, maxContentLength: Infinity, maxBodyLength: Infinity, timeout: 600000 // 10 分钟超时 }); return response.data.data; // 返回上传结果,包含 download_url } ``` #### 3. 更新版本下载地址 ```javascript async function updateVersionDownloadUrl(versionId, downloadUrl, fileHash) { const response = await axios.post('http://api.example.com/api/version/update', { id: versionId, download_url: downloadUrl, file_hash: fileHash }, { headers: { 'Authorization': `${token}`, 'Content-Type': 'application/json' } }); return response.data.data; } ``` #### 4. 完整发布流程示例 ```javascript async function publishVersion() { try { // 1. 创建版本记录 console.log('创建版本记录...'); const version = await createVersion(); console.log('版本记录创建成功,ID:', version.id); // 2. 上传文件 console.log('上传文件到 OSS...'); const uploadResult = await uploadVersionFile(version.id); console.log('文件上传成功,下载地址:', uploadResult.download_url); // 3. 更新版本下载地址 console.log('更新版本下载地址...'); await updateVersionDownloadUrl(version.id, uploadResult.download_url, uploadResult.file_hash); console.log('版本发布完成!'); } catch (error) { console.error('发布失败:', error.response?.data || error.message); throw error; } } ``` ### cURL 示例 #### 创建版本记录 ```bash curl -X POST http://api.example.com/api/version/create \ -H "Authorization: your-token-here" \ -H "Content-Type: application/json" \ -d '{ "version": "1.0.0", "platform": "win32", "arch": "x64", "download_url": "", "file_path": "/path/to/file.exe", "file_size": 12345678, "file_hash": "sha256-hash-value", "release_notes": "发布说明", "force_update": 0, "status": 1 }' ``` #### 上传文件 ```bash curl -X POST http://api.example.com/api/file/upload_version \ -H "Authorization: your-token-here" \ -F "file=@./dist/app-1.0.0.exe" \ -F "version=1.0.0" \ -F "platform=win32" \ -F "arch=x64" \ -F "file_hash=sha256-hash-value" \ -F "file_size=12345678" \ -F "version_id=123" ``` ## 🔗 相关文档 - [API 配置说明](./API_CONFIG.md) - [打包说明](./BUILD.md) - [更新逻辑检查报告](./更新逻辑检查报告.md)