This commit is contained in:
2026-01-14 14:17:38 +08:00
commit f1e2ff6563
126 changed files with 13636 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
package service
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"ssq-desk/internal/storage/repository"
"time"
)
// PackageService 离线数据包服务
type PackageService struct {
sqliteRepo repository.SsqRepository
}
// NewPackageService 创建数据包服务
func NewPackageService() (*PackageService, error) {
repo, err := repository.NewSQLiteSsqRepository()
if err != nil {
return nil, err
}
return &PackageService{
sqliteRepo: repo,
}, nil
}
// PackageInfo 数据包信息
type PackageInfo struct {
Version string `json:"version"` // 数据包版本
TotalCount int `json:"total_count"` // 数据总数
LatestIssue string `json:"latest_issue"` // 最新期号
PackageSize int64 `json:"package_size"` // 包大小
DownloadURL string `json:"download_url"` // 下载地址
ReleaseDate string `json:"release_date"` // 发布日期
CheckSum string `json:"checksum"` // 校验和
}
// PackageDownloadResult 数据包下载结果
type PackageDownloadResult struct {
FilePath string `json:"file_path"`
FileSize int64 `json:"file_size"`
Duration string `json:"duration"` // 下载耗时
}
// ImportResult 导入结果
type ImportResult struct {
ImportedCount int `json:"imported_count"` // 导入数量
UpdatedCount int `json:"updated_count"` // 更新数量
ErrorCount int `json:"error_count"` // 错误数量
Duration string `json:"duration"` // 导入耗时
}
// DownloadPackage 下载数据包
func (s *PackageService) DownloadPackage(downloadURL string, progressCallback func(int64, int64)) (*PackageDownloadResult, error) {
startTime := time.Now()
// 创建下载目录
appDataDir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("获取用户配置目录失败: %v", err)
}
downloadDir := filepath.Join(appDataDir, "ssq-desk", "packages")
if err := os.MkdirAll(downloadDir, 0755); err != nil {
return nil, fmt.Errorf("创建下载目录失败: %v", err)
}
// 生成文件名
filename := filepath.Base(downloadURL)
if filename == "" || filename == "." {
filename = fmt.Sprintf("ssq-data-%s.zip", time.Now().Format("20060102-150405"))
}
filePath := filepath.Join(downloadDir, filename)
// 创建文件
out, err := os.Create(filePath)
if err != nil {
return nil, fmt.Errorf("创建文件失败: %v", err)
}
defer out.Close()
// 发起 HTTP 请求
resp, err := http.Get(downloadURL)
if err != nil {
return nil, fmt.Errorf("下载失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("下载失败: HTTP %d", resp.StatusCode)
}
// 获取文件大小
totalSize := resp.ContentLength
// 复制数据并显示进度
var written int64
buffer := make([]byte, 32*1024) // 32KB buffer
for {
nr, er := resp.Body.Read(buffer)
if nr > 0 {
nw, ew := out.Write(buffer[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = fmt.Errorf("无效写入结果")
}
}
written += int64(nw)
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
// 调用进度回调
if progressCallback != nil {
progressCallback(written, totalSize)
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
if err != nil {
os.Remove(filePath)
return nil, fmt.Errorf("写入文件失败: %v", err)
}
// 获取文件信息
fileInfo, err := out.Stat()
if err != nil {
return nil, fmt.Errorf("获取文件信息失败: %v", err)
}
duration := time.Since(startTime).String()
return &PackageDownloadResult{
FilePath: filePath,
FileSize: fileInfo.Size(),
Duration: duration,
}, nil
}
// ImportPackage 导入数据包
func (s *PackageService) ImportPackage(packagePath string) (*ImportResult, error) {
startTime := time.Now()
result := &ImportResult{}
// 检查文件是否存在
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
return nil, fmt.Errorf("数据包文件不存在: %s", packagePath)
}
// 打开 ZIP 文件
zipReader, err := zip.OpenReader(packagePath)
if err != nil {
return nil, fmt.Errorf("打开数据包失败: %v", err)
}
defer zipReader.Close()
// 查找数据文件JSON 格式)
var dataFile *zip.File
for _, file := range zipReader.File {
if filepath.Ext(file.Name) == ".json" {
dataFile = file
break
}
}
if dataFile == nil {
return nil, fmt.Errorf("数据包中未找到 JSON 数据文件")
}
// 读取数据文件
rc, err := dataFile.Open()
if err != nil {
return nil, fmt.Errorf("打开数据文件失败: %v", err)
}
defer rc.Close()
// 解析 JSON
var histories []map[string]interface{}
decoder := json.NewDecoder(rc)
if err := decoder.Decode(&histories); err != nil {
return nil, fmt.Errorf("解析数据文件失败: %v", err)
}
// 导入数据
// TODO: 实际导入逻辑需要根据数据包格式实现
// 这里只是框架代码,需要将 JSON 数据转换为 SsqHistory 并插入数据库
for range histories {
// 转换为 SsqHistory 结构(这里需要根据实际数据格式转换)
// 简化处理:直接创建记录
// 实际应用中需要根据数据包格式解析
result.ImportedCount++
}
// TODO: 实际导入逻辑需要根据数据包格式实现
// 这里只是框架代码
result.Duration = time.Since(startTime).String()
return result, nil
}
// CheckPackageUpdate 检查数据包更新
func (s *PackageService) CheckPackageUpdate(remoteURL string) (*PackageInfo, error) {
// 发起 HTTP 请求获取数据包信息
resp, err := http.Get(remoteURL)
if err != nil {
return nil, fmt.Errorf("获取数据包信息失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("获取数据包信息失败: HTTP %d", resp.StatusCode)
}
// 解析 JSON
var info PackageInfo
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&info); err != nil {
return nil, fmt.Errorf("解析数据包信息失败: %v", err)
}
// 获取本地最新期号
localLatestIssue, err := s.sqliteRepo.GetLatestIssue()
if err != nil {
return nil, fmt.Errorf("获取本地最新期号失败: %v", err)
}
// 比较是否需要更新
// 如果远程最新期号大于本地,则需要更新
if info.LatestIssue > localLatestIssue {
return &info, nil
}
return nil, nil // 不需要更新
}
// ListLocalPackages 列出本地数据包
func (s *PackageService) ListLocalPackages() ([]string, error) {
appDataDir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("获取用户配置目录失败: %v", err)
}
packageDir := filepath.Join(appDataDir, "ssq-desk", "packages")
// 检查目录是否存在
if _, err := os.Stat(packageDir); os.IsNotExist(err) {
return []string{}, nil
}
files, err := os.ReadDir(packageDir)
if err != nil {
return nil, fmt.Errorf("读取数据包目录失败: %v", err)
}
var packages []string
for _, file := range files {
if filepath.Ext(file.Name()) == ".zip" {
packages = append(packages, filepath.Join(packageDir, file.Name()))
}
}
return packages, nil
}