作者 yangfu

tts

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