85a0b44ddd
- 后端 /api/releases/latest 代理 gitea Releases(10min 缓存,无发布/不可达时回退 SUNDYNIX_FALLBACK_VERSION) - 前端 use-release hook 就绪(下载区动态接线暂缓) - 仓库链接统一指向 git.sundynix.cn/sundynix/sundynix-agentix,下载按钮 # 占位改为 releases 页 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
// Package service 业务服务层。
|
|
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Release 对外暴露的最新版本信息。
|
|
type Release struct {
|
|
Version string `json:"version"`
|
|
PageURL string `json:"page_url"`
|
|
MacOS string `json:"macos_url,omitempty"`
|
|
Windows string `json:"windows_url,omitempty"`
|
|
Source string `json:"source"` // gitea | fallback
|
|
}
|
|
|
|
// ReleaseService 代理 gitea Releases API,带缓存与静态回退。
|
|
type ReleaseService struct {
|
|
giteaBase string
|
|
repo string
|
|
fallback Release
|
|
|
|
mu sync.Mutex
|
|
cached *Release
|
|
cachedAt time.Time
|
|
ttl time.Duration
|
|
client *http.Client
|
|
}
|
|
|
|
func NewReleaseService() *ReleaseService {
|
|
base := envOr("SUNDYNIX_GITEA_BASE", "https://git.sundynix.cn")
|
|
repo := envOr("SUNDYNIX_RELEASE_REPO", "sundynix/sundynix-agentix")
|
|
pageURL := fmt.Sprintf("%s/%s/releases", base, repo)
|
|
return &ReleaseService{
|
|
giteaBase: base,
|
|
repo: repo,
|
|
fallback: Release{
|
|
Version: envOr("SUNDYNIX_FALLBACK_VERSION", "v0.1.2"),
|
|
PageURL: pageURL,
|
|
Source: "fallback",
|
|
},
|
|
ttl: 10 * time.Minute,
|
|
client: &http.Client{Timeout: 8 * time.Second},
|
|
}
|
|
}
|
|
|
|
// Latest 返回最新 release;gitea 不可达或无发布时回退静态配置。
|
|
func (s *ReleaseService) Latest() Release {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.cached != nil && time.Since(s.cachedAt) < s.ttl {
|
|
return *s.cached
|
|
}
|
|
rel := s.fetch()
|
|
s.cached = &rel
|
|
s.cachedAt = time.Now()
|
|
return rel
|
|
}
|
|
|
|
type giteaAsset struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"browser_download_url"`
|
|
}
|
|
|
|
type giteaRelease struct {
|
|
TagName string `json:"tag_name"`
|
|
HTMLURL string `json:"html_url"`
|
|
Draft bool `json:"draft"`
|
|
Prerelease bool `json:"prerelease"`
|
|
Assets []giteaAsset `json:"assets"`
|
|
}
|
|
|
|
func (s *ReleaseService) fetch() Release {
|
|
url := fmt.Sprintf("%s/api/v1/repos/%s/releases?limit=5", s.giteaBase, s.repo)
|
|
resp, err := s.client.Get(url)
|
|
if err != nil {
|
|
return s.fallback
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return s.fallback
|
|
}
|
|
|
|
var list []giteaRelease
|
|
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
|
return s.fallback
|
|
}
|
|
for _, r := range list {
|
|
if r.Draft || r.Prerelease {
|
|
continue
|
|
}
|
|
rel := Release{
|
|
Version: r.TagName,
|
|
PageURL: r.HTMLURL,
|
|
Source: "gitea",
|
|
}
|
|
if rel.PageURL == "" {
|
|
rel.PageURL = s.fallback.PageURL
|
|
}
|
|
for _, a := range r.Assets {
|
|
name := strings.ToLower(a.Name)
|
|
switch {
|
|
case strings.HasSuffix(name, ".dmg"),
|
|
strings.Contains(name, "darwin"),
|
|
strings.Contains(name, "macos"):
|
|
rel.MacOS = a.URL
|
|
case strings.HasSuffix(name, ".exe"),
|
|
strings.Contains(name, "windows"):
|
|
rel.Windows = a.URL
|
|
}
|
|
}
|
|
return rel
|
|
}
|
|
return s.fallback
|
|
}
|
|
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|