本节分享给大家通过调用腾讯云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() +" " + "host:" + host + " "; String signedHeaders = "content-type;host"; String hashedRequestPayload = sha256Hex(jsonString); String canonicalRequest = menth.getName() + " " + canonicalUri + " " + canonicalQueryString + " " + canonicalHeaders + " " + signedHeaders + " " + hashedRequestPayload; // ************* 步骤 2:拼接待签名字符串 ************* String credentialScope = date + "/" + service + "/" + "tc3_request"; String hashedCanonicalRequest = sha256Hex(canonicalRequest); String stringToSign = signMenodEnum.getMendoName() + " " + params.get("Timestamp") + " " + credentialScope + " " + 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/