feat: 长文本语音合成

This commit is contained in:
Blizzard
2026-03-09 17:25:23 +08:00
parent dda4d2e1d6
commit bdcd96a058
13 changed files with 358 additions and 23 deletions
+55
View File
@@ -154,6 +154,61 @@ func (a *InteractionApi) ToggleLike(c *gin.Context) {
response.OkWithData(isLiked, c)
}
// GetLikeList 获取点赞列表
// @Tags 用户互动
// @Summary 获取收藏列表
// @Produce application/json
// @Param data body request.GetLikeList true "分页查询"
// @Success 200 {object} response.Response
// @Router /like/list [post]
func (a *InteractionApi) GetLikeList(c *gin.Context) {
userId := auth.GetUserId(c)
if userId == "" || userId == "0" {
response.FailWithMsg("用户未登录", c)
return
}
var req request.GetLikeList
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMsg("参数错误: "+err.Error(), c)
return
}
list, total, err := interactionService.GetLikeList(userId, req)
if err != nil {
global.Logger.Error("获取收藏列表失败!", zap.Error(err))
response.FailWithMsg(err.Error(), c)
return
}
response.OkWithData(response.PageResult{
List: list,
Total: total,
Page: req.Current,
}, c)
}
// RemoveAllLike 清空所有赞
// @Tags 用户互动
// @Summary 清空所有赞
// @Produce application/json
// @Success 200 {object} response.Response
// @Router /like/removeAll [get]
func (a *InteractionApi) RemoveAllLike(c *gin.Context) {
userId := auth.GetUserId(c)
if userId == "" || userId == "0" {
response.FailWithMsg("用户未登录", c)
return
}
err := interactionService.RemoveAllLike(userId)
if err != nil {
global.Logger.Error("清空所有赞失败!", zap.Error(err))
response.FailWithMsg(err.Error(), c)
return
}
response.OkWithMsg("清空所有赞成功", c)
}
// GetFavoriteList 获取收藏列表
// @Tags 用户互动
// @Summary 获取收藏列表
+8 -1
View File
@@ -22,6 +22,13 @@ mini-program:
app-id: wx52dfc635739a9c19
app-secret: 84c6ddab1f24d0222da57bedb681c81f
# 腾讯文字转语音
tencent-tts:
app-id: 1312892187
secret-id: AKIDKaeU7XjhSzIOGuKWUEk26wY1MUP6asyr
secret-key: lU0JOFrGSSGqDMLKBoIbnmX6TcXIqKbe
# 微信支付
wechat-pay:
mch-id: 1735188493 # 商户号
@@ -75,7 +82,7 @@ redis:
- 172.21.0.2:7002
db: 1
# name: ""
# password: "sundynix"
password: "sundynix"
cluster: false
zap:
+95 -4
View File
@@ -813,6 +813,55 @@ const docTemplate = `{
}
}
},
"/like/list": {
"post": {
"produces": [
"application/json"
],
"tags": [
"用户互动"
],
"summary": "获取收藏列表",
"parameters": [
{
"description": "分页查询",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.GetLikeList"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/like/removeAll": {
"get": {
"produces": [
"application/json"
],
"tags": [
"用户互动"
],
"summary": "清空所有赞",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/like/toggle": {
"post": {
"produces": [
@@ -1880,6 +1929,34 @@ const docTemplate = `{
}
}
},
"/radio/program/generate-tts": {
"get": {
"produces": [
"application/json"
],
"tags": [
"节目管理"
],
"summary": "生成TTS语音",
"parameters": [
{
"type": "string",
"description": "节目ID",
"name": "id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/radio/program/list": {
"post": {
"produces": [
@@ -2636,7 +2713,7 @@ const docTemplate = `{
}
},
"/vip/config/detail": {
"get": {
"post": {
"produces": [
"application/json"
],
@@ -2934,6 +3011,23 @@ const docTemplate = `{
}
}
},
"request.GetLikeList": {
"type": "object",
"properties": {
"current": {
"description": "页码",
"type": "integer"
},
"keyword": {
"description": "关键字",
"type": "string"
},
"pageSize": {
"description": "每页大小",
"type": "integer"
}
}
},
"request.GetMenuTree": {
"type": "object",
"properties": {
@@ -2967,9 +3061,6 @@ const docTemplate = `{
},
"request.GetProgramList": {
"type": "object",
"required": [
"channelId"
],
"properties": {
"channelId": {
"description": "频道ID",
+95 -4
View File
@@ -806,6 +806,55 @@
}
}
},
"/like/list": {
"post": {
"produces": [
"application/json"
],
"tags": [
"用户互动"
],
"summary": "获取收藏列表",
"parameters": [
{
"description": "分页查询",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.GetLikeList"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/like/removeAll": {
"get": {
"produces": [
"application/json"
],
"tags": [
"用户互动"
],
"summary": "清空所有赞",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/like/toggle": {
"post": {
"produces": [
@@ -1873,6 +1922,34 @@
}
}
},
"/radio/program/generate-tts": {
"get": {
"produces": [
"application/json"
],
"tags": [
"节目管理"
],
"summary": "生成TTS语音",
"parameters": [
{
"type": "string",
"description": "节目ID",
"name": "id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/radio/program/list": {
"post": {
"produces": [
@@ -2629,7 +2706,7 @@
}
},
"/vip/config/detail": {
"get": {
"post": {
"produces": [
"application/json"
],
@@ -2927,6 +3004,23 @@
}
}
},
"request.GetLikeList": {
"type": "object",
"properties": {
"current": {
"description": "页码",
"type": "integer"
},
"keyword": {
"description": "关键字",
"type": "string"
},
"pageSize": {
"description": "每页大小",
"type": "integer"
}
}
},
"request.GetMenuTree": {
"type": "object",
"properties": {
@@ -2960,9 +3054,6 @@
},
"request.GetProgramList": {
"type": "object",
"required": [
"channelId"
],
"properties": {
"channelId": {
"description": "频道ID",
+62 -3
View File
@@ -152,6 +152,18 @@ definitions:
description: 每页大小
type: integer
type: object
request.GetLikeList:
properties:
current:
description: 页码
type: integer
keyword:
description: 关键字
type: string
pageSize:
description: 每页大小
type: integer
type: object
request.GetMenuTree:
properties:
category:
@@ -193,8 +205,6 @@ definitions:
title:
description: 节目标题
type: string
required:
- channelId
type: object
request.GetRoleList:
properties:
@@ -1179,6 +1189,37 @@ paths:
summary: 获取收听历史列表
tags:
- 用户互动
/like/list:
post:
parameters:
- description: 分页查询
in: body
name: data
required: true
schema:
$ref: '#/definitions/request.GetLikeList'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
summary: 获取收藏列表
tags:
- 用户互动
/like/removeAll:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
summary: 清空所有赞
tags:
- 用户互动
/like/toggle:
post:
parameters:
@@ -1816,6 +1857,24 @@ paths:
summary: 获取节目详情
tags:
- 节目管理
/radio/program/generate-tts:
get:
parameters:
- description: 节目ID
in: query
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
summary: 生成TTS语音
tags:
- 节目管理
/radio/program/list:
post:
parameters:
@@ -2267,7 +2326,7 @@ paths:
tags:
- 用户管理
/vip/config/detail:
get:
post:
parameters:
- description: id
in: query
+1
View File
@@ -9,6 +9,7 @@ type RadioFavorite struct {
global.BaseModel
UserId string `gorm:"size:50;index" json:"userId"` // 用户ID
ProgramId string `gorm:"size:50;index" json:"programId"` // 节目ID
RadioProgram *RadioProgram `gorm:"foreignKey:ProgramId" json:"program"`
}
func (RadioFavorite) TableName() string {
+1
View File
@@ -9,6 +9,7 @@ type RadioLike struct {
global.BaseModel
UserId string `gorm:"size:50;index" json:"userId"` // 用户ID
ProgramId string `gorm:"size:50;index" json:"programId"` // 节目ID
RadioProgram *RadioProgram `gorm:"foreignKey:ProgramId" json:"program"`
}
func (RadioLike) TableName() string {
+1 -1
View File
@@ -22,7 +22,7 @@ type RadioProgram struct {
Status int `gorm:"default:1" json:"status"` // 状态 0:下架 1:上架
Channel *RadioChannel `gorm:"foreignKey:ChannelId" json:"channel"`
HasLiked int `gorm:"-" json:"hasLiked"` // 是否点赞
HasFavorite int `gorm:"-" json:"HasFavorite"` // 是否收藏
HasFavorite int `gorm:"-" json:"hasFavorite"` // 是否收藏
}
func (RadioProgram) TableName() string {
+5
View File
@@ -55,6 +55,11 @@ type GetHistoryList struct {
common.PageInfo
}
// GetLikeList 获取点赞列表请求
type GetLikeList struct {
common.PageInfo
}
// GetFavoriteList 获取收藏列表请求
type GetFavoriteList struct {
common.PageInfo
+2
View File
@@ -20,6 +20,8 @@ func (r *InteractionRouter) InitInteractionRouter(Router *gin.RouterGroup) {
likeRouter := Router.Group("like")
{
likeRouter.POST("toggle", interactionApi.ToggleLike)
likeRouter.POST("list", interactionApi.GetLikeList)
likeRouter.GET("removeAll", interactionApi.RemoveAllLike)
}
// 收藏
+21 -1
View File
@@ -91,6 +91,22 @@ func (s *InteractionService) ToggleLike(userId, programId string) (bool, error)
return false, nil
}
// GetLikeList 获取点赞列表
func (s *InteractionService) GetLikeList(userId string, req radioReq.GetLikeList) ([]radio.RadioLike, int64, error) {
db := global.DB.Model(&radio.RadioLike{}).Where("user_id = ?", userId).Preload("RadioProgram")
var list []radio.RadioLike
var total int64
err := db.Count(&total).Error
if err != nil {
return nil, 0, err
}
offset := (req.Current - 1) * req.PageSize
err = db.Offset(offset).Limit(req.PageSize).Order("created_at DESC").Find(&list).Error
return list, total, err
}
// IsLiked 检查是否已点赞
func (s *InteractionService) IsLiked(userId, programId string) (bool, error) {
var count int64
@@ -124,7 +140,7 @@ func (s *InteractionService) RemoveFavorite(userId, programId string) error {
// GetFavoriteList 获取收藏列表
func (s *InteractionService) GetFavoriteList(userId string, info radioReq.GetFavoriteList) ([]radio.RadioFavorite, int64, error) {
db := global.DB.Model(&radio.RadioFavorite{}).Where("user_id = ?", userId)
db := global.DB.Model(&radio.RadioFavorite{}).Where("user_id = ?", userId).Preload("RadioProgram")
var list []radio.RadioFavorite
var total int64
@@ -188,3 +204,7 @@ func (s *InteractionService) DeleteAllHistory(userId string) error {
func (s *InteractionService) RemoveAllFavorite(userId string) error {
return global.DB.Where("user_id = ?", userId).Delete(&radio.RadioFavorite{}).Error
}
func (s *InteractionService) RemoveAllLike(userId string) error {
return global.DB.Where("user_id = ?", userId).Delete(&radio.RadioLike{}).Error
}
+2 -1
View File
@@ -128,7 +128,8 @@ func (s *ProgramService) GenerateTTS(programId string) error {
// 2. 调用TTS提交任务 (异步,后台处理)
ttsReq := TTSTextToSpeechRequest{
Text: program.Content,
VoiceType: 101021, // 亲和女声
//VoiceType: 101001, // 智瑜 情感女声
VoiceType: 101013, // 智辉 新闻男声
Speed: 0, // 正常语速
Volume: 0, // 正常音量
ProgramId: programId,
+5 -3
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"time"
"sundynix-go/global"
@@ -160,7 +161,7 @@ func (t *TTSService) doSubmitTTSTask(req TTSTextToSpeechRequest) (string, error)
// waitForResult 轮询查询任务结果,返回音频下载URL
func (t *TTSService) waitForResult(taskId string) (string, error) {
maxRetries := 30
interval := 5 * time.Second
interval := 15 * time.Second
for i := 0; i < maxRetries; i++ {
time.Sleep(interval)
@@ -256,8 +257,9 @@ func (t *TTSService) uploadToOSS(audioData []byte, programId string) (string, er
if !ok {
return "", fmt.Errorf("获取MinIO客户端失败")
}
key := fmt.Sprintf("audio/%s/%s.mp3", time.Now().Format("2006-01-02"), programId)
timestamp := time.Now().UnixMicro()
timestr := strconv.FormatInt(timestamp, 10)
key := fmt.Sprintf("audio/%s/%s.mp3", time.Now().Format("2006-01-02"), programId+"-"+timestr)
filename := fmt.Sprintf("program-%s.mp3", programId)
fileURL, err := minioClient.UploadBytes(audioData, key, "audio/mpeg")