作者 yangfu

tts

... ... @@ -7,6 +7,10 @@
@doc "日志查询"
@handler commonGetLog
get /log/:module
@doc "文件服务"
@handler commonGetFile
get /file/:module
}
// 通用接口
... ... @@ -20,6 +24,10 @@
@handler commonSmsCode
post /common/sms/code (CommonSmsCodeRequest) returns (CommonSmsCodeResposne)
@doc "短信验证码"
@handler commonTextToSpeech
post /common/tts (TextToSpeechRequest) returns (TextToSpeechResponse)
@doc "微信二维码"
@handler miniQrcodeInvite
post /mini/qrcode (MiniQrCodeRequest)
... ... @@ -44,4 +52,13 @@
Page string `json:"page"` // 微信页面入口
Scene string `json:"scene"` // 参数
}
)
type(
TextToSpeechRequest{
Text string `json:"text"` // 文本信息
}
TextToSpeechResponse{
AudioUrl string `json:"audioUrl"` // 音频文件地址
}
)
\ No newline at end of file
... ...
... ... @@ -40,4 +40,15 @@ ApiAuth:
Wechat:
AppID: wxae5b305849343ec8
AppSecret: f584adb68f7d784425b60e1ebb2ffd4b
QrcodeEnv: trial
\ No newline at end of file
QrcodeEnv: trial
TTS:
ReginID: cn-shanghai #ap-southeast-1
AccessKeyID: LTAI4Fz1LUBW2fXp6QWaJHRS
AccessKeySecret: aLZXwK8pgrs10Ws03qcN7NsrSXFVsg
Domain: nls-meta.cn-shanghai.aliyuncs.com #nlsmeta.cn-shenzhen.aliyuncs.com #
AppKey: hRAovF4pNBhKJdFG
Voice: xiaoyun
Volume: 50
SpeechRate: 0
PitchRate: 0
\ No newline at end of file
... ...
... ... @@ -18,6 +18,7 @@ type Config struct {
DebugSmsCode string `json:",optional,default=999512"`
LogRequest bool `json:",optional,default=true"`
ContentSecurityCheck bool `json:",optional,default=false"`
TTS TTS `json:",optional"`
}
type ApiService struct {
... ... @@ -25,3 +26,16 @@ type ApiService struct {
Host string
Timeout time.Duration
}
// TTS text to speech
type TTS struct {
ReginID string
AccessKeyID string
AccessKeySecret string
Domain string
AppKey string
Voice string `json:",default=xiaoyun"` //发音人
Volume int `json:",default=50"` // 音量,范围是0~100,可选,默认50。
SpeechRate int `json:",default=0"` //语速,范围是-500~500,可选,默认是0
PitchRate int `json:",default=0"` //语调,范围是-500~500,可选,默认是0
}
... ...
package common
import (
"github.com/zeromicro/go-zero/rest/httpx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/svc"
"net/http"
"path/filepath"
)
func CommonGetFileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Module string `path:"module"`
}
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
path := "public"
handler := http.FileServer(http.Dir(path))
r.URL.Path = filepath.Join(req.Module)
handler.ServeHTTP(w, r)
}
}
... ...
package common
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/logic/common"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/types"
)
func CommonTextToSpeechHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.TextToSpeechRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := common.NewCommonTextToSpeechLogic(r.Context(), svcCtx)
resp, err := l.CommonTextToSpeech(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
... ...
... ... @@ -19,6 +19,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/log/:module",
Handler: common.CommonGetLogHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/file/:module",
Handler: common.CommonGetFileHandler(serverCtx),
},
},
rest.WithPrefix("/v1"),
)
... ... @@ -34,6 +39,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
},
{
Method: http.MethodPost,
Path: "/common/tts",
Handler: common.CommonTextToSpeechHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/mini/qrcode",
Handler: common.MiniQrcodeInviteHandler(serverCtx),
},
... ...
package common
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/svc"
)
type CommonGetFileLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCommonGetFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommonGetFileLogic {
return &CommonGetFileLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CommonGetFileLogic) CommonGetFile() error {
// todo: add your logic here and delete this line
return nil
}
... ...
package common
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
nls "github.com/aliyun/alibabacloud-nls-go-sdk"
"github.com/google/uuid"
"github.com/zeromicro/go-zero/core/logx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/cmd/bsi/api/internal/types"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc/pkg/xerr"
"io/ioutil"
"net/http"
"os"
"path"
"strconv"
)
type CommonTextToSpeechLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCommonTextToSpeechLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommonTextToSpeechLogic {
return &CommonTextToSpeechLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CommonTextToSpeechLogic) CommonTextToSpeech(req *types.TextToSpeechRequest) (resp *types.TextToSpeechResponse, err error) {
token, err := l.getToken1()
if err != nil {
return nil, xerr.NewErrMsgErr("授权失败", err)
}
id, _ := uuid.NewUUID()
file := fmt.Sprintf("%s.wav", id.String())
filename := path.Join("public", file)
if err = l.processPOSTRequest(token, req.Text, filename, "wav", 16000); err != nil {
return nil, xerr.NewErrMsgErr("TTS转换失败", err)
}
url := path.Join("file", file)
resp = &types.TextToSpeechResponse{
AudioUrl: url,
}
return
}
func (l *CommonTextToSpeechLogic) getToken() (string, error) {
client, err := sdk.NewClientWithAccessKey(l.svcCtx.Config.TTS.ReginID, l.svcCtx.Config.TTS.AccessKeyID, l.svcCtx.Config.TTS.AccessKeySecret)
if err != nil {
return "", xerr.NewErrMsgErr("授权失败", err)
}
request := requests.NewCommonRequest()
request.Method = "POST"
request.Domain = l.svcCtx.Config.TTS.Domain //"nlsmeta.ap-southeast-1.aliyuncs.com"
request.ApiName = "CreateToken"
request.Version = "2019-02-28"
response, err := client.ProcessCommonRequest(request)
if err != nil {
return "", xerr.NewErrMsgErr("授权失败", err)
}
token := response.GetHttpContentString()
return token, nil
}
func (l *CommonTextToSpeechLogic) getToken1() (string, error) {
tts := l.svcCtx.Config.TTS
client, err := nls.GetToken(tts.ReginID, tts.Domain, tts.AccessKeyID, tts.AccessKeySecret, "2019-02-28")
if err != nil {
return "", xerr.NewErrMsgErr("授权失败", err)
}
token := client.TokenResult.Id
return token, nil
}
func (l *CommonTextToSpeechLogic) processPOSTRequest(token string, text string, audioSaveFile string, format string, sampleRate int) error {
/**
* 设置HTTPS POST请求:
* 1.使用HTTPS协议
* 2.语音合成服务域名:nls-gateway-ap-southeast-1.aliyuncs.com
* 3.语音合成接口请求路径:/stream/v1/tts
* 4.设置必须请求参数:appkey、token、text、format、sample_rate
* 5.设置可选请求参数:voice、volume、speech_rate、pitch_rate
*/
ttsConfig := l.svcCtx.Config.TTS
var url string = fmt.Sprintf("https://%s/stream/v1/tts", "nls-gateway-ap-southeast-1.aliyuncs.com")
bodyContent := make(map[string]interface{})
bodyContent["appkey"] = ttsConfig.AppKey
bodyContent["text"] = text
bodyContent["token"] = token
bodyContent["format"] = format
bodyContent["sample_rate"] = sampleRate
// voice 发音人,可选,默认是xiaoyun。
bodyContent["voice"] = ttsConfig.Voice
// volume 音量,范围是0~100,可选,默认50。
bodyContent["volume"] = ttsConfig.Volume
// speech_rate 语速,范围是-500~500,可选,默认是0。
bodyContent["speech_rate"] = ttsConfig.SpeechRate
// pitch_rate 语调,范围是-500~500,可选,默认是0。
bodyContent["pitch_rate"] = ttsConfig.PitchRate
bodyJson, err := json.Marshal(bodyContent)
if err != nil {
panic(nil)
}
fmt.Println(string(bodyJson))
/**
* 发送HTTPS POST请求,处理服务端的响应。
*/
response, err := http.Post(url, "application/json;charset=utf-8", bytes.NewBuffer([]byte(bodyJson)))
if err != nil {
return err
}
defer response.Body.Close()
contentType := response.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(response.Body)
var file *os.File
if "audio/mpeg" == contentType {
file, err = os.Create(audioSaveFile)
if err != nil {
return err
}
defer file.Close()
file.Write([]byte(body))
//fmt.Println("The POST request succeed!")
} else {
// ContentType 为 null 或者为 "application/json"
statusCode := response.StatusCode
fmt.Println("The HTTP statusCode: " + strconv.Itoa(statusCode))
fmt.Println("The POST request failed: " + string(body))
return xerr.NewErrMsgErr("The POST request failed: "+string(body), nil)
}
return nil
}
... ...
... ... @@ -13,6 +13,14 @@ type MiniQrCodeRequest struct {
Scene string `json:"scene"` // 参数
}
type TextToSpeechRequest struct {
Text string `json:"text"` // 文本信息
}
type TextToSpeechResponse struct {
AudioUrl string `json:"audioUrl"` // 音频文件地址
}
type ActivityOpenRequest struct {
Id int64 `json:"id"` // 唯一标识
}
... ...
... ... @@ -20,6 +20,8 @@ require (
require (
github.com/Shopify/sarama v1.37.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.62.708 // indirect
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 // indirect
github.com/beego/beego/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
... ... @@ -46,6 +48,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
... ... @@ -60,6 +63,7 @@ require (
github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/leodido/go-urn v1.1.0 // indirect
... ... @@ -72,6 +76,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/onsi/gomega v1.26.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/openzipkin/zipkin-go v0.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
... ... @@ -85,6 +90,7 @@ require (
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.1 // indirect
github.com/samber/lo v1.39.0 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
... ... @@ -119,6 +125,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
... ...