feat: 互动处理
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+9
-1
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
+1
-1
@@ -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{},
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ func Routers() {
|
||||
systemRouter.InitOssRouter(NeedAuthGroup) //OSS相关
|
||||
|
||||
// Radio模块路由
|
||||
radioRouter.InitVipRouter(NeedAuthGroup) //VIP相关
|
||||
radioRouter.InitCategoryRouter(NeedAuthGroup) //分类相关
|
||||
radioRouter.InitChannelRouter(NeedAuthGroup) //频道相关
|
||||
radioRouter.InitProgramRouter(NeedAuthGroup) //节目相关
|
||||
|
||||
@@ -7,3 +7,5 @@
|
||||
[sundynix-radio-server]2026-02-28 15:39:51 [31merror[0m /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 [31merror[0m /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 [31merror[0m /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 [31merror[0m /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 [31merror[0m /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"}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
[sundynix-radio-server]2026-02-28 15:39:45 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /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 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/morning-radio/morning-radio-backend/initialize/gorm.go:52 Migrate table success
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package system
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Login interface {
|
||||
@@ -20,10 +21,16 @@ type User struct {
|
||||
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"`
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
|
||||
@@ -8,6 +8,7 @@ type ServiceGroup struct {
|
||||
InteractionService
|
||||
PayService
|
||||
OrderService
|
||||
VipService
|
||||
}
|
||||
|
||||
var GroupApp = new(ServiceGroup)
|
||||
|
||||
@@ -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,8 +34,14 @@ func (s *OrderService) ExecuteOrderUnlock(tx *gorm.DB, outTradeNo string) error
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 根据订单中的 sub_type 决定增加几个月
|
||||
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 if order.Type == 1 {
|
||||
// 4. 订阅 根据订单中的 sub_type 决定增加几个月
|
||||
var months int
|
||||
switch order.SubscriptionType {
|
||||
case "1":
|
||||
@@ -70,3 +77,5 @@ func (s *OrderService) ExecuteOrderUnlock(tx *gorm.DB, outTradeNo string) error
|
||||
}).Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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()])
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user