init: 初始化项目

This commit is contained in:
0264408
2026-03-10 16:26:48 +08:00
commit 57e0ef2cf6
79 changed files with 8943 additions and 0 deletions

312
docs/ASSET_API.md Normal file
View File

@@ -0,0 +1,312 @@
# Asset API 使用示例
服务器地址: http://localhost:9050
## 📦 存储架构
### 可扩展的存储接口设计
项目采用接口化设计,支持多种存储后端:
```go
type Storage interface {
Save(filename string, content io.Reader) (string, error)
Delete(path string) error
GetURL(path string) string
Exists(path string) bool
}
```
**当前实现:**
-**LocalStorage** - 本地文件系统存储
- 🔲 **OSSStorage** - 阿里云 OSS预留
- 🔲 **S3Storage** - AWS S3预留
**切换存储方式只需:**
1. 实现 `Storage` 接口
2. 在配置中修改 `STORAGE_TYPE`
3. 无需修改业务代码
### 配置
通过环境变量配置存储:
```bash
# 本地存储(默认)
STORAGE_TYPE=local
STORAGE_BASE_PATH=./uploads
STORAGE_BASE_URL=http://localhost:9050/files
# 未来支持
# STORAGE_TYPE=oss
# STORAGE_TYPE=s3
```
## 📝 API 端点
### 1. 上传文件并创建资源
**重要:** 这是最常用的方式,上传文件并自动创建记录。
```bash
# 使用 curl
curl -X POST http://localhost:9050/api/releases/1/assets/upload \
-F "file=@path/to/app.exe" \
-F "platform=windows" \
-F "arch=amd64"
# PowerShell 示例
$file = "D:\app.exe"
$uri = "http://localhost:9050/api/releases/1/assets/upload"
$form = @{
file = Get-Item -Path $file
platform = "windows"
arch = "amd64"
}
Invoke-RestMethod -Uri $uri -Method Post -Form $form
```
**支持的文件类型:**
- Windows: `.exe`, `.msi`, `.zip`
- macOS: `.dmg`, `.pkg`, `.app`
- Linux: `.deb`, `.rpm`, `.appimage`, `.tar.gz`
**响应:**
```json
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"releaseId": 1,
"platform": "windows",
"arch": "amd64",
"url": "http://localhost:9050/files/releases/1/windows-amd64/app.exe",
"checksum": "sha256哈希值",
"createdAt": "2026-03-10T12:00:00Z"
}
}
```
### 2. 获取所有资源列表
```bash
curl http://localhost:9050/api/assets
# PowerShell
Invoke-RestMethod -Uri "http://localhost:9050/api/assets"
```
### 3. 获取指定版本的资源列表
```bash
curl http://localhost:9050/api/releases/1/assets
# PowerShell
Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets"
```
### 4. 获取单个资源详情
```bash
curl http://localhost:9050/api/assets/1
# PowerShell
Invoke-RestMethod -Uri "http://localhost:9050/api/assets/1"
```
### 5. 创建资源记录(外部 URL
如果文件托管在其他地方(如 GitHub Releases、CDN可以只创建记录
```bash
curl -X POST http://localhost:9050/api/assets \
-H "Content-Type: application/json" \
-d '{
"releaseId": 1,
"platform": "windows",
"arch": "amd64",
"url": "https://github.com/user/repo/releases/download/v1.0.0/app.exe",
"checksum": "sha256...",
"signature": "签名数据"
}'
# PowerShell
$body = @{
releaseId = 1
platform = "windows"
arch = "amd64"
url = "https://github.com/user/repo/releases/download/v1.0.0/app.exe"
checksum = "sha256..."
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:9050/api/assets" `
-Method Post -Body $body -ContentType "application/json"
```
### 6. 更新资源信息
```bash
curl -X PUT http://localhost:9050/api/assets/1 \
-H "Content-Type: application/json" \
-d '{
"platform": "windows",
"arch": "arm64",
"signature": "新签名"
}'
# PowerShell
$body = @{
platform = "windows"
arch = "arm64"
signature = "新签名"
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:9050/api/assets/1" `
-Method Put -Body $body -ContentType "application/json"
```
### 7. 删除资源
**注意:** 会同时删除文件和数据库记录。
```bash
curl -X DELETE http://localhost:9050/api/assets/1
# PowerShell
Invoke-RestMethod -Uri "http://localhost:9050/api/assets/1" -Method Delete
```
### 8. 下载资源文件
```bash
# 方式1通过API端点下载
curl http://localhost:9050/api/assets/1/download -o app.exe
# 方式2直接访问文件URL
curl http://localhost:9050/files/releases/1/windows-amd64/app.exe -o app.exe
# PowerShell - 方式1
Invoke-WebRequest -Uri "http://localhost:9050/api/assets/1/download" -OutFile "app.exe"
# PowerShell - 方式2推荐支持断点续传
Invoke-WebRequest -Uri "http://localhost:9050/files/releases/1/windows-amd64/app.exe" -OutFile "app.exe"
```
## 🔄 完整工作流程
```powershell
# 1. 创建应用
$app = Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/apps" `
-ContentType "application/json" `
-Body '{"id":"my-app","name":"我的应用","description":"测试"}'
# 2. 创建版本
$release = Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/releases" `
-ContentType "application/json" `
-Body '{"appID":"my-app","version":"1.0.0","notes":"初始版本","channel":"stable","pubDate":"2026-03-10T10:00:00Z"}'
# 3. 上传 Windows 版本
$winForm = @{
file = Get-Item -Path "D:\builds\app-windows.exe"
platform = "windows"
arch = "amd64"
}
$winAsset = Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets/upload" `
-Method Post -Form $winForm
# 4. 上传 macOS 版本
$macForm = @{
file = Get-Item -Path "D:\builds\app-macos.dmg"
platform = "darwin"
arch = "arm64"
}
$macAsset = Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets/upload" `
-Method Post -Form $macForm
# 5. 上传 Linux 版本
$linuxForm = @{
file = Get-Item -Path "D:\builds\app-linux"
platform = "linux"
arch = "amd64"
}
$linuxAsset = Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets/upload" `
-Method Post -Form $linuxForm
# 6. 查看该版本的所有资源
$assets = Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets"
$assets.data | Format-Table id, platform, arch, url
```
## 📊 API 路由列表
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/assets | 获取所有资源列表 |
| GET | /api/assets/:id | 获取单个资源详情 |
| POST | /api/assets | 创建资源记录外部URL |
| PUT | /api/assets/:id | 更新资源信息 |
| DELETE | /api/assets/:id | 删除资源(含文件) |
| GET | /api/assets/:id/download | 下载资源文件 |
| GET | /api/releases/:id/assets | 获取版本的资源列表 |
| POST | /api/releases/:id/assets/upload | 上传文件并创建资源 |
| GET | /files/* | 静态文件访问 |
## 🔧 文件存储结构
```
uploads/
└── releases/
└── {releaseID}/
├── windows-amd64/
│ └── app.exe
├── darwin-arm64/
│ └── app.dmg
└── linux-amd64/
└── app
```
## 🔐 安全特性
1. **文件校验和**:自动计算 SHA256确保文件完整性
2. **文件类型限制**:只允许特定扩展名的文件
3. **平台唯一性**:同一版本的同一平台+架构只能有一个资源
4. **级联删除**:删除资源时自动删除文件
## 🚀 扩展到云存储
未来要切换到 OSS/S3只需
1. **实现新的存储适配器**
```go
// internal/storage/oss.go
type OSSStorage struct {
client *oss.Client
bucket string
}
func (s *OSSStorage) Save(filename string, content io.Reader) (string, error) {
// OSS 上传逻辑
}
// ... 实现其他接口方法
```
2. **在 main.go 中添加分支**
```go
switch cfg.Storage.Type {
case "local":
stor, err = storage.NewLocalStorage(...)
case "oss":
stor, err = storage.NewOSSStorage(...)
case "s3":
stor, err = storage.NewS3Storage(...)
}
```
3. **业务代码无需修改**
所有 Service 和 Handler 代码都基于接口编程,自动支持新的存储方式!

405
docs/AUTH_JWT.md Normal file
View File

@@ -0,0 +1,405 @@
# JWT 认证系统文档
## 🔐 认证架构
Nebula 项目使用 **JWT (JSON Web Token)** 认证系统,提供完整的用户认证和授权功能。
### 核心特性
-**JWT 双 Token 机制**Access Token + Refresh Token
-**密码加密存储**bcrypt
-**灵活的 Token 过期时间配置**
-**中间件保护路由**
-**角色权限支持**user/admin
-**用户注册和登录**
-**修改密码**
-**Token 刷新**
## 📝 API 端点
### 1. 用户注册
```http
POST /api/auth/register
Content-Type: application/json
{
"username": "testuser",
"email": "test@example.com",
"password": "password123"
}
```
**响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"user": {
"id": "uuid",
"username": "testuser",
"email": "test@example.com",
"role": "user",
"createdAt": "2026-03-10T12:00:00Z",
"updatedAt": "2026-03-10T12:00:00Z"
},
"tokens": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200
}
}
}
```
### 2. 用户登录
```http
POST /api/auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "password123"
}
```
**响应:** 同注册响应
### 3. 刷新 Token
```http
POST /api/auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
```
**响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"access_token": "new_access_token",
"refresh_token": "new_refresh_token",
"expires_in": 7200
}
}
```
### 4. 获取当前用户信息
```http
GET /api/auth/profile
Authorization: Bearer <access_token>
```
**响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"id": "uuid",
"username": "testuser",
"email": "test@example.com",
"role": "user",
"createdAt": "2026-03-10T12:00:00Z",
"updatedAt": "2026-03-10T12:00:00Z"
}
}
```
### 5. 修改密码
```http
POST /api/auth/change-password
Authorization: Bearer <access_token>
Content-Type: application/json
{
"old_password": "password123",
"new_password": "newpassword456"
}
```
**响应:**
```json
{
"code": 0,
"message": "password changed successfully"
}
```
## 🧪 PowerShell 测试示例
### 完整测试流程
```powershell
# 1. 注册用户
$registerBody = @{
username = "testuser"
email = "test@example.com"
password = "password123"
} | ConvertTo-Json
$registerResponse = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/register" `
-Method Post -Body $registerBody -ContentType "application/json"
Write-Host "✅ 注册成功!"
Write-Host "用户 ID: $($registerResponse.data.user.id)"
Write-Host "Access Token: $($registerResponse.data.tokens.access_token.Substring(0, 20))..."
# 保存 token
$accessToken = $registerResponse.data.tokens.access_token
$refreshToken = $registerResponse.data.tokens.refresh_token
# 2. 登录(测试)
$loginBody = @{
username = "testuser"
password = "password123"
} | ConvertTo-Json
$loginResponse = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/login" `
-Method Post -Body $loginBody -ContentType "application/json"
Write-Host "✅ 登录成功!"
# 3. 获取用户信息
$headers = @{
Authorization = "Bearer $accessToken"
}
$profile = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/profile" `
-Method Get -Headers $headers
Write-Host "✅ 获取用户信息成功!"
Write-Host "用户名: $($profile.data.username)"
Write-Host "邮箱: $($profile.data.email)"
# 4. 创建应用(测试认证保护)
$appBody = @{
id = "test-app"
name = "测试应用"
description = "需要认证才能创建"
} | ConvertTo-Json
$app = Invoke-RestMethod -Uri "http://localhost:9050/api/apps" `
-Method Post -Body $appBody -ContentType "application/json" -Headers $headers
Write-Host "✅ 创建应用成功(认证有效)!"
# 5. 刷新 Token
Start-Sleep -Seconds 2
$refreshBody = @{
refresh_token = $refreshToken
} | ConvertTo-Json
$newTokens = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/refresh" `
-Method Post -Body $refreshBody -ContentType "application/json"
Write-Host "✅ Token 刷新成功!"
# 6. 修改密码
$changePasswordBody = @{
old_password = "password123"
new_password = "newpassword456"
} | ConvertTo-Json
$changeResult = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/change-password" `
-Method Post -Body $changePasswordBody -ContentType "application/json" -Headers $headers
Write-Host "✅ 密码修改成功!"
# 7. 测试未认证访问(应该失败)
try {
Invoke-RestMethod -Uri "http://localhost:9050/api/apps" -Method Get
} catch {
Write-Host "❌ 未认证访问被拒绝(正确)"
}
```
## 🔧 配置
### 环境变量
```bash
# JWT 密钥(生产环境必须修改!)
JWT_SECRET=your-secret-key-change-in-production
# Access Token 过期时间(默认 2 小时)
JWT_ACCESS_TOKEN_DURATION=7200 # 秒
# 或者
JWT_ACCESS_TOKEN_DURATION=2h
# Refresh Token 过期时间(默认 7 天)
JWT_REFRESH_TOKEN_DURATION=604800 # 秒
# 或者
JWT_REFRESH_TOKEN_DURATION=168h
```
### 推荐配置
**开发环境:**
- Access Token: 2 小时
- Refresh Token: 7 天
**生产环境:**
- Access Token: 15 分钟 - 1 小时
- Refresh Token: 30 天
- 强密钥256位以上
## 🛡️ 安全特性
### 1. 密码加密
```go
// 使用 bcrypt 加密
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
// 验证密码
bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
```
### 2. Token 验证
- ✅ 签名验证
- ✅ 过期时间检查
- ✅ 算法验证HMAC-SHA256
### 3. 中间件保护
```go
// 需要认证
authRequired.Use(auth.JWTMiddleware(jwtService))
// 需要管理员权限
adminRequired.Use(auth.JWTMiddleware(jwtService), auth.AdminMiddleware())
```
## 📊 路由保护状态
### 公开路由(无需认证)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/auth/register | 用户注册 |
| POST | /api/auth/login | 用户登录 |
| POST | /api/auth/refresh | 刷新 Token |
| GET | /api/check-update | 检查更新 |
### 需要认证的路由
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/auth/profile | 获取用户信息 |
| POST | /api/auth/change-password | 修改密码 |
| GET/POST/PUT/DELETE | /api/apps/* | 应用管理 |
| GET/POST/PUT/DELETE | /api/releases/* | 版本管理 |
| GET/POST/PUT/DELETE | /api/assets/* | 资源管理 |
## 🔄 Token 刷新流程
```
Client Server
| |
|-------- Request API --------->| (Access Token)
|<----- 401 Token Expired ------| ❌
| |
|--- Refresh Token Request ---->| (Refresh Token)
|<--- New Access Token ---------| ✅
| |
|-------- Request API --------->| (New Access Token)
|<-------- Success -------------| ✅
```
## 💡 客户端集成示例
### JavaScript/TypeScript
```typescript
class AuthService {
private accessToken: string | null = null;
private refreshToken: string | null = null;
async login(username: string, password: string) {
const response = await fetch('http://localhost:9050/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.code === 0) {
this.accessToken = data.data.tokens.access_token;
this.refreshToken = data.data.tokens.refresh_token;
// 保存到 localStorage
localStorage.setItem('access_token', this.accessToken);
localStorage.setItem('refresh_token', this.refreshToken);
}
return data;
}
async request(url: string, options: RequestInit = {}) {
options.headers = {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
};
let response = await fetch(url, options);
// Token 过期,尝试刷新
if (response.status === 401) {
await this.refresh();
options.headers['Authorization'] = `Bearer ${this.accessToken}`;
response = await fetch(url, options);
}
return response.json();
}
async refresh() {
const response = await fetch('http://localhost:9050/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: this.refreshToken })
});
const data = await response.json();
if (data.code === 0) {
this.accessToken = data.data.access_token;
this.refreshToken = data.data.refresh_token;
localStorage.setItem('access_token', this.accessToken);
localStorage.setItem('refresh_token', this.refreshToken);
}
}
}
```
## 🚀 扩展功能
### 未来可添加
1. **Token 黑名单**:退出登录时将 token 加入黑名单
2. **双因素认证2FA**:增强安全性
3. **OAuth2 第三方登录**:支持 GitHub、Google 等
4. **权限细粒度控制**:基于资源的权限管理
5. **登录日志**:记录登录历史和异常
6. **IP 白名单**:限制访问来源
7. **账号锁定**:多次登录失败后锁定
## ✅ 测试验证
1. ✅ 注册新用户
2. ✅ 登录获取 token
3. ✅ 使用 token 访问受保护的 API
4. ✅ 刷新 token
5. ✅ 修改密码
6. ✅ 未认证访问被拒绝
完整的 JWT 认证系统已经实现并运行正常!🎉

164
docs/JSON_KEY_FORMAT.md Normal file
View File

@@ -0,0 +1,164 @@
# JSON Key 格式验证
## ✅ 已统一为小驼峰camelCase格式
### 修改的文件
1. **`internal/auth/jwt.go`**
- `user_id``userId`
- `access_token``accessToken`
- `refresh_token``refreshToken`
- `expires_in``expiresIn`
2. **`internal/auth/service.go`**
- `refresh_token``refreshToken`
3. **`internal/api/handler/auth.go`**
- `old_password``oldPassword`
- `new_password``newPassword`
### 统一的 JSON Key 规范
| Go 结构体字段 | JSON Key (小驼峰) |
|--------------|------------------|
| ID | id |
| UserID | userId |
| Username | username |
| Email | email |
| Role | role |
| CreatedAt | createdAt |
| UpdatedAt | updatedAt |
| AccessToken | accessToken |
| RefreshToken | refreshToken |
| ExpiresIn | expiresIn |
| OldPassword | oldPassword |
| NewPassword | newPassword |
| AppID | appId (如果有) |
| ReleaseID | releaseId |
| Platform | platform |
| Arch | arch |
| URL | url |
| Signature | signature |
| Checksum | checksum |
### API 响应示例
**注册/登录响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"user": {
"id": "uuid",
"username": "testuser",
"email": "test@example.com",
"role": "user",
"createdAt": "2026-03-10T12:00:00Z",
"updatedAt": "2026-03-10T12:00:00Z"
},
"tokens": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 7200
}
}
}
```
**刷新 Token 请求:**
```json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
```
**刷新 Token 响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"accessToken": "new_token...",
"refreshToken": "new_refresh...",
"expiresIn": 7200
}
}
```
**修改密码请求:**
```json
{
"oldPassword": "oldpass123",
"newPassword": "newpass456"
}
```
**资源响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"releaseId": 1,
"platform": "windows",
"arch": "amd64",
"url": "http://localhost:9050/files/...",
"signature": "...",
"checksum": "sha256...",
"createdAt": "2026-03-10T12:00:00Z",
"updatedAt": "2026-03-10T12:00:00Z"
}
}
```
### 测试验证
```powershell
# 1. 注册用户(验证 camelCase
$body = @{
username = "testuser"
email = "test@example.com"
password = "password123"
} | ConvertTo-Json
$response = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/register" `
-Method Post -Body $body -ContentType "application/json"
# 检查响应格式
Write-Host "✅ Access Token 字段: accessToken"
Write-Host "✅ Refresh Token 字段: refreshToken"
Write-Host "✅ Expires In 字段: expiresIn"
Write-Host "✅ Created At 字段: createdAt"
Write-Host "✅ Updated At 字段: updatedAt"
# 2. 刷新 Token验证 camelCase
$refreshBody = @{
refreshToken = $response.data.tokens.refreshToken
} | ConvertTo-Json
$newTokens = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/refresh" `
-Method Post -Body $refreshBody -ContentType "application/json"
Write-Host "✅ 刷新 Token 成功,字段格式正确"
# 3. 修改密码(验证 camelCase
$headers = @{
Authorization = "Bearer $($response.data.tokens.accessToken)"
}
$changePasswordBody = @{
oldPassword = "password123"
newPassword = "newpassword456"
} | ConvertTo-Json
$result = Invoke-RestMethod -Uri "http://localhost:9050/api/auth/change-password" `
-Method Post -Body $changePasswordBody -ContentType "application/json" -Headers $headers
Write-Host "✅ 修改密码成功,字段格式正确"
```
## ✅ 统一完成
所有 JSON key 已统一为小驼峰格式camelCase符合前端 JavaScript/TypeScript 的命名规范。

138
docs/RELEASE_API.md Normal file
View File

@@ -0,0 +1,138 @@
# Release API 使用示例
服务器地址: http://localhost:9050
## 1. 创建应用(前置步骤)
```bash
curl -X POST http://localhost:9050/api/apps \
-H "Content-Type: application/json" \
-d '{
"id": "my-app",
"name": "我的应用",
"description": "这是一个测试应用"
}'
```
## 2. 创建版本发布
```bash
curl -X POST http://localhost:9050/api/releases \
-H "Content-Type: application/json" \
-d '{
"appID": "my-app",
"version": "1.0.0",
"notes": "第一个正式版本\n- 新增功能A\n- 修复bug B",
"channel": "stable",
"pubDate": "2026-03-10T10:00:00Z"
}'
```
## 3. 获取所有版本列表
```bash
curl http://localhost:9050/api/releases
```
## 4. 获取指定应用的版本列表
```bash
curl http://localhost:9050/api/apps/my-app/releases
```
## 5. 按渠道筛选版本
```bash
# 获取 stable 渠道的版本
curl "http://localhost:9050/api/apps/my-app/releases?channel=stable"
```
## 6. 获取最新版本
```bash
curl http://localhost:9050/api/apps/my-app/releases/latest
# 获取指定渠道的最新版本
curl "http://localhost:9050/api/apps/my-app/releases/latest?channel=stable"
```
## 7. 获取单个版本详情
```bash
curl http://localhost:9050/api/releases/1
```
## 8. 更新版本信息
```bash
curl -X PUT http://localhost:9050/api/releases/1 \
-H "Content-Type: application/json" \
-d '{
"version": "1.0.1",
"notes": "更新内容",
"channel": "stable",
"pubDate": "2026-03-10T12:00:00Z"
}'
```
## 9. 删除版本
```bash
curl -X DELETE http://localhost:9050/api/releases/1
```
## 完整测试流程
```powershell
# 1. 创建应用
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/apps" `
-ContentType "application/json" `
-Body '{"id":"test-app","name":"测试应用","description":"测试用"}'
# 2. 创建第一个版本
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/releases" `
-ContentType "application/json" `
-Body '{"appID":"test-app","version":"1.0.0","notes":"初始版本","channel":"stable","pubDate":"2026-03-10T10:00:00Z"}'
# 3. 创建第二个版本
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/releases" `
-ContentType "application/json" `
-Body '{"appID":"test-app","version":"1.1.0","notes":"功能更新","channel":"stable","pubDate":"2026-03-10T12:00:00Z"}'
# 4. 查看该应用的所有版本
Invoke-RestMethod -Uri "http://localhost:9050/api/apps/test-app/releases"
# 5. 获取最新版本
Invoke-RestMethod -Uri "http://localhost:9050/api/apps/test-app/releases/latest"
```
## API 路由列表
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/releases | 获取所有版本列表 |
| GET | /api/releases/:id | 获取单个版本详情 |
| POST | /api/releases | 创建新版本 |
| PUT | /api/releases/:id | 更新版本信息 |
| DELETE | /api/releases/:id | 删除版本 |
| GET | /api/apps/:id/releases | 获取指定应用的版本列表 |
| GET | /api/apps/:id/releases/latest | 获取应用的最新版本 |
## 响应格式
成功响应:
```json
{
"code": 0,
"msg": "ok",
"data": { ... }
}
```
错误响应:
```json
{
"code": 500,
"msg": "错误信息"
}
```

267
docs/UPDATE_CHECK.md Normal file
View File

@@ -0,0 +1,267 @@
# 更新检查 API 文档
## 🔧 修复内容
### 已修复的 Bug
1.**版本比较逻辑错误**
- 之前:使用字符串比较(`"1.10.0" < "1.9.0"` 错误)
- 现在实现语义化版本号比较semver
2.**Asset 查询错误处理缺失**
- 之前:查询失败时返回空 URL
- 现在:正确处理错误并返回明确的错误信息
3.**参数验证缺失**
- 现在验证所有必需参数app, version, platform, arch
4.**排序逻辑优化**
- 之前:按 version 字符串排序
- 现在:按 pub_date 排序,更准确
5.**响应格式统一**
- 现在使用统一的响应格式,并添加 checksum 字段
## 📝 API 使用
### 端点
```
GET /api/check-update
```
### 请求参数
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| app | string | ✅ | 应用 ID |
| version | string | ✅ | 当前版本号 |
| platform | string | ✅ | 平台windows/darwin/linux |
| arch | string | ✅ | 架构amd64/arm64/386 |
### 响应格式
**有更新:**
```json
{
"code": 0,
"msg": "ok",
"data": {
"update": true,
"version": "1.2.0",
"notes": "更新说明\n- 新功能\n- Bug修复",
"url": "http://localhost:9050/files/releases/2/windows-amd64/app.exe",
"checksum": "sha256哈希值"
}
}
```
**无需更新:**
```json
{
"code": 0,
"msg": "ok",
"data": {
"update": false
}
}
```
**错误响应:**
```json
{
"code": 500,
"msg": "no asset found for this platform and architecture"
}
```
## 🧪 测试示例
### PowerShell 测试
```powershell
# 测试更新检查(需要更新)
$params = @{
app = "test-app"
version = "1.0.0"
platform = "windows"
arch = "amd64"
}
$query = ($params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
Invoke-RestMethod -Uri "http://localhost:9050/api/check-update?$query"
# 测试更新检查(已是最新)
$params = @{
app = "test-app"
version = "2.0.0"
platform = "windows"
arch = "amd64"
}
$query = ($params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
Invoke-RestMethod -Uri "http://localhost:9050/api/check-update?$query"
# 测试不存在的平台
$params = @{
app = "test-app"
version = "1.0.0"
platform = "windows"
arch = "arm" # 假设没有这个架构的版本
}
$query = ($params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
Invoke-RestMethod -Uri "http://localhost:9050/api/check-update?$query"
```
### curl 测试
```bash
# 测试更新检查
curl "http://localhost:9050/api/check-update?app=test-app&version=1.0.0&platform=windows&arch=amd64"
# 测试参数缺失
curl "http://localhost:9050/api/check-update?app=test-app"
```
## 📦 版本号比较规则
实现了完整的语义化版本号semver比较
### 支持的版本格式
- `1.0.0` - 标准格式
- `v1.0.0` - 带 v 前缀
- `1.0.0-beta.1` - 预发布版本
- `1.0.0+build.123` - 构建元数据
### 比较规则
1. **主版本号优先**`2.0.0 > 1.9.9`
2. **次版本号次之**`1.10.0 > 1.9.0`
3. **修订号最后**`1.0.10 > 1.0.9`
4. **预发布版本**`1.0.0 > 1.0.0-rc.1 > 1.0.0-beta.1 > 1.0.0-alpha.1`
### 示例
```go
// pkg/util/version.go
CompareVersion("1.10.0", "1.9.0") // 返回 1 (1.10.0 > 1.9.0)
CompareVersion("2.0.0", "1.99.99") // 返回 1 (2.0.0 > 1.99.99)
CompareVersion("1.0.0", "1.0.0-beta") // 返回 1 (正式版 > 预发布版)
CompareVersion("v1.0.0", "1.0.0") // 返回 0 (相等)
IsNewerVersion("1.0.0", "1.1.0") // 返回 true
IsNewerVersion("1.1.0", "1.0.0") // 返回 false
```
## 🔄 完整更新流程
### 1. 准备数据
```powershell
# 创建应用
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/apps" `
-ContentType "application/json" `
-Body '{"id":"my-app","name":"我的应用","description":"测试"}'
# 创建版本 1.0.0
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/releases" `
-ContentType "application/json" `
-Body '{"appID":"my-app","version":"1.0.0","notes":"初始版本","channel":"stable","pubDate":"2026-03-10T10:00:00Z"}'
# 上传 1.0.0 的 Windows 版本
$form = @{
file = Get-Item -Path "D:\app-v1.0.0.exe"
platform = "windows"
arch = "amd64"
}
Invoke-RestMethod -Uri "http://localhost:9050/api/releases/1/assets/upload" -Method Post -Form $form
# 创建版本 1.1.0
Invoke-RestMethod -Method POST -Uri "http://localhost:9050/api/releases" `
-ContentType "application/json" `
-Body '{"appID":"my-app","version":"1.1.0","notes":"新增功能","channel":"stable","pubDate":"2026-03-11T10:00:00Z"}'
# 上传 1.1.0 的 Windows 版本
$form = @{
file = Get-Item -Path "D:\app-v1.1.0.exe"
platform = "windows"
arch = "amd64"
}
Invoke-RestMethod -Uri "http://localhost:9050/api/releases/2/assets/upload" -Method Post -Form $form
```
### 2. 客户端检查更新
```powershell
# 用户当前使用 1.0.0,检查更新
$response = Invoke-RestMethod -Uri "http://localhost:9050/api/check-update?app=my-app&version=1.0.0&platform=windows&arch=amd64"
if ($response.data.update) {
Write-Host "发现新版本: $($response.data.version)"
Write-Host "更新说明: $($response.data.notes)"
Write-Host "下载地址: $($response.data.url)"
Write-Host "校验和: $($response.data.checksum)"
# 下载更新
Invoke-WebRequest -Uri $response.data.url -OutFile "app-update.exe"
# 验证校验和(可选)
$hash = (Get-FileHash -Path "app-update.exe" -Algorithm SHA256).Hash
if ($hash.ToLower() -eq $response.data.checksum) {
Write-Host "✅ 文件校验通过"
} else {
Write-Host "❌ 文件校验失败"
}
} else {
Write-Host "✅ 当前已是最新版本"
}
```
## 🛡️ 错误处理
所有可能的错误情况:
| 错误信息 | 原因 | 解决方法 |
|---------|------|---------|
| `app is required` | 缺少 app 参数 | 提供应用 ID |
| `version is required` | 缺少 version 参数 | 提供当前版本号 |
| `platform is required` | 缺少 platform 参数 | 提供平台信息 |
| `arch is required` | 缺少 arch 参数 | 提供架构信息 |
| `no release found for this app` | 应用没有发布版本 | 先创建版本发布 |
| `no asset found for this platform and architecture` | 没有对应平台的安装包 | 上传对应平台的资源 |
| `asset URL is empty` | 资源记录存在但 URL 为空 | 检查资源数据完整性 |
## ✅ 改进总结
### 修复前的问题
```go
// ❌ 错误的版本比较
if req.Version >= latest.Version { // "1.10.0" < "1.9.0"
return &CheckResponse{Update: false}, nil
}
// ❌ 没有错误处理
db.Where(...).First(&asset) // 忽略了错误
return &CheckResponse{URL: asset.URL} // URL 可能为空
```
### 修复后
```go
// ✅ 正确的版本比较
if !util.IsNewerVersion(req.Version, latest.Version) {
return &CheckResponse{Update: false}, nil
}
// ✅ 完整的错误处理
err = db.Where(...).First(&ast).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("no asset found...")
}
return nil, err
}
```
现在更新检查功能已经完全可靠!🎉