feat(admin): 添加APK管理功能

实现APK文件的上传、下载和列表功能
- 新增APKInfo数据结构
- 添加/admin/apk/upload、/admin/apk/download和/admin/apk/list路由
- 实现APK存储服务方法
- 添加相关测试用例
This commit is contained in:
2026-03-15 18:53:00 +08:00
parent a0ef7f430d
commit e629b69411
6 changed files with 279 additions and 2 deletions

View File

@@ -167,3 +167,82 @@ func (s *LocalStore) ListReleases(runtimeVersion string) ([]model.ReleaseInfo, e
return out, nil
}
func (s *LocalStore) SaveAPK(runtimeVersion string, reader io.Reader) (string, error) {
apkDir := filepath.Join(s.root, "apk")
if err := os.MkdirAll(apkDir, 0o755); err != nil {
return "", err
}
filename := fmt.Sprintf("%s.apk", runtimeVersion)
apkPath := filepath.Join(apkDir, filename)
tmpPath := apkPath + ".tmp"
f, err := os.Create(tmpPath)
if err != nil {
return "", err
}
_, copyErr := io.Copy(f, reader)
syncErr := f.Sync()
closeErr := f.Close()
if copyErr != nil {
os.Remove(tmpPath)
return "", copyErr
}
if syncErr != nil {
os.Remove(tmpPath)
return "", syncErr
}
if closeErr != nil {
os.Remove(tmpPath)
return "", closeErr
}
if err := os.Rename(tmpPath, apkPath); err != nil {
os.Remove(tmpPath)
return "", err
}
return apkPath, nil
}
func (s *LocalStore) GetAPKPath(runtimeVersion string) (string, error) {
filename := fmt.Sprintf("%s.apk", runtimeVersion)
apkPath := filepath.Join(s.root, "apk", filename)
if _, err := os.Stat(apkPath); err != nil {
return "", err
}
return apkPath, nil
}
func (s *LocalStore) ListAPKs() ([]model.APKInfo, error) {
apkDir := filepath.Join(s.root, "apk")
if _, err := os.Stat(apkDir); err != nil {
if os.IsNotExist(err) {
return []model.APKInfo{}, nil
}
return nil, err
}
entries, err := os.ReadDir(apkDir)
if err != nil {
return nil, err
}
out := make([]model.APKInfo, 0, len(entries))
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".apk") {
continue
}
runtimeVersion := strings.TrimSuffix(e.Name(), ".apk")
full := filepath.Join(apkDir, e.Name())
fi, err := os.Stat(full)
if err != nil {
continue
}
out = append(out, model.APKInfo{
RuntimeVersion: runtimeVersion,
Path: full,
Size: fi.Size(),
CreatedAt: fi.ModTime(),
})
}
sort.Slice(out, func(i, j int) bool { return out[i].RuntimeVersion > out[j].RuntimeVersion })
return out, nil
}