From 7d446b46c2c4183c802339f307af7b7419a1cd7b Mon Sep 17 00:00:00 2001 From: Rogee Date: Fri, 6 Dec 2024 15:06:15 +0800 Subject: [PATCH] feat: complte media store --- backend/common/media_store/store.go | 19 ++- .../migrations/20241128075611_init.sql | 3 +- .../models/qvyun/public/model/medias.go | 3 +- .../models/qvyun/public/table/medias.go | 10 +- backend/fixtures/audio.html | 96 +++++++++++ backend/fixtures/video.html | 46 +++++ backend/modules/medias/service.go | 73 +++----- backend/modules/medias/service_test.go | 158 ------------------ .../modules/tasks/discover/discover_medias.go | 7 +- backend/modules/tasks/store/store_medias.go | 10 +- 10 files changed, 199 insertions(+), 226 deletions(-) create mode 100644 backend/fixtures/audio.html create mode 100644 backend/fixtures/video.html diff --git a/backend/common/media_store/store.go b/backend/common/media_store/store.go index 119f4c8..9b16503 100644 --- a/backend/common/media_store/store.go +++ b/backend/common/media_store/store.go @@ -14,7 +14,24 @@ type Store []VideoInfo type VideoInfo struct { Hash string Name string - Duration uint + Duration int64 +} + +// Price +func (info VideoInfo) Price() int64 { + min := int64(10) + // if duration is less than 300 seconds, return 10 + if info.Duration < 5*60 { + return min * 100 + } + + // 每多一分钟,价格加 3, 如果余数大于 30 秒,按一分钟计算 + cells := (info.Duration - 5*30) / 60 + if info.Duration%60 > 30 { + cells++ + } + + return (min + cells*3) * 100 } func NewStore(path string) (Store, error) { diff --git a/backend/database/migrations/20241128075611_init.sql b/backend/database/migrations/20241128075611_init.sql index 24c1eb0..ecd74d5 100644 --- a/backend/database/migrations/20241128075611_init.sql +++ b/backend/database/migrations/20241128075611_init.sql @@ -68,7 +68,7 @@ CREATE INDEX idx_user_balance_histories_tenant_id ON user_balance_histories (ten CREATE TABLE medias ( id SERIAL8 PRIMARY KEY, - uuid uuid NOT NULL, + hash VARCHAR(128) NOT NULL UNIQUE, tenant_id INT8 NOT NULL, title VARCHAR(198) NOT NULL, description VARCHAR(198) NOT NULL default '', @@ -106,6 +106,5 @@ DROP TABLE users_tenants; DROP TABLE tenant_user_balances; DROP TABLE user_balance_histories; DROP TABLE medias; -DROP TABLE media_resources; DROP TABLE user_medias; -- +goose StatementEnd diff --git a/backend/database/models/qvyun/public/model/medias.go b/backend/database/models/qvyun/public/model/medias.go index f51ab90..a0dba7c 100644 --- a/backend/database/models/qvyun/public/model/medias.go +++ b/backend/database/models/qvyun/public/model/medias.go @@ -9,13 +9,12 @@ package model import ( "backend/pkg/pg" - "github.com/google/uuid" "time" ) type Medias struct { ID int64 `sql:"primary_key" json:"id"` - UUID uuid.UUID `json:"uuid"` + Hash string `json:"hash"` TenantID int64 `json:"tenant_id"` Title string `json:"title"` Description string `json:"description"` diff --git a/backend/database/models/qvyun/public/table/medias.go b/backend/database/models/qvyun/public/table/medias.go index 5dd9881..c98b6b2 100644 --- a/backend/database/models/qvyun/public/table/medias.go +++ b/backend/database/models/qvyun/public/table/medias.go @@ -18,7 +18,7 @@ type mediasTable struct { // Columns ID postgres.ColumnInteger - UUID postgres.ColumnString + Hash postgres.ColumnString TenantID postgres.ColumnInteger Title postgres.ColumnString Description postgres.ColumnString @@ -69,7 +69,7 @@ func newMediasTable(schemaName, tableName, alias string) *MediasTable { func newMediasTableImpl(schemaName, tableName, alias string) mediasTable { var ( IDColumn = postgres.IntegerColumn("id") - UUIDColumn = postgres.StringColumn("uuid") + HashColumn = postgres.StringColumn("hash") TenantIDColumn = postgres.IntegerColumn("tenant_id") TitleColumn = postgres.StringColumn("title") DescriptionColumn = postgres.StringColumn("description") @@ -79,8 +79,8 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable { ResourcesColumn = postgres.StringColumn("resources") CreatedAtColumn = postgres.TimestampColumn("created_at") UpdatedAtColumn = postgres.TimestampColumn("updated_at") - allColumns = postgres.ColumnList{IDColumn, UUIDColumn, TenantIDColumn, TitleColumn, DescriptionColumn, PriceColumn, DiscountColumn, PublishColumn, ResourcesColumn, CreatedAtColumn, UpdatedAtColumn} - mutableColumns = postgres.ColumnList{UUIDColumn, TenantIDColumn, TitleColumn, DescriptionColumn, PriceColumn, DiscountColumn, PublishColumn, ResourcesColumn, CreatedAtColumn, UpdatedAtColumn} + allColumns = postgres.ColumnList{IDColumn, HashColumn, TenantIDColumn, TitleColumn, DescriptionColumn, PriceColumn, DiscountColumn, PublishColumn, ResourcesColumn, CreatedAtColumn, UpdatedAtColumn} + mutableColumns = postgres.ColumnList{HashColumn, TenantIDColumn, TitleColumn, DescriptionColumn, PriceColumn, DiscountColumn, PublishColumn, ResourcesColumn, CreatedAtColumn, UpdatedAtColumn} ) return mediasTable{ @@ -88,7 +88,7 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable { //Columns ID: IDColumn, - UUID: UUIDColumn, + Hash: HashColumn, TenantID: TenantIDColumn, Title: TitleColumn, Description: DescriptionColumn, diff --git a/backend/fixtures/audio.html b/backend/fixtures/audio.html new file mode 100644 index 0000000..9294a16 --- /dev/null +++ b/backend/fixtures/audio.html @@ -0,0 +1,96 @@ + + + + + + + Document + + + + + + +
+ + + 0:00 +
+ + + + \ No newline at end of file diff --git a/backend/fixtures/video.html b/backend/fixtures/video.html new file mode 100644 index 0000000..8ea6774 --- /dev/null +++ b/backend/fixtures/video.html @@ -0,0 +1,46 @@ + + + + + + + Document + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/modules/medias/service.go b/backend/modules/medias/service.go index 9e57094..1aa1bf6 100644 --- a/backend/modules/medias/service.go +++ b/backend/modules/medias/service.go @@ -5,13 +5,13 @@ import ( "database/sql" "path/filepath" + "backend/common/media_store" "backend/database/models/qvyun/public/table" "backend/pkg/path" "backend/pkg/pg" "backend/providers/storage" . "github.com/go-jet/jet/v2/postgres" - "github.com/google/uuid" "github.com/pkg/errors" "github.com/samber/lo" "github.com/sirupsen/logrus" @@ -162,75 +162,40 @@ func (svc *Service) HasUserBought(ctx context.Context, tenantId, userId, mediaId return mediaID > 0, nil } -// UnPublishTenantWithNotInUUIDs -func (svc *Service) UnPublishTenantWithNotInUUIDs(ctx context.Context, tenantId int64, uuids []string) error { - log := svc.log.WithField("method", "UnPublishTenantWithNotInUUIDs") - - tbl := table.Medias - stmt := tbl. - UPDATE(). - SET( - tbl.Publish.SET(Bool(false)), - ). - WHERE( - tbl.TenantID.EQ(Int(tenantId)).AND( - tbl.UUID.NOT_IN(lo.Map(uuids, func(item string, _ int) Expression { - return String(item) - })...), - ), - ) - log.Debug(stmt.DebugSql()) - - if _, err := stmt.ExecContext(ctx, svc.db); err != nil { - return errors.Wrap(err, "unpublish tenant with not in uuids") - } - - return nil -} - -// GetTenantUUIDs -func (svc *Service) GetTenantUUIDs(ctx context.Context, tenantId int64) ([]string, error) { - log := svc.log.WithField("method", "GetTenantUUIDs") - - tbl := table.Medias - stmt := tbl. - SELECT(tbl.UUID). - WHERE(tbl.TenantID.EQ(Int(tenantId))) - log.Debug(stmt.DebugSql()) - - var uuids []string - if err := stmt.QueryContext(ctx, svc.db, &uuids); err != nil { - return nil, errors.Wrap(err, "query tenant uuids") - } - - return uuids, nil -} - -// PublishTenant -func (svc *Service) PublishTenantMedia(ctx context.Context, tenantId int64, uuid uuid.UUID, name string, price uint) error { - log := svc.log.WithField("method", "PublishTenant") +// Upsert +func (svc *Service) Upsert(ctx context.Context, tenantId int64, item media_store.VideoInfo) error { + log := svc.log.WithField("method", "Upsert") resources := pg.MediaResources{} - if path.DirExists(filepath.Join(svc.storageConfig.Path, uuid.String(), pg.MediaTypeVideo.String())) { + if path.DirExists(filepath.Join(svc.storageConfig.Path, item.Hash, pg.MediaTypeVideo.String())) { resources = append(resources, pg.MediaTypeVideo) } - if path.DirExists(filepath.Join(svc.storageConfig.Path, uuid.String(), pg.MediaTypeAudio.String())) { + if path.DirExists(filepath.Join(svc.storageConfig.Path, item.Hash, pg.MediaTypeAudio.String())) { resources = append(resources, pg.MediaTypeAudio) } - if path.DirExists(filepath.Join(svc.storageConfig.Path, uuid.String(), pg.MediaTypePdf.String())) { + if path.DirExists(filepath.Join(svc.storageConfig.Path, item.Hash, pg.MediaTypePdf.String())) { resources = append(resources, pg.MediaTypePdf) } tbl := table.Medias stmt := tbl. - INSERT(tbl.TenantID, tbl.UUID, tbl.Title, tbl.Price, tbl.Resources, tbl.Publish). - VALUES(Int(tenantId), UUID(uuid), String(name), Int(int64(price)), Json(resources.MustValue()), Bool(true)) + INSERT(tbl.TenantID, tbl.Hash, tbl.Title, tbl.Price, tbl.Resources, tbl.Publish). + VALUES(Int(tenantId), String(item.Hash), String(item.Name), Int(item.Price()), Json(resources), Bool(true)). + ON_CONFLICT(tbl.Hash). + DO_UPDATE( + SET( + tbl.Title.SET(String(item.Name)), + tbl.Price.SET(Int(item.Price())), + tbl.Resources.SET(Json(resources)), + tbl.Publish.SET(Bool(true)), + ), + ) log.Debug(stmt.DebugSql()) if _, err := stmt.ExecContext(ctx, svc.db); err != nil { - return errors.Wrap(err, "publish tenant") + return errors.Wrapf(err, "upsert media: %s %s", item.Hash, item.Name) } return nil diff --git a/backend/modules/medias/service_test.go b/backend/modules/medias/service_test.go index 29e18d5..e450198 100644 --- a/backend/modules/medias/service_test.go +++ b/backend/modules/medias/service_test.go @@ -8,10 +8,7 @@ import ( "backend/database/models/qvyun/public/table" "backend/fixtures" dbUtil "backend/pkg/db" - "backend/providers/storage" - . "github.com/go-jet/jet/v2/postgres" - "github.com/google/uuid" "github.com/samber/lo" . "github.com/smartystreets/goconvey/convey" ) @@ -52,158 +49,3 @@ func TestService_GetUserBoughtMedias(t *testing.T) { }) }) } - -func TestService_List(t *testing.T) { - Convey("TestService_list", t, func() { - db, err := fixtures.GetDB() - So(err, ShouldBeNil) - defer db.Close() - - Convey("truncate all tables", func() { - So(dbUtil.TruncateAllTables(context.TODO(), db, "medias", "media_resources", "user_medias"), ShouldBeNil) - }) - - Convey("insert user_medias data", func() { - items := []model.UserMedias{ - {UserID: 1, TenantID: 1, MediaID: 1, Price: 10}, - } - - tbl := table.UserMedias - stmt := tbl.INSERT(tbl.UserID, tbl.TenantID, tbl.MediaID, tbl.Price).MODELS(items) - - _, err := stmt.Exec(db) - So(err, ShouldBeNil) - }) - - Convey("insert medias data", func() { - items := []model.Medias{ - { - UUID: uuid.New(), - TenantID: 1, - Title: "title1", - Description: "hello", - Price: 100, - Publish: true, - }, - { - UUID: uuid.New(), - TenantID: 1, - Title: "title2", - Description: "hello", - Price: 100, - Publish: true, - }, - { - UUID: uuid.New(), - TenantID: 1, - Title: "title3", - Description: "hello", - Price: 100, - Publish: false, - }, - } - - tbl := table.Medias - stmt := tbl.INSERT( - tbl.UUID, - tbl.TenantID, - tbl.Title, - tbl.Description, - tbl.Price, - tbl.Publish, - ).MODELS(items) - t.Log(stmt.DebugSql()) - - _, err := stmt.Exec(db) - So(err, ShouldBeNil) - }) - - Convey("get list", func() { - svc := &Service{db: db} - So(svc.Prepare(), ShouldBeNil) - - items, err := svc.List(context.TODO(), 1, 1, &ListFilter{ - Bought: lo.ToPtr(true), - }) - So(err, ShouldBeNil) - - t.Logf("items: %+v", items) - So(items, ShouldHaveLength, 1) - }) - }) -} - -func TestService_List1(t *testing.T) { - Convey("TestService_list", t, func() { - db, err := fixtures.GetDB() - So(err, ShouldBeNil) - defer db.Close() - - tbl := table.Medias - stmt := tbl. - SELECT(tbl.AllColumns). - WHERE( - tbl.Publish.EQ(Bool(true)).AND( - tbl.TenantID.EQ(Int(1)), - ), - ). - WHERE(tbl.ID.EQ(Int(1))). - ORDER_BY(tbl.ID.DESC()) - - t.Log(stmt.DebugSql()) - - var dest []ListItem - err = stmt.QueryContext(context.TODO(), db, &dest) - So(err, ShouldBeNil) - - t.Logf("dest: %+v", dest) - }) -} - -func TestService_PublishTenant(t *testing.T) { - Convey("TestService_PublishTenant", t, func() { - db, err := fixtures.GetDB() - So(err, ShouldBeNil) - defer db.Close() - - Convey("truncate all tables", func() { - So(dbUtil.TruncateAllTables(context.TODO(), db, "medias"), ShouldBeNil) - }) - - u1, err := uuid.Parse("6c63c4d8-b2cb-4588-8dd3-9a2276918d17") - So(err, ShouldBeNil) - - u2, err := uuid.Parse("8c183427-02b1-4426-ad65-18c6e7735072") - So(err, ShouldBeNil) - - u3, err := uuid.Parse("24aa56a1-a85b-4a03-9a1b-c39a36103ddf") - So(err, ShouldBeNil) - - Convey("insert some data", func() { - items := []model.Medias{ - {UUID: u1, TenantID: 1, Title: "title1", Description: "hello", Price: 100, Publish: true}, - {UUID: u2, TenantID: 1, Title: "title2", Description: "hello", Price: 100, Publish: true}, - {UUID: u3, TenantID: 1, Title: "title3", Description: "hello", Price: 100, Publish: true}, - } - - Convey("publish tenant", func() { - svc := &Service{ - db: db, - storageConfig: &storage.Config{ - Path: "/projects/mp-qvyun/backend/fixtures/medias", - }, - } - So(svc.Prepare(), ShouldBeNil) - - err := svc.PublishTenantMedia(context.TODO(), 1, items[0].UUID, items[0].Title, 100) - So(err, ShouldBeNil) - - err = svc.PublishTenantMedia(context.TODO(), 1, items[1].UUID, items[1].Title, 200) - So(err, ShouldBeNil) - - err = svc.PublishTenantMedia(context.TODO(), 1, items[2].UUID, items[2].Title, 300) - So(err, ShouldBeNil) - }) - }) - }) -} diff --git a/backend/modules/tasks/discover/discover_medias.go b/backend/modules/tasks/discover/discover_medias.go index 5e78870..b8e2a82 100644 --- a/backend/modules/tasks/discover/discover_medias.go +++ b/backend/modules/tasks/discover/discover_medias.go @@ -5,6 +5,7 @@ import ( "crypto/md5" "fmt" "io" + "math" "os" "os/exec" "path/filepath" @@ -108,7 +109,7 @@ func (d *DiscoverMedias) processVideo(video string, to string) (media_store.Vide if err != nil { return info, errors.Wrapf(err, "get media duration: %s", video) } - info.Duration = uint(duration) + info.Duration = duration return info, nil } @@ -295,7 +296,7 @@ func (d *DiscoverMedias) getPrice(dir string) (uint, error) { } // getMediaDuration get the duration of a media file -func (d *DiscoverMedias) getMediaDuration(file string) (float64, error) { +func (d *DiscoverMedias) getMediaDuration(file string) (int64, error) { // ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input_video.mp4 args := []string{ "-v", "error", @@ -315,7 +316,7 @@ func (d *DiscoverMedias) getMediaDuration(file string) (float64, error) { return 0, errors.Wrapf(err, "get media duration: %s", file) } - return duration, nil + return int64(math.Floor(duration)), nil } // getMedias get the medias in the directory diff --git a/backend/modules/tasks/store/store_medias.go b/backend/modules/tasks/store/store_medias.go index 83ae59f..f90378a 100644 --- a/backend/modules/tasks/store/store_medias.go +++ b/backend/modules/tasks/store/store_medias.go @@ -1,6 +1,8 @@ package store import ( + "context" + "backend/common/media_store" "backend/modules/medias" @@ -27,7 +29,13 @@ func (d *StoreMedias) RunE(targetPath string) error { if err != nil { return errors.Wrapf(err, "new store: %s", targetPath) } - _ = store + + for _, item := range store { + err := d.mediasSvc.Upsert(context.Background(), 1, item) + if err != nil { + d.log.WithError(err).Errorf("upsert media: %s - %s", item.Hash, item.Name) + } + } return nil }