本节分享给大家通过调用腾讯云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/