init: 初始化项目
This commit is contained in:
312
docs/ASSET_API.md
Normal file
312
docs/ASSET_API.md
Normal 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
405
docs/AUTH_JWT.md
Normal 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
164
docs/JSON_KEY_FORMAT.md
Normal 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
138
docs/RELEASE_API.md
Normal 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
267
docs/UPDATE_CHECK.md
Normal 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
|
||||
}
|
||||
```
|
||||
|
||||
现在更新检查功能已经完全可靠!🎉
|
||||
Reference in New Issue
Block a user