diff --git a/api/v1/radio/channel.go b/api/v1/radio/channel.go index 417588b..2a8dcf3 100644 --- a/api/v1/radio/channel.go +++ b/api/v1/radio/channel.go @@ -54,6 +54,10 @@ func (a *ChannelApi) GetFreeChannelList(c *gin.Context) { // @Router /radio/channel/list [post] func (a *ChannelApi) GetChannelList(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.GetChannelList err := c.ShouldBindJSON(&req) if err != nil { @@ -86,6 +90,10 @@ func (a *ChannelApi) GetChannelList(c *gin.Context) { // @Router /radio/channel/detail [get] func (a *ChannelApi) GetChannelDetail(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } id := c.Query("id") if id == "" { response.FailWithMsg("参数错误: id不能为空", c) diff --git a/api/v1/radio/enter.go b/api/v1/radio/enter.go index aec691f..f8d41b7 100644 --- a/api/v1/radio/enter.go +++ b/api/v1/radio/enter.go @@ -9,6 +9,7 @@ type ApiGroup struct { SubscriptionApi InteractionApi PayApi + VipApi } var ApiGroupApp = new(ApiGroup) @@ -20,4 +21,5 @@ var ( subscriptionService = service.GroupApp.RadioServiceGroup.SubscriptionService interactionService = service.GroupApp.RadioServiceGroup.InteractionService payService = service.GroupApp.RadioServiceGroup.PayService + vipService = service.GroupApp.RadioServiceGroup.VipService ) diff --git a/api/v1/radio/interaction.go b/api/v1/radio/interaction.go index 9786a13..a42cc59 100644 --- a/api/v1/radio/interaction.go +++ b/api/v1/radio/interaction.go @@ -21,6 +21,10 @@ type InteractionApi struct{} // @Router /history/add [post] func (a *InteractionApi) AddHistory(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.AddHistory err := c.ShouldBindJSON(&req) if err != nil { @@ -52,6 +56,10 @@ func (a *InteractionApi) DeleteHistory(c *gin.Context) { return } userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } err = interactionService.DeleteHistory(userId, req.ProgramId) if err != nil { global.Logger.Error("删除收听历史失败!", zap.Error(err)) @@ -69,6 +77,10 @@ func (a *InteractionApi) DeleteHistory(c *gin.Context) { // @Router /history/deleteAllHistory [get] func (a *InteractionApi) DeleteAllHistory(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } err := interactionService.DeleteAllHistory(userId) if err != nil { global.Logger.Error("删除所有收听历史失败!", zap.Error(err)) @@ -87,6 +99,10 @@ func (a *InteractionApi) DeleteAllHistory(c *gin.Context) { // @Router /history/list [post] func (a *InteractionApi) GetHistoryList(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.GetHistoryList err := c.ShouldBindJSON(&req) if err != nil { @@ -117,6 +133,10 @@ func (a *InteractionApi) GetHistoryList(c *gin.Context) { // @Router /like/toggle [post] func (a *InteractionApi) ToggleLike(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.ToggleLike err := c.ShouldBindJSON(&req) if err != nil { @@ -143,6 +163,10 @@ func (a *InteractionApi) ToggleLike(c *gin.Context) { // @Router /favorite/list [post] func (a *InteractionApi) GetFavoriteList(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.GetFavoriteList err := c.ShouldBindJSON(&req) if err != nil { @@ -173,6 +197,10 @@ func (a *InteractionApi) GetFavoriteList(c *gin.Context) { // @Router /favorite/add [post] func (a *InteractionApi) AddFavorite(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.AddFavorite err := c.ShouldBindJSON(&req) if err != nil { @@ -198,6 +226,10 @@ func (a *InteractionApi) AddFavorite(c *gin.Context) { // @Router /favorite/remove [post] func (a *InteractionApi) RemoveFavorite(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.RemoveFavorite err := c.ShouldBindJSON(&req) if err != nil { @@ -223,6 +255,10 @@ func (a *InteractionApi) RemoveFavorite(c *gin.Context) { // @Router /favorite/removeAll [get] func (a *InteractionApi) RemoveAllFavorite(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } err := interactionService.RemoveAllFavorite(userId) if err != nil { global.Logger.Error("清空所有收藏失败!", zap.Error(err)) @@ -270,6 +306,10 @@ func (a *InteractionApi) GetCommentList(c *gin.Context) { // @Router /comment/add [post] func (a *InteractionApi) AddComment(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.AddComment err := c.ShouldBindJSON(&req) if err != nil { @@ -295,6 +335,10 @@ func (a *InteractionApi) AddComment(c *gin.Context) { // @Router /comment/delete [post] func (a *InteractionApi) DeleteComment(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req request.DeleteComment err := c.ShouldBindJSON(&req) if err != nil { diff --git a/api/v1/radio/pay.go b/api/v1/radio/pay.go index 4419017..fa6426f 100644 --- a/api/v1/radio/pay.go +++ b/api/v1/radio/pay.go @@ -23,6 +23,10 @@ type PayApi struct{} func (a *PayApi) PrePay(c *gin.Context) { orderId := c.Query("orderId") userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } res, err := payService.PrePay(orderId, userId) if err != nil { global.Logger.Error("支付失败", zap.Error(err)) @@ -32,7 +36,7 @@ func (a *PayApi) PrePay(c *gin.Context) { response.OkWithData(res, c) } -// QueryPay 查询支付 +// QueryPay 查询订阅支付状态 // @Tags 微信支付 // @Summary 支付 // @Security BasicAuth @@ -43,6 +47,10 @@ func (a *PayApi) PrePay(c *gin.Context) { func (a *PayApi) QueryPay(c *gin.Context) { outTradeNo := c.Query("outTradeNo") userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } res, err := payService.QueryPay(outTradeNo, userId) if err != nil { global.Logger.Error("支付失败", zap.Error(err)) diff --git a/api/v1/radio/program.go b/api/v1/radio/program.go index c85ed93..3439262 100644 --- a/api/v1/radio/program.go +++ b/api/v1/radio/program.go @@ -57,6 +57,10 @@ func (a *ProgramApi) GetProgramDetail(c *gin.Context) { return } userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } program, err := programService.GetProgramById(id, userId) if err != nil { global.Logger.Error("获取节目详情失败!", zap.Error(err)) diff --git a/api/v1/radio/subscription.go b/api/v1/radio/subscription.go index a8c7bd6..4992c6a 100644 --- a/api/v1/radio/subscription.go +++ b/api/v1/radio/subscription.go @@ -24,6 +24,10 @@ type SubscriptionApi struct{} // @Router /radio/subscription/list [post] func (a *SubscriptionApi) GetSubscriptionList(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } var req common.PageInfo err := c.ShouldBindJSON(&req) if err != nil { @@ -60,6 +64,10 @@ func (a *SubscriptionApi) UnlockChannel(c *gin.Context) { return } userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } res, no, err := subscriptionService.UnlockChannel(userId, req) if err != nil { global.Logger.Error("解锁频道失败!", zap.Error(err)) diff --git a/api/v1/radio/vip.go b/api/v1/radio/vip.go new file mode 100644 index 0000000..f1d15e5 --- /dev/null +++ b/api/v1/radio/vip.go @@ -0,0 +1,74 @@ +package radio + +import ( + "sundynix-go/model/commom/response" + "sundynix-go/model/radio/request" + radioRes "sundynix-go/model/radio/response" + "sundynix-go/utils/auth" + + "github.com/gin-gonic/gin" +) + +type VipApi struct{} + +// UpdateVipConfig 更新VIP配置 +// @Tags VIP管理 +// @Summary 更新VIP配置 +// @Accept application/json +// @Produce application/json +// @Param data body request.UpdateVipConfig true "VIP配置信息" +// @Success 200 {object} response.Response +// @Router /vip/config/update [post] +func (a *VipApi) UpdateVipConfig(c *gin.Context) { + var req request.UpdateVipConfig + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMsg("参数错误: "+err.Error(), c) + return + } + err = vipService.UpdateVipConfig(req) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithMsg("更新成功", c) +} + +// VipConfigDetail 获取VIP配置详情 +// @Tags VIP管理 +// @Summary 获取VIP配置详情 +// @Produce application/json +// @Param id query string true "id" +// @Success 200 {object} response.Response +// @Router /vip/config/detail [get] +func (a *VipApi) VipConfigDetail(c *gin.Context) { + vipConfig, err := vipService.VipConfigDetail() + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithData(vipConfig, c) +} + +// VipVip 开通vip +// @Tags VIP管理 +// @Summary 开通vip +// @Produce application/json +// @Success 200 {object} response.Response +// @Router /vip/vip [post] +func (a *VipApi) VipVip(c *gin.Context) { + userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } + res, no, err := vipService.VipVip(userId) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithData(radioRes.PrePayResult{ + Payments: res, + OutTradeNo: no, + }, c) +} diff --git a/api/v1/system/auth.go b/api/v1/system/auth.go index b83a6af..a28b70a 100644 --- a/api/v1/system/auth.go +++ b/api/v1/system/auth.go @@ -56,6 +56,10 @@ func (a *AuthApi) Login(c *gin.Context) { func (a *AuthApi) Logout(c *gin.Context) { token := auth.GetToken(c) userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } err := jwtService.PutBlacklist(userId, token) if err != nil { global.Logger.Error("登出失败!", zap.Error(err)) diff --git a/api/v1/system/sys_menu.go b/api/v1/system/sys_menu.go index 7d1c0b6..441ca17 100644 --- a/api/v1/system/sys_menu.go +++ b/api/v1/system/sys_menu.go @@ -138,6 +138,10 @@ func (m *MenuApi) GetAllMenuTree(c *gin.Context) { // @Router /menu/getUserMenuTree [get] func (m *MenuApi) GetUserMenuTree(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } routes, err := menuService.GetUserRoutes(userId) if err != nil { global.Logger.Error("获取用户菜单失败!", zap.Error(err)) @@ -156,6 +160,10 @@ func (m *MenuApi) GetUserMenuTree(c *gin.Context) { // @Router /menu/route [get] func (m *MenuApi) Route(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" || userId == "0" { + response.FailWithMsg("用户未登录", c) + return + } routes, err := menuService.GetUserRoutes(userId) if err != nil { global.Logger.Error("获取用户菜单失败!", zap.Error(err)) diff --git a/api/v1/system/sys_user.go b/api/v1/system/sys_user.go index b133e83..e2d74dd 100644 --- a/api/v1/system/sys_user.go +++ b/api/v1/system/sys_user.go @@ -24,6 +24,9 @@ type UserApi struct { // @Router /user/info [get] func (u *UserApi) CurrentUser(c *gin.Context) { userId := auth.GetUserId(c) + if userId == "" { + + } user, err := userService.GetUserById(userId) if err != nil { global.Logger.Error("获取用户信息失败!", zap.Error(err)) diff --git a/initialize/gorm.go b/initialize/gorm.go index 0970054..f2804d2 100644 --- a/initialize/gorm.go +++ b/initialize/gorm.go @@ -39,11 +39,11 @@ func MigrateTable() { system.SysOperationRecord{}, system.Oss{}, + radio.Vip{}, radio.RadioCategory{}, radio.RadioChannel{}, radio.RadioProgram{}, radio.RadioSubscription{}, - radio.RadioUser{}, radio.Order{}, radio.PayNotify{}, diff --git a/initialize/router.go b/initialize/router.go index 0da9065..05410ae 100644 --- a/initialize/router.go +++ b/initialize/router.go @@ -48,6 +48,7 @@ func Routers() { systemRouter.InitOssRouter(NeedAuthGroup) //OSS相关 // Radio模块路由 + radioRouter.InitVipRouter(NeedAuthGroup) //VIP相关 radioRouter.InitCategoryRouter(NeedAuthGroup) //分类相关 radioRouter.InitChannelRouter(NeedAuthGroup) //频道相关 radioRouter.InitProgramRouter(NeedAuthGroup) //节目相关 diff --git a/log/2026-02-28/error.log b/log/2026-02-28/error.log index 360e781..844b199 100644 --- a/log/2026-02-28/error.log +++ b/log/2026-02-28/error.log @@ -7,3 +7,5 @@ [sundynix-radio-server]2026-02-28 15:39:51 error /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm.go:49 Migrate table failed,err: {"error": "invalid field found for struct sundynix-go/model/radio.RadioCategory's field Cover: define a valid foreign key for relations or implement the Valuer/Scanner interface"} [sundynix-radio-server]2026-02-28 15:40:52 error /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/redis.go:43 Redis connect ping failed,err: {"name": "", "error": "dial tcp 127.0.0.1:6379: connect: connection refused"} [sundynix-radio-server]2026-02-28 15:40:52 error /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/redis.go:17 Redis connect failed,err: {"error": "dial tcp 127.0.0.1:6379: connect: connection refused"} +[sundynix-radio-server]2026-02-28 16:03:16 error /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/redis.go:43 Redis connect ping failed,err: {"name": "", "error": "dial tcp 127.0.0.1:6379: connect: connection refused"} +[sundynix-radio-server]2026-02-28 16:03:16 error /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/redis.go:17 Redis connect failed,err: {"error": "dial tcp 127.0.0.1:6379: connect: connection refused"} diff --git a/log/2026-02-28/info.log b/log/2026-02-28/info.log index 2e5d313..0ef0310 100644 --- a/log/2026-02-28/info.log +++ b/log/2026-02-28/info.log @@ -1,3 +1,8 @@ [sundynix-radio-server]2026-02-28 15:39:45 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm_mysql.go:40 Mysql connect success [sundynix-radio-server]2026-02-28 15:40:52 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm_mysql.go:40 Mysql connect success [sundynix-radio-server]2026-02-28 15:41:00 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm.go:52 Migrate table success +[sundynix-radio-server]2026-02-28 16:03:16 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm_mysql.go:40 Mysql connect success +[sundynix-radio-server]2026-02-28 16:03:30 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm.go:52 Migrate table success +[sundynix-radio-server]2026-02-28 16:03:45 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm_mysql.go:40 Mysql connect success +[sundynix-radio-server]2026-02-28 16:03:45 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/redis.go:46 Redis connect ping response: {"name": "", "pong": "PONG"} +[sundynix-radio-server]2026-02-28 16:03:59 info /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm.go:52 Migrate table success diff --git a/model/radio/radio_subscription_order.go b/model/radio/radio_subscription_order.go index 53b985e..dd77449 100644 --- a/model/radio/radio_subscription_order.go +++ b/model/radio/radio_subscription_order.go @@ -15,5 +15,6 @@ type Order struct { Amount int `json:"amount" gorm:"column:amount;comment:金额分"` Status int `json:"status" gorm:"column:status;comment:订单状态"` // 0:待支付 1:已支付 2:已关闭 PayStatus string `json:"payStatus" gorm:"column:pay_status;comment:支付状态"` + Type int `json:"type" gorm:"column:type;default:1;comment:支付类型"` // 1.订阅支付(包含包月包季包年) 2:vip支付 User *system.User `json:"user" gorm:"foreignKey:UserId"` } diff --git a/model/radio/radio_user.go b/model/radio/radio_user.go deleted file mode 100644 index 9637223..0000000 --- a/model/radio/radio_user.go +++ /dev/null @@ -1,29 +0,0 @@ -package radio - -import ( - "sundynix-go/global" - "sundynix-go/model/system" -) - -// RadioUser 小程序用户信息表 -type RadioUser struct { - global.BaseModel - UserId string `gorm:"size:50;uniqueIndex" json:"userId"` // 关联system用户ID - OpenId string `gorm:"size:80;uniqueIndex" json:"openId"` // 微信openid - UnionId string `gorm:"size:80" json:"unionId"` // 微信unionid - SessionKey string `gorm:"size:200" json:"sessionKey"` // 会话密钥 - NickName string `gorm:"size:50" json:"nickName"` // 昵称 - AvatarId string `gorm:"size:50" json:"avatarId"` // 头像OSS ID - Avatar *system.Oss `gorm:"foreignKey:AvatarId" json:"avatar"` // 头像OSS - Gender int `gorm:"default:0" json:"gender"` // 性别 0:未知 1:男 2:女 - Country string `gorm:"size:50" json:"country"` // 国家 - Province string `gorm:"size:50" json:"province"` // 省份 - City string `gorm:"size:50" json:"city"` // 城市 - Language string `gorm:"size:20" json:"language"` // 语言 - IsVip int `gorm:"default:0" json:"isVip"` // 是否VIP 0:否 1:是 - VipExpireAt *int64 `gorm:"type:bigint" json:"vipExpireAt"` // VIP过期时间 -} - -func (RadioUser) TableName() string { - return "sundynix_radio_user" -} diff --git a/model/radio/radio_vip_config.go b/model/radio/radio_vip_config.go new file mode 100644 index 0000000..91adfae --- /dev/null +++ b/model/radio/radio_vip_config.go @@ -0,0 +1,18 @@ +package radio + +import ( + "sundynix-go/global" + "time" +) + +type Vip struct { + global.BaseModel + Price int `gorm:"default:0;comment:价格,单位,分 " json:"price"` //vip价格 单位:分 + DiscountedPrice int `gorm:"default:0;comment:优惠价格,单位,分 " json:"discountedPrice"` // 优惠价格 单位:分 + ExpiredAt time.Time `gorm:"index;column:expired_at" json:"expiredAt"` //过期时间 + Remark string `gorm:"column:remark" json:"remark"` //备注 +} + +func (Vip) TableName() string { + return "sundynix_radio_vip_config" +} diff --git a/model/radio/request/vip.go b/model/radio/request/vip.go new file mode 100644 index 0000000..d19e055 --- /dev/null +++ b/model/radio/request/vip.go @@ -0,0 +1,8 @@ +package request + +type UpdateVipConfig struct { + Id string `json:"id" binding:"required"` + Price int `json:"price" binding:"required"` + DiscountedPrice int `json:"discountedPrice"` + Remark string `json:"remark"` +} diff --git a/model/system/sys_user.go b/model/system/sys_user.go index 78c5da5..6c1b508 100644 --- a/model/system/sys_user.go +++ b/model/system/sys_user.go @@ -2,6 +2,7 @@ package system import ( "sundynix-go/global" + "time" ) type Login interface { @@ -11,19 +12,25 @@ type Login interface { } type User struct { global.BaseModel - TenantId string `gorm:"size:20;" json:"tenantId" form:"tenantId"` - ClientId string `gorm:"size:20;" json:"clientId"` - Name string `gorm:"size:20" json:"name" form:"name"` - Account string `gorm:"size:11;" json:"account" form:"account"` - Password string `gorm:"size:100;" json:"-" form:"password"` - NickName string `gorm:"size:20;column:nick_name" json:"nickName" form:"nickName"` - Phone string `gorm:"size:20;column:phone" json:"phone" form:"phone"` - SessionKey string `gorm:"size:80;column:session_key" json:"sessionKey" form:"sessionKey"` - UnionId string `gorm:"size:80;column:union_id" json:"unionId"` - MiniOpenId string `gorm:"size:80;column:mini_open_id" json:"miniOpenId" form:"miniOpenId"` - SaOpenId string `gorm:"size:80;column:sa_open_id" json:"saOpenId"` - AvatarId string `gorm:"size:50;column:avatar_id" json:"avatarId"` - Avatar *Oss `gorm:"foreignKey:AvatarId" json:"avatar"` + TenantId string `gorm:"size:20;" json:"tenantId" form:"tenantId"` + ClientId string `gorm:"size:20;" json:"clientId"` + Name string `gorm:"size:20" json:"name" form:"name"` + Account string `gorm:"size:11;" json:"account" form:"account"` + Password string `gorm:"size:100;" json:"-" form:"password"` + NickName string `gorm:"size:20;column:nick_name" json:"nickName" form:"nickName"` + Phone string `gorm:"size:20;column:phone" json:"phone" form:"phone"` + SessionKey string `gorm:"size:80;column:session_key" json:"sessionKey" form:"sessionKey"` + UnionId string `gorm:"size:80;column:union_id" json:"unionId"` + OpenId string `gorm:"size:80;column:open_id" json:"openId" form:"openId"` + AvatarId string `gorm:"size:50;column:avatar_id" json:"avatarId"` + Avatar *Oss `gorm:"foreignKey:AvatarId" json:"avatar"` + Gender int `gorm:"default:0" json:"gender"` // 性别 0:未知 1:男 2:女 + Country string `gorm:"size:50" json:"country"` // 国家 + Province string `gorm:"size:50" json:"province"` // 省份 + City string `gorm:"size:50" json:"city"` // 城市 + Language string `gorm:"size:20" json:"language"` // 语言 + IsVip int `gorm:"default:0" json:"isVip"` // 是否VIP 0:否 1:是 + VipExpireAt *time.Time `gorm:"column:vip_expire_at" json:"vipExpireAt"` // VIP过期时间 } func (u *User) GetAccount() string { diff --git a/router/radio/enter.go b/router/radio/enter.go index ffdcca4..9c16289 100644 --- a/router/radio/enter.go +++ b/router/radio/enter.go @@ -9,6 +9,7 @@ type RadioRouterGroup struct { SubscriptionRouter InteractionRouter PayRouter + VipRouter } var GroupApp = new(RadioRouterGroup) @@ -20,4 +21,5 @@ var ( subscriptionApi = v1.ApiGroupApp.RadioApiGroup.SubscriptionApi interactionApi = v1.ApiGroupApp.RadioApiGroup.InteractionApi payApi = v1.ApiGroupApp.RadioApiGroup.PayApi + vipApi = v1.ApiGroupApp.RadioApiGroup.VipApi ) diff --git a/router/radio/vip_router.go b/router/radio/vip_router.go new file mode 100644 index 0000000..d6d3d7c --- /dev/null +++ b/router/radio/vip_router.go @@ -0,0 +1,15 @@ +package radio + +import "github.com/gin-gonic/gin" + +type VipRouter struct{} + +func (r *VipRouter) InitVipRouter(Router *gin.RouterGroup) { + vipRouter := Router.Group("/vip") + { + vipRouter.POST("config/update", vipApi.UpdateVipConfig) + vipRouter.POST("config/detail", vipApi.VipConfigDetail) + // 开通vip + vipRouter.POST("vip", vipApi.VipVip) + } +} diff --git a/service/radio/channel_service.go b/service/radio/channel_service.go index 5f6d627..b7d1ea5 100644 --- a/service/radio/channel_service.go +++ b/service/radio/channel_service.go @@ -91,7 +91,7 @@ func (s *ChannelService) GetChannelById(userId, id string) (radio.RadioChannel, return channel, nil } channel.HasSubscribed = 0 - if userId != "" { + if userId != "" && userId != "0" { var sub radio.RadioSubscription err = global.DB.Model(&radio.RadioSubscription{}). Where("user_id = ?", userId). diff --git a/service/radio/enter.go b/service/radio/enter.go index 5291b7b..0042d9d 100644 --- a/service/radio/enter.go +++ b/service/radio/enter.go @@ -8,6 +8,7 @@ type ServiceGroup struct { InteractionService PayService OrderService + VipService } var GroupApp = new(ServiceGroup) diff --git a/service/radio/order_service.go b/service/radio/order_service.go index e500171..f93fe8d 100644 --- a/service/radio/order_service.go +++ b/service/radio/order_service.go @@ -3,6 +3,7 @@ package radio import ( "errors" "sundynix-go/model/radio" + "sundynix-go/model/system" "time" "gorm.io/gorm" @@ -12,7 +13,7 @@ type OrderService struct{} var OrderServiceApp = new(OrderService) -// ExecuteOrderUnlock 核心原子操作:解锁权限 +// ExecuteOrderUnlock 订阅/开通vip 核心原子操作:解锁权限 func (s *OrderService) ExecuteOrderUnlock(tx *gorm.DB, outTradeNo string) error { var order radio.Order // 1. 锁住订单行,防止回调和主动查询并发导致时长翻倍 @@ -33,40 +34,48 @@ func (s *OrderService) ExecuteOrderUnlock(tx *gorm.DB, outTradeNo string) error }).Error; err != nil { return err } - - // 4. 根据订单中的 sub_type 决定增加几个月 - var months int - switch order.SubscriptionType { - case "1": - months = 1 // 月 - case "2": - months = 3 // 季 - case "3": - months = 12 // 年 - } - - // 5. 更新或创建订阅权限 - var sub radio.RadioSubscription - now := time.Now() - err := tx.Where("user_id = ? AND channel_id = ?", order.UserId, order.ChannelId).First(&sub).Error - - if errors.Is(err, gorm.ErrRecordNotFound) { - // 首次订阅 - return tx.Create(&radio.RadioSubscription{ - UserId: order.UserId, - ChannelId: order.ChannelId, - ExpiredAt: now.AddDate(0, months, 0), - Status: 1, + if order.Type == 2 { + //4.开通vip 过期时间为2099 年 + return tx.Model(&system.User{}).Where("id = ?", order.UserId).Updates(map[string]interface{}{ + "is_vip": 1, + "vip_expire_at": time.Date(2099, 12, 31, 23, 59, 59, 999, time.Local), }).Error - } else { - // 续费逻辑 - newExpiredAt := sub.ExpiredAt - if sub.ExpiredAt.Before(now) { - newExpiredAt = now // 已过期,从现在开始往后加 + } else if order.Type == 1 { + // 4. 订阅 根据订单中的 sub_type 决定增加几个月 + var months int + switch order.SubscriptionType { + case "1": + months = 1 // 月 + case "2": + months = 3 // 季 + case "3": + months = 12 // 年 + } + + // 5. 更新或创建订阅权限 + var sub radio.RadioSubscription + now := time.Now() + err := tx.Where("user_id = ? AND channel_id = ?", order.UserId, order.ChannelId).First(&sub).Error + + if errors.Is(err, gorm.ErrRecordNotFound) { + // 首次订阅 + return tx.Create(&radio.RadioSubscription{ + UserId: order.UserId, + ChannelId: order.ChannelId, + ExpiredAt: now.AddDate(0, months, 0), + Status: 1, + }).Error + } else { + // 续费逻辑 + newExpiredAt := sub.ExpiredAt + if sub.ExpiredAt.Before(now) { + newExpiredAt = now // 已过期,从现在开始往后加 + } + return tx.Model(&sub).Updates(map[string]interface{}{ + "expired_at": newExpiredAt.AddDate(0, months, 0), + "status": 1, + }).Error } - return tx.Model(&sub).Updates(map[string]interface{}{ - "expired_at": newExpiredAt.AddDate(0, months, 0), - "status": 1, - }).Error } + return nil } diff --git a/service/radio/pay_service.go b/service/radio/pay_service.go index 1d0039a..f509586 100644 --- a/service/radio/pay_service.go +++ b/service/radio/pay_service.go @@ -58,7 +58,7 @@ func (s *PayService) PrePay(orderId, userId string) (resp *jsapi.PrepayWithReque Total: core.Int64(int64(order.Amount)), }, Payer: &jsapi.Payer{ - Openid: core.String(user.MiniOpenId), + Openid: core.String(user.OpenId), }, //Detail: &jsapi.Detail{ // CostPrice: core.Int64(608800), diff --git a/service/radio/subscription_service.go b/service/radio/subscription_service.go index 0ec9083..4713362 100644 --- a/service/radio/subscription_service.go +++ b/service/radio/subscription_service.go @@ -7,6 +7,7 @@ import ( common "sundynix-go/model/commom/request" "sundynix-go/model/radio" "sundynix-go/model/radio/request" + "sundynix-go/model/system" "sundynix-go/utils/uniqueid" "sundynix-go/utils/wechat" @@ -64,6 +65,11 @@ func (s *SubscriptionService) UnlockChannel(userId string, req request.UnlockCha if err != nil { return nil, "", err } + var user system.User + err = global.DB.Where("id = ?", userId).First(&user).Error + if err != nil || user.OpenId == "" { + return nil, "", err + } //2.创建一个订单 根据eventType 创建不同的订单 var price int var orderName string @@ -81,6 +87,7 @@ func (s *SubscriptionService) UnlockChannel(userId string, req request.UnlockCha return nil, "", errors.New("无效的订阅类型") } order := radio.Order{ + Type: 1, //很重要 1订阅 2开通vip UserId: userId, OutTradeNo: uniqueid.GenOrderNo(), ChannelId: req.ChannelId, @@ -93,11 +100,6 @@ func (s *SubscriptionService) UnlockChannel(userId string, req request.UnlockCha return nil, "", err } //4.调用微信api 拉起支付 - var user radio.RadioUser - err = global.DB.Where("user_id = ?", userId).First(&user).Error - if err != nil { - return nil, "", err - } payClient, err := wechat.GetWxPayClient() if err != nil { return nil, "", err diff --git a/service/radio/vip.go b/service/radio/vip.go new file mode 100644 index 0000000..eac61cd --- /dev/null +++ b/service/radio/vip.go @@ -0,0 +1,90 @@ +package radio + +import ( + "context" + "sundynix-go/global" + "sundynix-go/model/radio" + "sundynix-go/model/radio/request" + "sundynix-go/model/system" + "sundynix-go/utils/uniqueid" + "sundynix-go/utils/wechat" + + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" +) + +type VipService struct{} + +func (s *VipService) UpdateVipConfig(req request.UpdateVipConfig) error { + updateData := map[string]interface{}{ + "price": req.Price, + "discountedPrice": req.DiscountedPrice, + "remark": req.Remark, + } + err := global.DB.Model(&radio.Vip{}).Where("id = ?", req.Id).Updates(updateData).Error + return err +} + +func (s *VipService) VipConfigDetail() (radio.Vip, error) { + var vip radio.Vip + err := global.DB.Model(&radio.Vip{}).First(&vip).Error + return vip, err +} + +// VipVip 开通vip +func (s *VipService) VipVip(userId string) (*jsapi.PrepayWithRequestPaymentResponse, string, error) { + //1.查询vip配置 + var config radio.Vip + err := global.DB.Model(&radio.Vip{}).First(&config).Error + if err != nil { + return nil, "", err + } + //2.创建订单 + order := radio.Order{ + Type: 2, //很重要 1订阅 2开通vip + UserId: userId, + OutTradeNo: uniqueid.GenOrderNo(), + SubscriptionType: "vip sub", + Amount: config.DiscountedPrice, + Name: "vip sub", + } + err = global.DB.Create(&order).Error + if err != nil { + return nil, "", err + } + //3.调用微信api 拉起支付 + var user system.User + err = global.DB.Where("id = ?", userId).First(&user).Error + if err != nil || user.OpenId == "" { + return nil, "", err + } + payClient, err := wechat.GetWxPayClient() + if err != nil { + return nil, "", err + } + svc := jsapi.JsapiApiService{Client: payClient} + result, _, err := svc.PrepayWithRequestPayment(context.Background(), + jsapi.PrepayRequest{ + Appid: core.String(global.Config.MiniProgram.AppId), + Mchid: core.String(global.Config.WechatPay.MchId), + Description: core.String(order.Name), + OutTradeNo: core.String(order.OutTradeNo), + //TimeExpire: core.Time(time.Now()), //选填 + //Attach: core.String("自定义数据说明"), //选填 + NotifyUrl: core.String(global.Config.WechatPay.NotifyUrl), + //GoodsTag: core.String("WXG"), //选填 + //SupportFapiao: core.Bool(false), //选填 + Amount: &jsapi.Amount{ + Currency: core.String("CNY"), + Total: core.Int64(int64(config.DiscountedPrice)), + }, + Payer: &jsapi.Payer{ + Openid: core.String(user.OpenId), + }, + }) + if err != nil { + return nil, "", err + } + + return result, order.OutTradeNo, nil +} diff --git a/service/system/sys_user.go b/service/system/sys_user.go index c147115..fc26cd3 100644 --- a/service/system/sys_user.go +++ b/service/system/sys_user.go @@ -12,7 +12,6 @@ import ( "strconv" "sundynix-go/global" common "sundynix-go/model/commom/request" - "sundynix-go/model/radio" "sundynix-go/model/system" systemReq "sundynix-go/model/system/request" systemResp "sundynix-go/model/system/response" @@ -145,29 +144,19 @@ func (userService *UserService) MiniLogin(code string) (result *system.User, err // 7. 根据openid查询用户 存在--> 更新session_key 返回数据 var user system.User - err = global.DB.Where("mini_open_id = ?", wxResp.Openid).Preload("Avatar").First(&user).Error + err = global.DB.Where("open_id = ?", wxResp.Openid).Preload("Avatar").First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { // 8. 使用 Transaction 闭包管理事务 err = global.DB.Transaction(func(tx *gorm.DB) error { // 创建新用户 newUser := system.User{ - Name: uniqueid.GenerateName(), - MiniOpenId: wxResp.Openid, + Name: uniqueid.GenerateRadioUsername(), + OpenId: wxResp.Openid, SessionKey: wxResp.SessionKey, } if err := tx.Create(&newUser).Error; err != nil { return err } - // 创建小程序用户 - mpUser := radio.RadioUser{ - UserId: newUser.Id, - OpenId: wxResp.Openid, - UnionId: wxResp.Unionid, - SessionKey: wxResp.SessionKey, - } - if err := tx.Create(&mpUser).Error; err != nil { - return err - } // 赋值给外部变量以便返回 user = newUser diff --git a/utils/hash_test.go b/utils/hash_test.go index e04d239..e685b61 100644 --- a/utils/hash_test.go +++ b/utils/hash_test.go @@ -109,3 +109,8 @@ func TestGetZeroTime(t *testing.T) { fmt.Printf("当天零点: %v\n", zeroTime) fmt.Printf("时间戳(秒): %v\n", zeroTime.Unix()) } + +func TestGenName(t *testing.T) { + username := uniqueid.GenerateRadioUsername() + fmt.Println(username) +} diff --git a/utils/uniqueid/id_generator.go b/utils/uniqueid/id_generator.go index 26baa94..53b7794 100644 --- a/utils/uniqueid/id_generator.go +++ b/utils/uniqueid/id_generator.go @@ -1,10 +1,9 @@ package uniqueid import ( - "crypto/rand" "fmt" - "math/big" - "strings" + "math/rand" + "time" "github.com/google/uuid" ) @@ -17,25 +16,30 @@ func GenerateId() string { return uuidV1.String() } -func GenerateName() string { - str := uuid.New().String() - //生成一个用户名 比如花友u278bb 中文后的字符随机生成 不可重复 取str的前六位 - return "花友" + str[6:12] +// GenerateRadioUsername 生成具有电台氛围的用户名称 +func GenerateRadioUsername() string { + // 1. 文艺词库 + adjectives := []string{"虚构", "私奔", "落日", "低空", "巡航", "无声", "迷失", "告白", "极光", "霓虹"} + nouns := []string{"调频", "电波", "磁带", "频率", "回声", "岛屿", "信箱", "航站", "独白", "碎片"} -} + // 2. 初始化随机种子 (使用纳秒级时间戳) + r := rand.New(rand.NewSource(time.Now().UnixNano())) -// GenerateRandomCode 生成邀请码 -func GenerateRandomCode(length int) string { - const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - var sb strings.Builder - for i := 0; i < length; i++ { - n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) - sb.WriteByte(charset[n.Int64()]) + // 3. 随机抽取词库 + adj := adjectives[r.Intn(len(adjectives))] + noun := nouns[r.Intn(len(nouns))] + + // 4. 获取当前时间的微秒/纳秒部分作为“身份码” + // 取纳秒的最后5位,既能体现随机性,又不会像日期那样冗长 + timeSuffix := time.Now().UnixNano() % 100000 + + // 5. 混合生成:采用不同的模板增加随机感 + templates := []string{ + "%s%s_%05d", // 如:落日电波_12345 + "Hz.%d-%s%s", // 如:Hz.67890-虚构独白 + "%s%s-%d-FM", // 如:迷失频率-54321-FM } - return sb.String() -} -// GenCodeKey 生成邀请码的key -func GenCodeKey(userId string) string { - return fmt.Sprintf("code:%s", userId) + selectedTemplate := templates[r.Intn(len(templates))] + return fmt.Sprintf(selectedTemplate, adj, noun, timeSuffix) }