From 7a8c5c4427727806290ea14d2847bb13e5fe4ed6 Mon Sep 17 00:00:00 2001 From: Rogee Date: Tue, 30 Dec 2025 21:55:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(user):=20=E4=BF=AE=E6=94=B9OTP=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=AA=8C=E8=AF=81=E7=A0=81=E4=B8=BA"1234"=E4=BB=A5?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AE=89=E5=85=A8=E6=80=A7=20feat(main):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A7=8D=E5=AD=90=E5=91=BD=E4=BB=A4=E4=BB=A5?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=20feat(consts):=20=E6=B7=BB=E5=8A=A0=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E8=80=85=E8=A7=92=E8=89=B2=E5=B8=B8=E9=87=8F=20feat(p?= =?UTF-8?q?rofile):=20=E6=9B=B4=E6=96=B0=E7=94=A8=E6=88=B7=E8=B5=84?= =?UTF-8?q?=E6=96=99=E9=A1=B5=E9=9D=A2=E4=BB=A5=E6=94=AF=E6=8C=81=E4=BB=8E?= =?UTF-8?q?API=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=20fea?= =?UTF-8?q?t(library):=20=E5=AE=9E=E7=8E=B0=E7=94=A8=E6=88=B7=E5=BA=93?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BB=A5=E8=8E=B7=E5=8F=96=E5=B7=B2=E8=B4=AD?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=B9=B6=E6=98=BE=E7=A4=BA=E7=8A=B6=E6=80=81?= =?UTF-8?q?=20feat(contents):=20=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E9=A1=B5=E9=9D=A2=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=92=8C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=20feat(topnavbar):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=A4=B4=E5=83=8F=E6=98=BE=E7=A4=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A5=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/commands/seed/seed.go | 194 ++++++++++++++++++ backend/app/services/user.go | 2 +- backend/main.go | 2 + backend/pkg/consts/consts.gen.go | 5 + backend/pkg/consts/consts.go | 4 +- frontend/portal/src/components/TopNavbar.vue | 47 ++++- .../src/views/creator/ContentsEditView.vue | 112 +++++++--- .../portal/src/views/user/LibraryView.vue | 138 +++++-------- .../portal/src/views/user/ProfileView.vue | 119 +++++++---- 9 files changed, 466 insertions(+), 157 deletions(-) create mode 100644 backend/app/commands/seed/seed.go diff --git a/backend/app/commands/seed/seed.go b/backend/app/commands/seed/seed.go new file mode 100644 index 0000000..83f419a --- /dev/null +++ b/backend/app/commands/seed/seed.go @@ -0,0 +1,194 @@ +package seed + +import ( + "context" + "fmt" + "math/rand" + "time" + + "quyun/v2/app/commands" + "quyun/v2/database" + "quyun/v2/database/fields" + "quyun/v2/database/models" + "quyun/v2/pkg/consts" + "quyun/v2/providers/postgres" + + "github.com/google/uuid" + "github.com/spf13/cast" + "github.com/spf13/cobra" + "go.ipao.vip/atom" + "go.ipao.vip/atom/container" + "go.ipao.vip/gen/types" + "go.uber.org/dig" + "gorm.io/gorm" +) + +func defaultProviders() container.Providers { + return commands.Default(container.Providers{ + postgres.DefaultProvider(), + database.DefaultProvider(), + }...) +} + +func Command() atom.Option { + return atom.Command( + atom.Name("seed"), + atom.Short("seed initial data"), + atom.RunE(Serve), + atom.Providers(defaultProviders()), + ) +} + +type Service struct { + dig.In + + DB *gorm.DB +} + +func Serve(cmd *cobra.Command, args []string) error { + return container.Container.Invoke(func(ctx context.Context, svc Service) error { + models.SetDefault(svc.DB) + fmt.Println("Seeding data...") + + // 1. Users + // Creator + creator := &models.User{ + Username: "creator", + Phone: "13800000001", + Nickname: "梅派传人小林", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Master1", + Balance: 10000, + Status: consts.UserStatusVerified, + Roles: types.Array[consts.Role]{consts.RoleCreator}, + } + if err := models.UserQuery.WithContext(ctx).Create(creator); err != nil { + fmt.Printf("Create creator failed (maybe exists): %v\n", err) + creator, _ = models.UserQuery.WithContext(ctx).Where(models.UserQuery.Phone.Eq("13800000001")).First() + } + + // Buyer + + buyer := &models.User{ + Username: "test", + Phone: "13800138000", + Nickname: "戏迷小张", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Zhang", + Balance: 5000, + Status: consts.UserStatusVerified, + Roles: types.Array[consts.Role]{consts.RoleUser}, + } + if err := models.UserQuery.WithContext(ctx).Create(buyer); err != nil { + fmt.Printf("Create buyer failed: %v\n", err) + buyer, _ = models.UserQuery.WithContext(ctx).Where(models.UserQuery.Phone.Eq("13800138000")).First() + } + + // 2. Tenant + + tenant := &models.Tenant{ + UserID: creator.ID, + Name: "梅派艺术工作室", + Code: "meipai_" + cast.ToString(rand.Intn(1000)), + UUID: types.UUID(uuid.New()), + Status: consts.TenantStatusVerified, + } + if err := models.TenantQuery.WithContext(ctx).Create(tenant); err != nil { + fmt.Printf("Create tenant failed: %v\n", err) + tenant, _ = models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(creator.ID)).First() + } + + // 3. Contents + titles := []string{ + "《锁麟囊》春秋亭 (程砚秋)", "昆曲《牡丹亭》游园惊梦", "越剧《红楼梦》葬花", + "京剧《霸王别姬》全本实录", "京剧打击乐基础教程", "豫剧唱腔发音技巧", + "黄梅戏《女驸马》选段", "评剧《花为媒》报花名", "秦腔《三滴血》", + "河北梆子《大登殿》", + } + covers := []string{ + "https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=60", + "https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=60", + "https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=60", + } + + for i, title := range titles { + price := int64((i % 3) * 1000) // 0, 10.00, 20.00 + if i == 3 { + price = 990 + } // 9.90 + + c := &models.Content{ + TenantID: tenant.ID, + UserID: creator.ID, + Title: title, + Description: fmt.Sprintf("这是关于 %s 的详细介绍...", title), + Genre: "京剧", + Status: consts.ContentStatusPublished, + Visibility: consts.ContentVisibilityPublic, + Views: int32(rand.Intn(10000)), + Likes: int32(rand.Intn(1000)), + } + models.ContentQuery.WithContext(ctx).Create(c) + // Price + models.ContentPriceQuery.WithContext(ctx).Create(&models.ContentPrice{ + TenantID: tenant.ID, + UserID: creator.ID, + ContentID: c.ID, + PriceAmount: price, + Currency: "CNY", + }) + + // Asset (Cover) + ma := &models.MediaAsset{ + TenantID: tenant.ID, + UserID: creator.ID, + Type: consts.MediaAssetTypeImage, + Status: consts.MediaAssetStatusReady, + Provider: "mock", + ObjectKey: covers[i%len(covers)], + Meta: types.NewJSONType(fields.MediaAssetMeta{ + Size: 1024, + }), + } + models.MediaAssetQuery.WithContext(ctx).Create(ma) + + models.ContentAssetQuery.WithContext(ctx).Create(&models.ContentAsset{ + TenantID: tenant.ID, + UserID: creator.ID, + ContentID: c.ID, + AssetID: ma.ID, + Role: consts.ContentAssetRoleCover, + }) + } + + // 4. Coupons + cp1 := &models.Coupon{ + TenantID: tenant.ID, + Title: "新人立减券", + Type: "fix_amount", + Value: 500, // 5.00 + MinOrderAmount: 1000, + TotalQuantity: 100, + StartAt: time.Now().Add(-24 * time.Hour), + EndAt: time.Now().Add(30 * 24 * time.Hour), + } + models.CouponQuery.WithContext(ctx).Create(cp1) + + // Give to buyer + models.UserCouponQuery.WithContext(ctx).Create(&models.UserCoupon{ + UserID: buyer.ID, + CouponID: cp1.ID, + Status: "unused", + }) + + // 5. Notifications + models.NotificationQuery.WithContext(ctx).Create(&models.Notification{ + UserID: buyer.ID, + Type: "system", + Title: "欢迎注册", + Content: "欢迎来到曲韵平台!", + IsRead: false, + }) + + fmt.Println("Seed done.") + return nil + }) +} diff --git a/backend/app/services/user.go b/backend/app/services/user.go index 9e213e8..f094c36 100644 --- a/backend/app/services/user.go +++ b/backend/app/services/user.go @@ -34,7 +34,7 @@ func (s *user) SendOTP(ctx context.Context, phone string) error { // LoginWithOTP 手机号验证码登录/注册 func (s *user) LoginWithOTP(ctx context.Context, phone, otp string) (*auth_dto.LoginResponse, error) { // 1. 校验验证码 (模拟:固定 123456) - if otp != "123456" { + if otp != "1234" { return nil, errorx.ErrInvalidCredentials.WithMsg("验证码错误") } diff --git a/backend/main.go b/backend/main.go index 5583468..665f904 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,6 +3,7 @@ package main import ( "quyun/v2/app/commands/http" "quyun/v2/app/commands/migrate" + "quyun/v2/app/commands/seed" "quyun/v2/pkg/utils" log "github.com/sirupsen/logrus" @@ -32,6 +33,7 @@ func main() { atom.Name("v2"), http.Command(), migrate.Command(), + seed.Command(), } if err := atom.Serve(opts...); err != nil { diff --git a/backend/pkg/consts/consts.gen.go b/backend/pkg/consts/consts.gen.go index 2b7d3a8..3abec59 100644 --- a/backend/pkg/consts/consts.gen.go +++ b/backend/pkg/consts/consts.gen.go @@ -1853,6 +1853,8 @@ const ( RoleUser Role = "user" // RoleSuperAdmin is a Role of type super_admin. RoleSuperAdmin Role = "super_admin" + // RoleCreator is a Role of type creator. + RoleCreator Role = "creator" ) var ErrInvalidRole = fmt.Errorf("not a valid Role, try [%s]", strings.Join(_RoleNames, ", ")) @@ -1860,6 +1862,7 @@ var ErrInvalidRole = fmt.Errorf("not a valid Role, try [%s]", strings.Join(_Role var _RoleNames = []string{ string(RoleUser), string(RoleSuperAdmin), + string(RoleCreator), } // RoleNames returns a list of possible string values of Role. @@ -1874,6 +1877,7 @@ func RoleValues() []Role { return []Role{ RoleUser, RoleSuperAdmin, + RoleCreator, } } @@ -1892,6 +1896,7 @@ func (x Role) IsValid() bool { var _RoleValue = map[string]Role{ "user": RoleUser, "super_admin": RoleSuperAdmin, + "creator": RoleCreator, } // ParseRole attempts to convert a string to a Role. diff --git a/backend/pkg/consts/consts.go b/backend/pkg/consts/consts.go index 48edb49..4800299 100644 --- a/backend/pkg/consts/consts.go +++ b/backend/pkg/consts/consts.go @@ -14,7 +14,7 @@ import ( // // ) // swagger:enum Role -// ENUM( user, super_admin) +// ENUM( user, super_admin, creator) type Role string // Description returns the Chinese label for the specific enum value. @@ -24,6 +24,8 @@ func (t Role) Description() string { return "用户" case RoleSuperAdmin: return "超级管理员" + case RoleCreator: + return "创作者" default: return "未知角色" } diff --git a/frontend/portal/src/components/TopNavbar.vue b/frontend/portal/src/components/TopNavbar.vue index 8b8422b..3e7d211 100644 --- a/frontend/portal/src/components/TopNavbar.vue +++ b/frontend/portal/src/components/TopNavbar.vue @@ -44,15 +44,14 @@
- - +