feat: 互动处理

This commit is contained in:
Blizzard
2026-03-05 16:54:25 +08:00
parent 74b252550b
commit 2583b5f302
30 changed files with 412 additions and 119 deletions
+8
View File
@@ -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)
+2
View File
@@ -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
)
+44
View File
@@ -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
View File
@@ -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))
+4
View File
@@ -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))
+8
View File
@@ -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))
+74
View File
@@ -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)
}
+4
View File
@@ -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))
+8
View File
@@ -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))
+3
View File
@@ -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
View File
@@ -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{},
+1
View File
@@ -48,6 +48,7 @@ func Routers() {
systemRouter.InitOssRouter(NeedAuthGroup) //OSS相关
// Radio模块路由
radioRouter.InitVipRouter(NeedAuthGroup) //VIP相关
radioRouter.InitCategoryRouter(NeedAuthGroup) //分类相关
radioRouter.InitChannelRouter(NeedAuthGroup) //频道相关
radioRouter.InitProgramRouter(NeedAuthGroup) //节目相关
+2
View File
@@ -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"}
+5
View File
@@ -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
+1
View File
@@ -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"`
}
-29
View File
@@ -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"
}
+18
View File
@@ -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"
}
+8
View File
@@ -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"`
}
+9 -2
View File
@@ -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 {
+2
View File
@@ -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
)
+15
View File
@@ -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)
}
}
+1 -1
View File
@@ -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).
+1
View File
@@ -8,6 +8,7 @@ type ServiceGroup struct {
InteractionService
PayService
OrderService
VipService
}
var GroupApp = new(ServiceGroup)
+12 -3
View File
@@ -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
}
+1 -1
View File
@@ -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 -5
View File
@@ -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
+90
View File
@@ -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
}
+3 -14
View File
@@ -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
+5
View File
@@ -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)
}
+25 -21
View File
@@ -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)
}