575 lines
15 KiB
Markdown
575 lines
15 KiB
Markdown
# 发布脚本使用说明
|
||
|
||
## 📋 概述
|
||
|
||
发布脚本 (`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)
|
||
|