本節分享給大家通過調用騰訊雲API實現語音合成技術
package com.example.combat.controller; import com.example.combat.service.TTSService; import com.example.combat.ttsutis.param.TextToVoice; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; /** * @description: 語音合成 * @author:zhucj * @date: 2019-11-27 16:37 */ @RestController @RequestMapping("/tts") public class TTSControllerl { @Autowired private TTSService ttsService; @ApiOperation(value = "語音合成") @ApiImplicitParams({ @ApiImplicitParam(name = "text",value = "合成語音的源文本",required = true,dataType = "String"), @ApiImplicitParam(name = "volume",value = "音量大小,范圍:[0,10]",required = true,dataType = "String"), @ApiImplicitParam(name = "speed",value = "語速,范圍:[-2,2]",required = true,dataType = "String"), @ApiImplicitParam(name = "voiceType",value = "音色",required = true,dataType = "Integer") }) @PostMapping(value = "textToVoice") public void textToVoice(@Valid @RequestBody TextToVoice textToVoice, HttpServletResponse response){ ttsService.voiceSynthesis(textToVoice,response); } }
package com.example.combat.service; import com.example.combat.ttsutis.R; import com.example.combat.ttsutis.param.TextToVoice; import javax.servlet.http.HttpServletResponse; /** * @description: 語音合成實現類 * @author: zhucj * @date: 2019-11-27 16:12 */ public interface TTSService { /** * 語音合成 * @param textToVoice */ void voiceSynthesis(TextToVoice textToVoice, HttpServletResponse response); }
package com.example.combat.service.Impl; import cn.hutool.core.io.FileUtil; import com.example.combat.afsutils.Base64ConvertUtils; import com.example.combat.gaodemapUtils.SystemConstant; import com.example.combat.service.TTSService; import com.example.combat.ttsutis.R; import com.example.combat.ttsutis.TTSUtil; import com.example.combat.ttsutis.param.TextToVoice; import com.example.combat.ttsutis.param.TextToVoiceResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import sun.misc.BASE64Decoder; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.Objects; /** * @description: * @author: zhucj * @date: 2019-11-27 16:15 */ @Service @Slf4j public class TTSServiceImpl implements TTSService { @Autowired private TTSUtil ttsUtil; @Value("${tencent.pathImg}") private String pathImg; @Override public void voiceSynthesis(TextToVoice textToVoice, HttpServletResponse response) { R r = ttsUtil.voiceSynthesis(textToVoice); if (r.getSuccess() && Objects.equals(r.getCode(), SystemConstant.SUCCESS_CODE)){ TextToVoiceResponse data =(TextToVoiceResponse) r.getData(); String audio = data.getAudio(); //將base64編碼的wav/mp3音頻數據 下載給前端 exportImg(response,pathImg,audio); }else { throw new RuntimeException("語音合成失敗"); } } public static void exportImg(HttpServletResponse response, String filePath,String imgStr) { //對字節數組字符串進行Base64解碼並生成圖片 String imgFilePath = null; //圖像數據為空 if (imgStr == null){ return ; } BASE64Decoder decoder = new BASE64Decoder(); BufferedOutputStream buff = null; ServletOutputStream outStr = null; try { //Base64解碼 byte[] b = decoder.decodeBuffer(imgStr); for (int i = 0; i < b.length; ++i) { if (b[i] < 0) { //調整異常數據 b[i] += 256; } } response.setCharacterEncoding("utf-8"); //設置響應的內容類型 response.setContentType("text/plain"); //設置文件的名稱和格式 response.setHeader("content-type", "application/octet-stream"); response.setContentType("application/octet-stream"); response.addHeader("Content-Disposition", "attachment;filename=" + "合成語音.wav"); outStr = response.getOutputStream(); buff = new BufferedOutputStream(outStr); buff.write(b); buff.flush(); buff.close(); } catch (Exception e) { e.printStackTrace(); log.error("文件導出異常:", e); } finally { try { buff.close(); } catch (Exception e) { log.error("流關閉異常:", e); } try { outStr.close(); } catch (Exception e) { log.error("流關閉異常:", e); } } } }
package com.example.combat.ttsutis; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.example.combat.afsutils.HttpUtil; import com.example.combat.afsutils.SignUtils; import com.example.combat.asrutils.param.SentenceRecognitionApi; import com.example.combat.asrutils.param.SentenceResponse; import com.example.combat.asrutils.param.SystemConstants; import com.example.combat.config.constant.ContentTypeEnum; import com.example.combat.config.constant.HttpMethodEnum; import com.example.combat.config.constant.SignMenodEnum; import com.example.combat.ttsutis.param.TextToVoice; import com.example.combat.ttsutis.param.TextToVoiceResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; /** * @description: 語音合成 * @author: zhucj * @date: 2019-11-27 15:35 */ @Component @Slf4j public class TTSUtil { @Value("${tencent.secretId}") private String sercretId; @Value("${tencent.secretKey}") private String sercretKey; /** * 語音合成 * @param textToVoice * @return */ public R voiceSynthesis(TextToVoice textToVoice){ TreeMap treeMap = createPublicMap("TextToVoice", "2019-08-23", "ap-shenzhen-fsi"); HashMap<String,Object> hashMap = new HashMap<>(); try { hashMap.put("Text", URLEncoder.encode(textToVoice.getText(),"UTF-8")); } catch (UnsupportedEncodingException e) { log.error("URL Encoder異常:{}",e.getMessage()); return R.error("語音合成失敗").setCode(SystemConstants.SERVER_ERROR_CODE); } hashMap.put("SessionId", UUID.randomUUID().toString()); hashMap.put("ModelType",1); hashMap.put("Volume",Float.valueOf(textToVoice.getVolume())); hashMap.put("Speed",Float.valueOf(textToVoice.getSpeed())); hashMap.put("VoiceType",textToVoice.getVoiceType()); //簽名,公共參數不需要放到body中 String sign = null; try { sign = SignUtils.sign(treeMap, HttpMethodEnum.POST, SignMenodEnum.TC3_HMAC_SHA256, JSON.toJSONString(hashMap) , TextToVoiceConstant.TEXT_TO_VOICE, sercretKey, ContentTypeEnum.JSON); } catch (Exception e) { log.error("簽名異常:{}",e.getMessage()); return R.error("簽名異常").setCode(SystemConstants.SERVER_ERROR_CODE); } try { String respJson = HttpUtil.httpPost(TextToVoiceConstant.TEXT_TO_VOICE, JSON.parseObject(sign, Map.class),hashMap); JSONObject jsonObject = JSON.parseObject(respJson); String response = jsonObject.getString("Response"); JSONObject error =(JSONObject) JSON.parseObject(response).get("Error"); if (Objects.nonNull(error)){ return R.error(String.valueOf(error.get("Message"))).setCode(SystemConstants.SERVER_ERROR_CODE); }else { TextToVoiceResponse textToVoiceResponse = JSON.parseObject(response, TextToVoiceResponse.class); return R.ok(textToVoiceResponse.getAudio()).setCode(SystemConstants.SUCCESS_CODE); } } catch (Exception e) { log.error("語音合成失敗:{}",e.getMessage()); return R.error("語音合成失敗").setCode(SystemConstants.SERVER_ERROR_CODE); } } /** * 封裝請求公共參數 * @param action * @param version * @return */ public TreeMap createPublicMap(String action, String version,String region ){ TreeMap<String,Object> treeMap = new TreeMap<>(); treeMap.put("Action",action); treeMap.put("Version",version); treeMap.put("Region",region); treeMap.put("Timestamp",getCurrentTimestamp()); treeMap.put("Nonce",new Random().nextInt(Integer.MAX_VALUE)); treeMap.put("SecretId",sercretId); return treeMap; } /** * 獲取當前時間戳,單位秒 * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis()/1000; } }
package com.example.combat.ttsutis; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.ToString; import java.io.Serializable; /** * 返回類型 * @author choleece * @date 2018/9/27 */ @ApiModel @ToString public class R<T> implements Serializable { private static final long serialVersionUID = -6287952131441663819L; /** * 編碼 */ @ApiModelProperty(value = "響應碼", example = "200") private int code = 200; /** * 成功標志 */ @ApiModelProperty(value = "成功標志", example = "true") private Boolean success; /** * 返回消息 */ @ApiModelProperty(value = "返回消息說明", example = "操作成功") private String msg="操作成功"; /** * 返回數據 */ @ApiModelProperty(value = "返回數據") private T data; /** * 創建實例 * @return */ public static R instance() { return new R(); } public int getCode() { return code; } public R setCode(int code) { this.code = code; return this; } public Boolean getSuccess() { return success; } public R setSuccess(Boolean success) { this.success = success; return this; } public String getMsg() { return msg; } public R setMsg(String msg) { this.msg = msg; return this; } public T getData() { return data; } public R setData(T data) { this.data = data; return this; } public static R ok() { return R.instance().setSuccess(true); } public static R ok(Object data) { return ok().setData(data); } public static R ok(Object data, String msg) { return ok(data).setMsg(msg); } public static R error() { return R.instance().setSuccess(false); } public static R error(String msg) { return error().setMsg(msg); } /** * 無參 */ public R() { } public R(int code, String msg) { this.code = code; this.msg = msg; } public R(int code, T data){ this.code = code; this.data = data; } /** * 有全參 * @param code * @param msg * @param data * @param success */ public R(int code, String msg, T data, Boolean success) { this.code = code; this.msg = msg; this.data = data; this.success = success; } /** * 有參 * @param code * @param msg * @param data */ public R(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
package com.example.combat.ttsutis; /** * @description: 語音合成常量 * @author: zhucj * @date: 2019-11-27 15:51 */ public class TextToVoiceConstant { /** * 語音合成Api */ public static final String TEXT_TO_VOICE = "https://tts.ap-shenzhen-fsi.tencentcloudapi.com"; }
package com.example.combat.ttsutis.param; import io.swagger.annotations.ApiModel; import lombok.*; import javax.validation.constraints.NotNull; /** * @description: * @author: zhucj * @date: 2019-11-27 15:39 */ @Data @ApiModel(description = "語音合成請求實體") @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class TextToVoice { /** * 合成語音的源文本,按UTF-8編碼統一計算。 * 中文最大支持100個漢字(全角標點符號算一個漢字); * 英文最大支持400個字母(半角標點符號算一個字母)。包含空格等字符時需要url encode再傳輸。 */ @NotNull(message = "合成語音的源文本不為空") private String text; /** * 音量大小,范圍:[0,10],分別對應11個等級的音量,默認為0 */ @NotNull(message = "音量大小不為空") private String volume; /** *語速,范圍:[-2,2],分別對應不同語速: * -2代表0.6倍 * -1代表0.8倍 * 0代表1.0倍(默認) * 1代表1.2倍 * 2代表1.5倍 * 輸入除以上整數之外的其他參數不生效,按默認值處理。 */ @NotNull(message = "語速大小不為空") private String speed; /** * 音色 * 0-親和女聲(默認) * 1-親和男聲 * 2-成熟男聲 * 4-溫暖女聲 * 5-情感女聲 * 6-情感男聲 */ @NotNull(message = "音色選擇不為空") private Integer voiceType; }
package com.example.combat.ttsutis.param; import com.example.combat.afsutils.param.resp.Response; import io.swagger.annotations.ApiModel; import lombok.*; /** * @description: * @author: zhucj * @date: 2019-11-27 16:09 */ @Data @ApiModel(description = "語音合成響應實體") @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class TextToVoiceResponse extends Response { private String Audio; private String SessionId; }
package com.example.combat.afsutils; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; @Slf4j public class HttpUtil { public static final ContentType TEXT_PLAIN = ContentType.create("text/plain", StandardCharsets.UTF_8); /** * HttpClient 連接池 */ private static PoolingHttpClientConnectionManager cm = null; static { // 初始化連接池,可用於請求HTTP/HTTPS(信任所有證書) cm = new PoolingHttpClientConnectionManager(getRegistry()); // 整個連接池最大連接數 cm.setMaxTotal(200); // 每路由最大連接數,默認值是2 cm.setDefaultMaxPerRoute(5); } /** * 發送 HTTP GET請求 * <p>不帶請求參數和請求頭</p> * @param url 地址 * @return * @throws Exception */ public static String httpGet(String url) throws Exception { log.info("請求參數:{}",url); HttpGet httpGet = new HttpGet(url); return doHttp(httpGet); } /** * 發送 HTTP GET請求 * <p>帶請求參數,不帶請求頭</p> * @param url 地址 * @param params 參數 * @return * @throws Exception * @throws Exception */ public static String httpGet(String url, Map<String, Object> params) throws Exception { // 轉換請求參數 List<NameValuePair> pairs = covertParams2NVPS(params); // 裝載請求地址和參數 URIBuilder ub = new URIBuilder(); ub.setPath(url); ub.setParameters(pairs); HttpGet httpGet = new HttpGet(ub.build()); return doHttp(httpGet); } /** * 發送 HTTP GET請求 * <p>帶請求參數和請求頭</p> * @param url 地址 * @param headers 請求頭 * @param params 參數 * @return * @throws Exception * @throws Exception */ public static String httpGet(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception { // 轉換請求參數 List<NameValuePair> pairs = covertParams2NVPS(params); // 裝載請求地址和參數 URIBuilder ub = new URIBuilder(); ub.setPath(url); ub.setParameters(pairs); HttpGet httpGet = new HttpGet(ub.build()); // 設置請求頭 for (Map.Entry<String, Object> param : headers.entrySet()){ httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));} return doHttp(httpGet); } /** * 發送 HTTP POST請求 * <p>不帶請求參數和請求頭</p> * * @param url 地址 * @return * @throws Exception */ public static String httpPost(String url) throws Exception { HttpPost httpPost = new HttpPost(url); return doHttp(httpPost); } /** * 發送 HTTP POST請求 * <p>帶請求參數,不帶請求頭</p> * * @param url 地址 * @param params 參數 * @return * @throws Exception */ public static String httpPost(String url, Map<String, Object> params) throws Exception { // 轉換請求參數 List<NameValuePair> pairs = covertParams2NVPS(params); HttpPost httpPost = new HttpPost(url); // 設置請求參數 httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name())); return doHttp(httpPost); } /** * 發送 HTTP POST請求 * <p>帶請求參數和請求頭</p> * * @param url 地址 * @param headers 請求頭 * @param params 參數 * @return * @throws Exception */ public static String httpPost(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception { log.info("POST請求參數:{}",params); HttpPost httpPost = new HttpPost(url); // 設置請求參數 StringEntity entity = new StringEntity(JSON.toJSONString(params),ContentType.APPLICATION_JSON); httpPost.setEntity(entity); // 設置請求頭 for (Map.Entry<String, Object> param : headers.entrySet()){ httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));} return doHttp(httpPost); } /** * 轉換請求參數 * * @param params * @return */ public static List<NameValuePair> covertParams2NVPS(Map<String, Object> params) { List<NameValuePair> pairs = new ArrayList<NameValuePair>(); for (Map.Entry<String, Object> param : params.entrySet()){ pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));} return pairs; } /** * 發送 HTTP 請求 * * @param request * @return * @throws Exception */ private static String doHttp(HttpRequestBase request) throws Exception { // 通過連接池獲取連接對象 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); return doRequest(httpClient, request); } /** * 發送 HTTPS 請求 * <p>使用指定的證書文件及密碼</p> * * @param request * @param path * @param password * @return * @throws Exception * @throws Exception */ private static String doHttps(HttpRequestBase request, String path, String password) throws Exception { // 獲取HTTPS SSL證書 SSLConnectionSocketFactory csf = getSSLFactory(path, password); // 通過連接池獲取連接對象 CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); return doRequest(httpClient, request); } /** * 獲取HTTPS SSL連接工廠 * <p>使用指定的證書文件及密碼</p> * * @param path 證書全路徑 * @param password 證書密碼 * @return * @throws Exception * @throws Exception */ private static SSLConnectionSocketFactory getSSLFactory(String path, String password) throws Exception { // 初始化證書,指定證書類型為“PKCS12” KeyStore keyStore = KeyStore.getInstance("PKCS12"); // 讀取指定路徑的證書 FileInputStream input = new FileInputStream(new File(path)); try { // 裝載讀取到的證書,並指定證書密碼 keyStore.load(input, password.toCharArray()); } finally { input.close(); } // 獲取HTTPS SSL證書連接上下文 SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build(); // 獲取HTTPS連接工廠,指定TSL版本 SSLConnectionSocketFactory sslCsf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); return sslCsf; } /** * 獲取HTTPS SSL連接工廠 * <p>跳過證書校驗,即信任所有證書</p> * * @return * @throws Exception */ private static SSLConnectionSocketFactory getSSLFactory() throws Exception { // 設置HTTPS SSL證書信息,跳過證書校驗,即信任所有證書請求HTTPS SSLContextBuilder sslBuilder = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }); // 獲取HTTPS SSL證書連接上下文 SSLContext sslContext = sslBuilder.build(); // 獲取HTTPS連接工廠 SSLConnectionSocketFactory sslCsf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE); return sslCsf; } /** * 獲取 HTTPClient注冊器 * * @return * @throws Exception */ private static Registry<ConnectionSocketFactory> getRegistry() { Registry<ConnectionSocketFactory> registry = null; try { registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", getSSLFactory()).build(); } catch (Exception e) { log.error("獲取 HTTPClient注冊器失敗", e); } return registry; } /** * 處理Http/Https請求,並返回請求結果 * <p>注:默認請求編碼方式 UTF-8</p> * * @param httpClient * @param request * @return * @throws Exception */ private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception { String result = null; CloseableHttpResponse response = null; try { // 獲取請求結果 response = httpClient.execute(request); // 解析請求結果 HttpEntity entity = response.getEntity(); // 轉換結果 result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name()); // 關閉IO流 EntityUtils.consume(entity); } finally { if (null != response){ response.close();} } return result; } }
package com.example.combat.afsutils; import com.alibaba.fastjson.JSON; import com.example.combat.config.constant.ContentTypeEnum; import com.example.combat.config.constant.HttpMethodEnum; import com.example.combat.config.constant.SignMenodEnum; import lombok.extern.slf4j.Slf4j; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; import java.util.TimeZone; import java.util.TreeMap; /** * @description: 騰訊雲 簽名方法 * @author: zhucj * @date: 2019-10-18 14:14 */ @Slf4j public class SignUtils { private final static String CHARSET = "UTF-8"; private final static Charset UTF8 = StandardCharsets.UTF_8; public static String sign(TreeMap<String, Object> params, HttpMethodEnum menth, SignMenodEnum signMenodEnum, String jsonString, String reqUrl, String sercretKey, ContentTypeEnum typeEnum) throws Exception { String signString = null; String sercretId = String.valueOf(params.get("SecretId")); switch (signMenodEnum){ case TC3_HMAC_SHA256: String replace = reqUrl.replace("https://", ""); String service = replace.substring(0,3); String host = replace; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 注意時區,否則容易出錯 sdf.setTimeZone(TimeZone.getTimeZone("UTC")); String date = sdf.format(new Date(Long.valueOf(params.get("Timestamp") + "000"))); // ************* 步驟 1:拼接規范請求串 ************* String canonicalUri = "/"; String canonicalQueryString = ""; String canonicalHeaders = "content-type:"+typeEnum.getName() +"\n" + "host:" + host + "\n"; String signedHeaders = "content-type;host"; String hashedRequestPayload = sha256Hex(jsonString); String canonicalRequest = menth.getName() + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; // ************* 步驟 2:拼接待簽名字符串 ************* String credentialScope = date + "/" + service + "/" + "tc3_request"; String hashedCanonicalRequest = sha256Hex(canonicalRequest); String stringToSign = signMenodEnum.getMendoName() + "\n" + params.get("Timestamp") + "\n" + credentialScope + "\n" + hashedCanonicalRequest; log.info("待簽名參數:{}",stringToSign); // ************* 步驟 3:計算簽名 ************* byte[] secretDate = hmac256(("TC3" +sercretKey).getBytes(UTF8), date); byte[] secretService = hmac256(secretDate, service); byte[] secretSigning = hmac256(secretService, "tc3_request"); String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase(); log.info("生成簽名參數:{}",signature); // ************* 步驟 4:拼接 Authorization ************* String authorization = signMenodEnum.getMendoName() + " " + "Credential=" + sercretId + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; log.info("生成authorization參數:{}",authorization); TreeMap<String, String> headers = new TreeMap<String, String>(); headers.put("Authorization", authorization); headers.put("Content-Type",typeEnum.getName()); headers.put("Host", host); headers.put("X-TC-Action",String.valueOf(params.get("Action")) ); headers.put("X-TC-Timestamp",String.valueOf(params.get("Timestamp"))); headers.put("X-TC-Version",String.valueOf(params.get("Version"))); if (Objects.nonNull(params.get("Region"))){ headers.put("X-TC-Region",String.valueOf(params.get("Region"))); } signString = JSON.toJSONString(headers); break; default: StringBuilder s2s = new StringBuilder(reqUrl.replace("https://",menth.getName())+"/?"); // 簽名時要求對參數進行字典排序,此處用TreeMap保證順序 for (String k : params.keySet()) { s2s.append(k).append("=").append(params.get(k).toString()).append("&"); } String s = s2s.toString().substring(0, s2s.length() - 1); Mac mac = Mac.getInstance(signMenodEnum.getMendoName()); SecretKeySpec secretKeySpec = new SecretKeySpec(sercretKey.getBytes(CHARSET), mac.getAlgorithm()); mac.init(secretKeySpec); byte[] hash = mac.doFinal(s.getBytes(CHARSET)); signString = DatatypeConverter.printBase64Binary(hash); break; } return signString ; } /** * 獲取簽名之后的請求Url * @param params * @return * @throws UnsupportedEncodingException */ public static String getUrl(TreeMap<String, Object> params,String reqUrl) throws UnsupportedEncodingException { StringBuilder url = new StringBuilder(reqUrl+"/?"); // 實際請求的url中對參數順序沒有要求 for (String k : params.keySet()) { // 需要對請求串進行urlencode,由於key都是英文字母,故此處僅對其value進行urlencode url.append(k).append("=").append(URLEncoder.encode(params.get(k).toString(), CHARSET)).append("&"); } return url.toString().substring(0, url.length() - 1); } public static String getUrl(TreeMap<String, Object> params,String reqUrl,String jsonString,ContentTypeEnum typeEnum){ String replace = reqUrl.replace("https://", ""); StringBuilder sb = new StringBuilder(); sb.append("curl -X POST https://").append(replace) .append(" -H \"Authorization: ").append(params.get("Authorization")).append("\"") .append(" -H \"Content-Type:").append(typeEnum.getName()) .append(" -H \"Host: ").append(replace).append("\"") .append(" -H \"X-TC-Action: ").append(params.get("Action")).append("\"") .append(" -H \"X-TC-Timestamp: ").append(params.get("Timestamp")).append("\"") .append(" -H \"X-TC-Version: ").append(params.get("Version")).append("\""); if (Objects.nonNull(params.get("Region"))){ sb.append(" -H \"X-TC-Region: ").append(params.get("Region")).append("\""); } sb.append(" -d '").append(jsonString).append("'"); return sb.toString(); } public static byte[] hmac256(byte[] key, String msg) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); mac.init(secretKeySpec); return mac.doFinal(msg.getBytes(UTF8)); } public static String sha256Hex(String s) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] d = md.digest(s.getBytes(UTF8)); return DatatypeConverter.printHexBinary(d).toLowerCase(); } }
#騰訊雲服務配置
tencent:
secretId: *******
secretKey: *******
pathImg: E:/upload/