This commit is contained in:
2026-08-25 17:17:06 +08:00
parent e8812a0a73
commit 73ac7b85db
13 changed files with 175 additions and 9 deletions
+5
View File
@@ -48,3 +48,8 @@ func (c *cAdmin) ListAppVersions(ctx context.Context, req *dto.AdminAppVersionLi
func (c *cAdmin) AddAppVersion(ctx context.Context, req *dto.AdminAppVersionAddReq) (*dto.AdminAppVersionAddRes, error) {
return service.AppVersion.AdminAddVersion(ctx, req)
}
// DeleteAppVersion 删除版本记录
func (c *cAdmin) DeleteAppVersion(ctx context.Context, req *dto.AdminAppVersionDeleteReq) (*dto.AdminAppVersionDeleteRes, error) {
return service.AppVersion.AdminDeleteVersion(ctx, req)
}
+19
View File
@@ -64,6 +64,25 @@ func (d *appVersionDao) DeleteByVersion(ctx context.Context, version string) err
return err
}
// GetById 按主键查询版本记录,不存在返回 nil
func (d *appVersionDao) GetById(ctx context.Context, id int64) (*entity.AppVersion, error) {
var e entity.AppVersion
err := g.DB().Model(consts.TableAppVersion).Ctx(ctx).Where("id", id).Scan(&e)
if err != nil {
if common.IsNoRows(err) {
return nil, nil
}
return nil, err
}
return &e, nil
}
// DeleteById 按主键删除版本记录
func (d *appVersionDao) DeleteById(ctx context.Context, id int64) error {
_, err := g.DB().Model(consts.TableAppVersion).Ctx(ctx).Where("id", id).Delete()
return err
}
// GetByVersion 按版本号查询(新增前查重,返回 nil 表示不存在)
func (d *appVersionDao) GetByVersion(ctx context.Context, version string) (*entity.AppVersion, error) {
var e entity.AppVersion
+8
View File
@@ -124,3 +124,11 @@ type AdminAppVersionAddReq struct {
}
type AdminAppVersionAddRes struct{}
// AdminAppVersionDeleteReq 删除版本记录(删最新版本联动删除 APK 文件,删历史版本仅删记录)
type AdminAppVersionDeleteReq struct {
g.Meta `path:"/app-versions/delete" method:"post" summary:"删除版本" tags:"管理端"`
Id int64 `json:"id" v:"required|min:1" dc:"版本记录 id"`
}
type AdminAppVersionDeleteRes struct{}
+34
View File
@@ -94,6 +94,40 @@ func (s *appVersionService) AdminAddVersion(ctx context.Context, req *dto.AdminA
return &dto.AdminAppVersionAddRes{}, nil
}
// AdminDeleteVersion 删除版本记录(Serial 串行,防与下发并发交错):
// 删最新版本时联动删除 APK 文件(客户端 update 返回空不再提示、下载 404);
// 删历史版本仅删记录、不动文件(固定文件永远对应最新版本)。
// 先删记录再删文件:记录删除是核心操作,文件删除失败仅记日志不阻断
// (记录没了客户端不会误提示更新,残留旧文件无害)。
func (s *appVersionService) AdminDeleteVersion(ctx context.Context, req *dto.AdminAppVersionDeleteReq) (*dto.AdminAppVersionDeleteRes, error) {
err := common.Serial().Submit(ctx, func() error {
v, err := dao.AppVersion.GetById(ctx, req.Id)
if err != nil {
return err
}
if v == nil {
return gerror.NewCode(common.CodeVersionNotFound)
}
latest, err := dao.AppVersion.Latest(ctx)
if err != nil {
return err
}
if err := dao.AppVersion.DeleteById(ctx, req.Id); err != nil {
return err
}
if latest != nil && latest.Id == v.Id {
if err := os.Remove(common.ApkFilePath(ctx)); err != nil && !os.IsNotExist(err) {
g.Log().Errorf(ctx, "删除最新版本 %s 后清理 APK 失败: %+v", v.Version, err)
}
}
return nil
})
if err != nil {
return nil, err
}
return &dto.AdminAppVersionDeleteRes{}, nil
}
// saveApk 保存 APK:先落临时文件再原子重命名覆盖固定文件名,
// 保证目录下永远只保留最新一个文件(异常中断不产生半截正式文件)。
func saveApk(ctx context.Context, f *ghttp.UploadFile) error {
+73
View File
@@ -15,6 +15,7 @@ import (
"github.com/gogf/gf/v2/net/ghttp"
"observer-server/biz/dao"
"observer-server/biz/model/dto"
"observer-server/common"
)
@@ -153,3 +154,75 @@ func TestAppVersionList(t *testing.T) {
t.Fatalf("latest = %s, want %s", res.List[0].Version, base+"1")
}
}
// TestAppVersionDeleteLatest 删除最新版本:记录删除 + APK 文件联动删除
func TestAppVersionDeleteLatest(t *testing.T) {
ver := uniqueVersion()
if _, err := AppVersion.AdminAddVersion(ctx(), &dto.AdminAppVersionAddReq{
Version: ver, File: newApkUpload(t, "v.apk", "apk-v1"),
}); err != nil {
t.Fatalf("add version: %v", err)
}
latest, err := dao.AppVersion.Latest(ctx())
if err != nil || latest == nil {
t.Fatalf("latest = %+v, %v", latest, err)
}
if _, err := AppVersion.AdminDeleteVersion(ctx(), &dto.AdminAppVersionDeleteReq{Id: latest.Id}); err != nil {
t.Fatalf("delete version: %v", err)
}
// 记录已删(测试库有历史残留记录,update 不再返回被删版本即可)
gone, err := dao.AppVersion.GetById(ctx(), latest.Id)
if err != nil || gone != nil {
t.Fatalf("record should be deleted: %+v, %v", gone, err)
}
res, err := AppVersion.GetUpdate(ctx(), &dto.AppUpdateReq{})
if err != nil {
t.Fatalf("get update: %v", err)
}
if res.Version == ver {
t.Fatalf("update still returns deleted version %s", ver)
}
// APK 文件联动删除(下载 404)
if _, err := os.Stat(common.ApkFilePath(ctx())); !os.IsNotExist(err) {
t.Fatalf("apk file should be removed, stat err = %v", err)
}
}
// TestAppVersionDeleteHistoric 删除历史版本:仅删记录,APK 文件保留(仍对应最新版本)
func TestAppVersionDeleteHistoric(t *testing.T) {
ver1 := uniqueVersion()
ver2 := uniqueVersion() + "1"
for _, v := range []string{ver1, ver2} {
if _, err := AppVersion.AdminAddVersion(ctx(), &dto.AdminAppVersionAddReq{
Version: v, File: newApkUpload(t, "v.apk", "apk"),
}); err != nil {
t.Fatalf("add version %s: %v", v, err)
}
}
old, err := dao.AppVersion.GetByVersion(ctx(), ver1)
if err != nil || old == nil {
t.Fatalf("get by version: %+v, %v", old, err)
}
if _, err := AppVersion.AdminDeleteVersion(ctx(), &dto.AdminAppVersionDeleteReq{Id: old.Id}); err != nil {
t.Fatalf("delete version: %v", err)
}
// 最新记录仍是 ver2
res, err := AppVersion.GetUpdate(ctx(), &dto.AppUpdateReq{})
if err != nil {
t.Fatalf("get update: %v", err)
}
if res.Version != ver2 {
t.Fatalf("update = %+v, want %s", res, ver2)
}
// APK 文件保留
if _, err := os.Stat(common.ApkFilePath(ctx())); err != nil {
t.Fatalf("apk file should remain: %v", err)
}
}
// TestAppVersionDeleteNotFound 删除不存在的版本报错
func TestAppVersionDeleteNotFound(t *testing.T) {
if _, err := AppVersion.AdminDeleteVersion(ctx(), &dto.AdminAppVersionDeleteReq{Id: 999999999}); err == nil {
t.Fatal("delete missing version should fail")
}
}
Binary file not shown.