SDKTencentAiVirtualmanServiceImpl.java 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. package com.backendsys.service.SDKService.SDKTencent;
  2. import cn.hutool.core.util.StrUtil;
  3. import cn.hutool.json.JSONObject;
  4. import cn.hutool.json.JSONUtil;
  5. import com.backendsys.aspect.HttpRequestAspect;
  6. import com.backendsys.entity.Ai.Aiivh.*;
  7. import com.backendsys.entity.Ai.Aiivh.AiivhAsset.AiivhAssetQuotaStatisticsDTO;
  8. import com.backendsys.entity.Ai.Aiivh.AiivhMakeBroadcastTask.SpeechParamDTO;
  9. import com.backendsys.entity.Ai.Aiivh.AiivhMakeBroadcastTask.VideoParamAnchorParamDTO;
  10. import com.backendsys.entity.Ai.Aiivh.AiivhMakeBroadcastTask.VideoParamDTO;
  11. import com.backendsys.entity.Ai.Aiivh.AiivhMakeBroadcastTask.VideoParamLogoParamDTO;
  12. import com.backendsys.entity.Tencent.*;
  13. import com.backendsys.enums.IvhQuotaActiveEvent;
  14. import com.backendsys.exception.CustException;
  15. import com.backendsys.mapper.Ai.Aiivh.AiivhQuotaHistoryMapper;
  16. import com.backendsys.mapper.Ai.Aiivh.AiivhQuotaMapper;
  17. import com.backendsys.service.Ai.Aiivh.AiivhAssetService;
  18. import com.backendsys.service.Ai.Aiivh.AiivhMakeVirtualmanTaskService;
  19. import com.backendsys.utils.ListUtil;
  20. import com.backendsys.utils.MapUtil;
  21. import org.redisson.api.*;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.beans.factory.annotation.Value;
  24. import org.springframework.context.annotation.Lazy;
  25. import org.springframework.core.ParameterizedTypeReference;
  26. import org.springframework.http.HttpHeaders;
  27. import org.springframework.http.HttpMethod;
  28. import org.springframework.http.MediaType;
  29. import org.springframework.messaging.handler.annotation.Payload;
  30. import org.springframework.stereotype.Service;
  31. import org.springframework.transaction.annotation.Transactional;
  32. import org.springframework.web.reactive.function.client.WebClient;
  33. import org.springframework.web.util.UriComponentsBuilder;
  34. import java.lang.reflect.Field;
  35. import java.net.URI;
  36. import java.time.LocalDateTime;
  37. import java.time.format.DateTimeFormatter;
  38. import java.util.*;
  39. import java.util.concurrent.TimeUnit;
  40. import java.util.stream.Collectors;
  41. @Service
  42. public class SDKTencentAiVirtualmanServiceImpl implements SDKTencentAiVirtualmanService {
  43. @Lazy
  44. @Autowired
  45. RedissonClient redissonClient;
  46. @Autowired
  47. private HttpRequestAspect httpRequestAspect;
  48. @Autowired
  49. private SDKTencentService sdkTencentService;
  50. @Autowired
  51. private AiivhMakeVirtualmanTaskService aiivhMakeVirtualmanTaskService;
  52. @Autowired
  53. private AiivhAssetService aiivhAssetService;
  54. @Autowired
  55. private AiivhQuotaMapper aiivhQuotaMapper;
  56. @Autowired
  57. private AiivhQuotaHistoryMapper aiivhQuotaHistoryMapper;
  58. @Value("${tencent.ivh.app-key}")
  59. private String appKey;
  60. @Value("${tencent.ivh.access-token}")
  61. private String accessToken;
  62. @Value("${tencent.ivh.empty-app-key}")
  63. private String emptyAppKey;
  64. @Value("${tencent.ivh.empty-access-token}")
  65. private String emptyAccessToken;
  66. /**
  67. * [腾讯-数智人] 初始化 定制数智人列表 (使用公司账号)
  68. */
  69. @Transactional
  70. public Map<String, Object> initCustomVirtualman(Integer pageIndex, Integer pageSize) {
  71. Map<String, Object> payload = new HashMap<>();
  72. payload.put("VirtualmanTypeCode", "small_sample_2d");
  73. // payload.put("AnchorCodes", "Array of [string]");
  74. payload.put("PageIndex", pageIndex);
  75. payload.put("PageSize", pageSize);
  76. Map<String, Object> resultMap = Collections.singletonMap("Payload", payload);
  77. String bodyData = JSONUtil.toJsonStr(resultMap);
  78. // -- Http Request --------------------------------------------
  79. /**
  80. * 这里使用我自己的空白密钥,因为只有未创建自定义形象的账号,才能返回全部形象
  81. */
  82. Map<String, Object> signature = sdkTencentService.getSignature(appKey, accessToken); // 获得 Signature 签名
  83. String url = "https://gw.tvs.qq.com/v2/ivh/crmserver/customerassetservice/describesmallsampleimage";
  84. URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  85. .queryParam("appkey", signature.get("appkey"))
  86. .queryParam("signature", "{signature}")
  87. .queryParam("timestamp", signature.get("timestamp"))
  88. .build(signature.get("signature"));
  89. // Http 远程调用
  90. Map<String, Object> resp = WebClient.create()
  91. .method(HttpMethod.POST)
  92. .uri(uriWithParams)
  93. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  94. .bodyValue(bodyData.toString())
  95. .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
  96. })
  97. .block();
  98. return resp;
  99. }
  100. /**
  101. * [腾讯-数智人] 初始化公共数智人列表 (使用空白账号)
  102. * 注:有效期:2024/03/25-2024/04/08 过期后能否继续查询?
  103. */
  104. @Transactional
  105. public Map<String, Object> initPublicVirtualman(Integer pageIndex, Integer pageSize) {
  106. Map<String, Object> payload = new HashMap<>();
  107. payload.put("VirtualmanTypeCode", "small_sample_2d");
  108. // payload.put("AnchorCodes", "Array of [string]");
  109. payload.put("PageIndex", pageIndex);
  110. payload.put("PageSize", pageSize);
  111. Map<String, Object> resultMap = Collections.singletonMap("Payload", payload);
  112. String bodyData = JSONUtil.toJsonStr(resultMap);
  113. // -- Http Request --------------------------------------------
  114. /**
  115. * 这里使用我自己的空白密钥,因为只有未创建自定义形象的账号,才能返回全部形象
  116. */
  117. Map<String, Object> signature = sdkTencentService.getSignature(emptyAppKey, emptyAccessToken); // 获得 Signature 签名
  118. String url = "https://gw.tvs.qq.com/v2/ivh/crmserver/customerassetservice/describesmallsampleimage";
  119. URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  120. .queryParam("appkey", signature.get("appkey"))
  121. .queryParam("signature", "{signature}")
  122. .queryParam("timestamp", signature.get("timestamp"))
  123. .build(signature.get("signature"));
  124. // Http 远程调用
  125. Map<String, Object> resp = WebClient.create()
  126. .method(HttpMethod.POST)
  127. .uri(uriWithParams)
  128. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  129. .bodyValue(bodyData.toString())
  130. .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
  131. })
  132. .block();
  133. return resp;
  134. }
  135. /**
  136. * 基于实体类,将 anchor_name 转换为 AnchorName (将下划线转为大驼峰)
  137. *
  138. * Map<String, Object> resultMap = new HashMap<>();
  139. * convertObjectToMap(tencentCustomserviceDTO, resultMap);
  140. * System.out.println(resultMap);
  141. */
  142. public static void convertObjectToMap(Object obj, Map<String, Object> resultMap) throws IllegalAccessException {
  143. Class<?> clazz = obj.getClass();
  144. Field[] fields = clazz.getDeclaredFields();
  145. for (Field field : fields) {
  146. field.setAccessible(true);
  147. Object value = field.get(obj);
  148. String fieldName = field.getName();
  149. String convertedFieldName = StrUtil.upperFirst(StrUtil.toCamelCase(fieldName));
  150. if (value != null) {
  151. /**
  152. * 注意:需要手动填写实体类
  153. */
  154. if (value instanceof List) {
  155. // 如果值是列表类型,则遍历列表中的每个对象
  156. List<Object> list = (List<Object>)value;
  157. Map<String, Object> nestedMap = new HashMap<>();
  158. List<Map<String, Object>> nestedList = new ArrayList<>();
  159. for (Object item : list) {
  160. Map<String, Object> itemMap = new HashMap<>();
  161. convertObjectToMap(item, itemMap);
  162. nestedList.add(itemMap);
  163. }
  164. resultMap.put(convertedFieldName, nestedList);
  165. } else if (value.getClass().isAssignableFrom(VideoParamAnchorParamDTO.class) ||
  166. value.getClass().isAssignableFrom(VideoParamLogoParamDTO.class) ||
  167. value.getClass().isAssignableFrom(VideoParamDTO.class) ||
  168. value.getClass().isAssignableFrom(SpeechParamDTO.class) ||
  169. value.getClass().isAssignableFrom(TencentCustomservicePayloadDTO.class) ||
  170. value.getClass().isAssignableFrom(TencentCustomserviceConfirmPlayloadDTO.class) ||
  171. value.getClass().isAssignableFrom(TencentCustomserviceHeaderDTO.class) ||
  172. value.getClass().isAssignableFrom(Payload.class)) {
  173. // If the value is a nested object, recursively convert it to map
  174. Map<String, Object> nestedMap = new HashMap<>();
  175. convertObjectToMap(value, nestedMap);
  176. //
  177. fieldName = field.getName();
  178. convertedFieldName = StrUtil.upperFirst(StrUtil.toCamelCase(fieldName));
  179. resultMap.put(convertedFieldName, nestedMap);
  180. } else {
  181. fieldName = field.getName();
  182. convertedFieldName = StrUtil.upperFirst(StrUtil.toCamelCase(fieldName));
  183. resultMap.put(convertedFieldName, value);
  184. }
  185. }
  186. }
  187. }
  188. private String getAnchorNameView(String anchor_name) {
  189. LocalDateTime now = LocalDateTime.now();
  190. String timestamp = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
  191. return anchor_name + "_" + timestamp;
  192. }
  193. /**
  194. * [腾讯-数智人] 创建形象/音色定制任务
  195. */
  196. @Transactional
  197. public Map<String, Object> makeVirtualTask(TencentCustomserviceDTO tencentCustomserviceDTO) {
  198. RLock lock = redissonClient.getLock("makeVirtualTask");
  199. try { lock.tryLock(3, TimeUnit.SECONDS);
  200. // 参数校验:
  201. // - IdentityWrittenCosUrl 二选一或二者都传
  202. // - IdentityCosUrl 二选一或二者都传
  203. // 基于实体类,将 anchor_name 转换为 AnchorName (将下划线转为大驼峰)
  204. Map<String, Object> resultMap = new HashMap<>();
  205. // 生成: AnchorName 数智人名称_时间戳 (使接口值唯一,本地数据不唯一)
  206. // - anchor_name 唯一
  207. // - anchor_name_view 不唯一
  208. TencentCustomservicePayloadDTO payload = tencentCustomserviceDTO.getPayload();
  209. payload.setAnchor_name_view(getAnchorNameView(payload.getAnchor_name()));
  210. tencentCustomserviceDTO.setPayload(payload);
  211. convertObjectToMap(tencentCustomserviceDTO, resultMap);
  212. String bodyData = JSONUtil.toJsonStr(resultMap);
  213. // System.out.println("resultMap:");
  214. // System.out.println(resultMap);
  215. // System.out.println("-----------------------------------------");
  216. // System.out.println("bodyData:");
  217. // System.out.println(bodyData);
  218. // System.out.println("-----------------------------------------");
  219. // -- Http Request --------------------------------------------
  220. Map<String, Object> signature = sdkTencentService.getSignature(appKey, accessToken); // 获得 Signature 签名
  221. String url = "https://gw.tvs.qq.com/v2/ivh/assetmanager/customservice/make";
  222. URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  223. .queryParam("appkey", signature.get("appkey"))
  224. .queryParam("signature", "{signature}")
  225. .queryParam("timestamp", signature.get("timestamp"))
  226. .build(signature.get("signature"));
  227. // Http 远程调用
  228. Map<String, Object> resp = WebClient.create()
  229. .method(HttpMethod.POST)
  230. .uri(uriWithParams)
  231. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  232. .bodyValue(bodyData)
  233. .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
  234. })
  235. .block();
  236. System.out.println(resp);
  237. //// 临时数据 (模拟成功)
  238. //Map<String, Object> tempPayload = new LinkedHashMap<>();
  239. //String uuid = String.valueOf(UUID.randomUUID());
  240. //tempPayload.put("TaskId", uuid);
  241. //resp.put("Payload", tempPayload);
  242. /**
  243. * 创建任务 (远程)
  244. */
  245. // 判断 任务是否创建成功 (是,则创建数据库记录)
  246. Integer respHeaderCode = (Integer) MapUtil.get(resp, "Header.Code");
  247. if (respHeaderCode == 0) {
  248. /**
  249. * 创建任务 (本地)
  250. */
  251. TencentCustomservicePayloadDTO tencentCustomservicePayloadDTO = tencentCustomserviceDTO.getPayload();
  252. String make_type = tencentCustomservicePayloadDTO.getMake_type();
  253. // -- [Post] 创建数智人任务 --------------------------------------------------------
  254. AiPersonDTO aiPersonDTO = new AiPersonDTO();
  255. // [查询] 自身 UserId
  256. Long user_id = httpRequestAspect.getUserId();
  257. aiPersonDTO.setUser_id(user_id);
  258. //
  259. aiPersonDTO.setRequest_id((String) MapUtil.get(resp, "Header.RequestID"));
  260. aiPersonDTO.setTask_id((String) MapUtil.get(resp, "Payload.TaskId"));
  261. aiPersonDTO.setAnchor_name(tencentCustomservicePayloadDTO.getAnchor_name_view());
  262. aiPersonDTO.setAnchor_name_view(tencentCustomservicePayloadDTO.getAnchor_name());
  263. aiPersonDTO.setMake_type(make_type);
  264. aiPersonDTO.setIdentity_cos_url(tencentCustomservicePayloadDTO.getIdentity_cos_url());
  265. aiPersonDTO.setIdentity_written_cos_url(tencentCustomservicePayloadDTO.getIdentity_written_cos_url());
  266. aiPersonDTO.setMaterial_cos_url(tencentCustomservicePayloadDTO.getMaterial_cos_url());
  267. aiPersonDTO.setIs_remove_background(tencentCustomservicePayloadDTO.getIs_remove_background());
  268. aiPersonDTO.setSex_type(tencentCustomservicePayloadDTO.getSex_type());
  269. aiPersonDTO.setNotes(tencentCustomservicePayloadDTO.getNotes());
  270. aiPersonDTO.setText_driver(tencentCustomservicePayloadDTO.getText_driver());
  271. aiPersonDTO.setVoice_driver_cos_file(tencentCustomservicePayloadDTO.getVoice_driver_cos_file());
  272. aiivhMakeVirtualmanTaskService.insertAiivhMakeVirtualmanTask(aiPersonDTO);
  273. /**
  274. * 创建任务之后,此时是 { "Stage": EXAMINE } 状态,还未生成任务,
  275. * 大概1分钟内才会更变状态
  276. *
  277. * 创建任务成功后,创建一个临时 [形象] 记录,主要是记录 user_id 和 anchor_name 字段
  278. * 等待用户访问查询详情接口之后,再更新 [形象详情]
  279. */
  280. if(make_type.equals("IMAGE_GENERAL")) {
  281. // 定制形象 (创建临时记录)
  282. AiivhAnchorDTO aiivhAnchorDTO = new AiivhAnchorDTO();
  283. aiivhAnchorDTO.setAnchor_name(tencentCustomservicePayloadDTO.getAnchor_name_view());
  284. aiivhAnchorDTO.setAnchor_name_view(tencentCustomservicePayloadDTO.getAnchor_name());
  285. aiivhAnchorDTO.setUser_id(user_id);
  286. aiivhMakeVirtualmanTaskService.insertAiivhAnchorCustom(aiivhAnchorDTO);
  287. }
  288. if(make_type.equals("VOICE")) {
  289. // 定制音色 (创建临时记录)
  290. AiPersonTimbreCustomDTO aiPersonTimbreCustomDTO = new AiPersonTimbreCustomDTO();
  291. aiPersonTimbreCustomDTO.setTimbre_name(tencentCustomservicePayloadDTO.getAnchor_name_view());
  292. aiPersonTimbreCustomDTO.setTimbre_name_view(tencentCustomservicePayloadDTO.getAnchor_name());
  293. aiPersonTimbreCustomDTO.setUser_id(user_id);
  294. aiivhMakeVirtualmanTaskService.insertAiPersoneTimbreCustom(aiPersonTimbreCustomDTO);
  295. }
  296. }
  297. return resp;
  298. } catch (IllegalAccessException e) {
  299. throw new RuntimeException(e);
  300. } catch (InterruptedException e) { throw new RuntimeException(e);
  301. } finally { lock.unlock(); }
  302. }
  303. /**
  304. * [腾讯-数智人] 创建定制形象任务 (未提交远程)
  305. */
  306. @Transactional
  307. public Map<String, Object> insertVirtualmanTask(TencentCustomserviceDTO tencentCustomserviceDTO) {
  308. TencentCustomservicePayloadDTO tencentCustomservicePayloadDTO = tencentCustomserviceDTO.getPayload();
  309. // -- 将带 Payload 的参数进行格式化 --------------------------------------------------------
  310. AiPersonDTO aiPersonDTO = new AiPersonDTO();
  311. // [查询] 自身 UserId
  312. Long user_id = httpRequestAspect.getUserId();
  313. aiPersonDTO.setUser_id(user_id);
  314. //
  315. // aiPersonDTO.setRequest_id((String) MapUtil.get(resp, "Header.RequestID"));
  316. // aiPersonDTO.setTask_id((String) MapUtil.get(resp, "Payload.TaskId"));
  317. String anchor_name = tencentCustomservicePayloadDTO.getAnchor_name();
  318. aiPersonDTO.setAnchor_name(getAnchorNameView(anchor_name));
  319. aiPersonDTO.setAnchor_name_view(anchor_name);
  320. aiPersonDTO.setQuota_code(tencentCustomserviceDTO.getQueue_code());
  321. aiPersonDTO.setMake_type(tencentCustomserviceDTO.getPayload().getMake_type());
  322. aiPersonDTO.setIdentity_cos_url(tencentCustomservicePayloadDTO.getIdentity_cos_url());
  323. aiPersonDTO.setIdentity_written_cos_url(tencentCustomservicePayloadDTO.getIdentity_written_cos_url());
  324. aiPersonDTO.setMaterial_cos_url(tencentCustomservicePayloadDTO.getMaterial_cos_url());
  325. aiPersonDTO.setIs_remove_background(tencentCustomservicePayloadDTO.getIs_remove_background());
  326. aiPersonDTO.setSex_type(tencentCustomservicePayloadDTO.getSex_type());
  327. aiPersonDTO.setNotes(tencentCustomservicePayloadDTO.getNotes());
  328. aiPersonDTO.setText_driver(tencentCustomservicePayloadDTO.getText_driver());
  329. aiPersonDTO.setVoice_driver_cos_file(tencentCustomservicePayloadDTO.getVoice_driver_cos_file());
  330. aiPersonDTO.setTask_status("AUDIT");
  331. return aiivhMakeVirtualmanTaskService.insertAiivhMakeVirtualmanTask(aiPersonDTO);
  332. }
  333. /*
  334. // 排队机制(定时任务?)
  335. // 1.先查询排队表,如果为空,则提交,同时返回任务ID;如果前面有人,则排队,返回排队人数
  336. //
  337. // 2.数据插入定制数据人列表 (不执行),同时插入排队表 (包含提交的数据)
  338. // 4.用定时任务查询未完成记录 (查询成功或失败,则将排队表中的记录去除;同时将下一个任务自动提交)
  339. // 这一步还不用排队,要等运营人员点提交才有
  340. // // -- 使用 Redisson 排队 -----------------------------------------------------------------
  341. // RList<String> rList = redissonClient.getList("makeVirtualTaskQueue");
  342. // tencentCustomserviceDTO.setQueue_code(CommonUtil.generateUniqueRandomOrderCode("QUE"));
  343. // String jsonString = JSONUtil.toJsonStr(tencentCustomserviceDTO);
  344. // // 入队
  345. // rList.add(jsonString);
  346. */
  347. /**
  348. * [方法] 查询我的配额,并获得有效(类型匹配,状态可用,未过期),最近临期的那一条配额
  349. */
  350. private Map<String, Object> getEffectiveQuota(String quotaType, String makeType) {
  351. // [Get] 查询我的配额
  352. AiivhQuotaDTO aiivhQuotaDTO = new AiivhQuotaDTO();
  353. aiivhQuotaDTO.setUser_id(httpRequestAspect.getUserId());
  354. List<Map<String, Object>> myQuota = aiivhQuotaMapper.queryAiivhQuotaList(aiivhQuotaDTO);
  355. LocalDateTime now = LocalDateTime.now();
  356. String finalQuotaType = quotaType;
  357. myQuota = myQuota.stream().filter(map -> {
  358. String qType = (String) map.get("quota_type"); // 配额类型 (Anchor|Timbre)
  359. Integer qStatus = (Integer) map.get("quota_status"); // 配额状态 (1未使用|2使用中|3已使用)
  360. String expireDateString = (String) map.get("expire_date"); // 过期时间 (对比当前时间)
  361. LocalDateTime expireDate = LocalDateTime.parse(expireDateString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  362. return (finalQuotaType.equals(qType)) && qStatus == 1 && !now.isAfter(expireDate);
  363. }).sorted(Comparator.comparing(map -> {
  364. String expireDateString = map.get("expire_date").toString(); // 按过期时间排序 (最早创建在最前面)
  365. return LocalDateTime.parse(expireDateString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  366. })).collect(Collectors.toList());
  367. if (myQuota.size() == 0) {
  368. throw new CustException("(" + makeType + ") 配额不足");
  369. } else {
  370. // 获得最近临期的那一条配额
  371. Map<String, Object> targetQuota = myQuota.get(0);
  372. return targetQuota;
  373. }
  374. }
  375. /**
  376. * 在上面定制的基础上增加排队机制
  377. */
  378. @Transactional
  379. public Map<String, Object> makeVirtualTaskQueue(TencentCustomserviceDTO tencentCustomserviceDTO) {
  380. RLock lock = redissonClient.getLock("makeVirtualTask");
  381. try { lock.tryLock(3, TimeUnit.SECONDS);
  382. // [Get] 查询我的配额数量一览
  383. AiivhAssetQuotaStatisticsDTO aiivhAssetQuotaStatisticsDTO = aiivhAssetService.getMyOverview();
  384. // 判断配额是否足够
  385. Long quota_balance = null;
  386. String makeType = tencentCustomserviceDTO.getPayload().getMake_type();
  387. String quotaType = null;
  388. String activeType = null, activeTypeDescription = null;
  389. // [定制视频] 2D小样本-标准版(通用口型)形象定制
  390. if (makeType.equals("IMAGE_GENERAL")) {
  391. quota_balance = aiivhAssetQuotaStatisticsDTO.getAnchor_free_quota();
  392. quotaType = "Anchor";
  393. activeType = IvhQuotaActiveEvent.MAKE_ANCHOR.getCode();
  394. activeTypeDescription = IvhQuotaActiveEvent.MAKE_ANCHOR.getDescription();
  395. }
  396. // [定制音色] 2D小样本声音定制
  397. if (makeType.equals("VOICE")) {
  398. quota_balance = aiivhAssetQuotaStatisticsDTO.getTimbre_free_quota();
  399. quotaType = "Timbre";
  400. activeType = IvhQuotaActiveEvent.MAKE_TIMBRE.getCode();
  401. activeTypeDescription = IvhQuotaActiveEvent.MAKE_TIMBRE.getDescription();
  402. }
  403. if (quota_balance <= 0) {
  404. throw new CustException("(" + makeType + ") 配额不足");
  405. } else {
  406. // -- 1.查询我的配额,并获得有效(类型匹配,状态可用,未过期),最近临期的那一条配额 -----------------------------
  407. Map<String, Object> targetQuota = getEffectiveQuota(quotaType, makeType);
  408. String quotaCode = (String) targetQuota.get("quota_code");
  409. // -- 2.修改配额状态: 2使用中 ------------------------------------------------------------------------
  410. AiivhQuotaDTO aiivhQuotaDTO = new AiivhQuotaDTO();
  411. aiivhQuotaDTO.setQuota_code(quotaCode);
  412. aiivhQuotaDTO.setQuota_status(2);
  413. aiivhQuotaMapper.updateAiivhQuota(aiivhQuotaDTO);
  414. // -- 3.记录 ai_ivh_quota_history 操作 ------------------------------------------------------------
  415. List<AiivhQuotaHistoryDTO> list = new ArrayList<>();
  416. AiivhQuotaHistoryDTO aiivhQuotaHistoryDTO = new AiivhQuotaHistoryDTO();
  417. aiivhQuotaHistoryDTO.setUser_id(httpRequestAspect.getUserId());
  418. aiivhQuotaHistoryDTO.setActivity_type(activeType);
  419. aiivhQuotaHistoryDTO.setActivity_type_description(activeTypeDescription);
  420. aiivhQuotaHistoryDTO.setQuota_type(quotaType);
  421. aiivhQuotaHistoryDTO.setQuota_adjustment(-1);
  422. list.add(aiivhQuotaHistoryDTO);
  423. aiivhQuotaHistoryMapper.insertAiivhQuotaHistoryBatch(list);
  424. // 创建定制形象任务 (未提交)
  425. tencentCustomserviceDTO.setQueue_code(quotaCode);
  426. return insertVirtualmanTask(tencentCustomserviceDTO);
  427. }
  428. } catch (InterruptedException e) { throw new RuntimeException(e);
  429. } finally { lock.unlock(); }
  430. }
  431. /**
  432. * 发起任务 (填写 腾讯订单、备注,同时发起任务)
  433. */
  434. @Transactional
  435. public Map<String, Object> launchVirtualTaskQueue(VirtualmanLaunchDTO virtualmanLaunchDTO) {
  436. // 1.获得任务详情
  437. AiPersonDTO aiPersonDTO = new AiPersonDTO();
  438. aiPersonDTO.setQuota_code(virtualmanLaunchDTO.getQuota_code());
  439. Map<String, Object> taskDetail = aiivhMakeVirtualmanTaskService.queryAiivhMakeVirtualmanTaskDetail(aiPersonDTO);
  440. // 2.发起任务
  441. TencentCustomserviceDTO tencentCustomserviceDTO = new TencentCustomserviceDTO();
  442. TencentCustomservicePayloadDTO payloadDTO = new TencentCustomservicePayloadDTO();
  443. payloadDTO.setAnchor_name((String) taskDetail.get("anchor_name"));
  444. payloadDTO.setMake_type((String) taskDetail.get("make_type"));
  445. payloadDTO.setIdentity_cos_url((String) taskDetail.get("identity_cos_url"));
  446. payloadDTO.setIdentity_written_cos_url((String) taskDetail.get("identity_written_cos_url"));
  447. payloadDTO.setMaterial_cos_url((String) taskDetail.get("material_cos_url"));
  448. payloadDTO.setIs_remove_background((Integer) taskDetail.get("is_remove_background"));
  449. payloadDTO.setSex_type((String) taskDetail.get("sex_type"));
  450. payloadDTO.setNotes((String) taskDetail.get("notes"));
  451. payloadDTO.setText_driver((String) taskDetail.get("text_driver"));
  452. payloadDTO.setVoice_driver_cos_file((String) taskDetail.get("voice_driver_cos_file"));
  453. tencentCustomserviceDTO.setPayload(payloadDTO);
  454. System.out.println(tencentCustomserviceDTO);
  455. Map<String, Object> resp = makeVirtualTask(tencentCustomserviceDTO);
  456. System.out.println("--------------------------");
  457. System.out.println(resp);
  458. Integer respHeaderCode = (Integer) MapUtil.get(resp, "Header.Code");
  459. if (respHeaderCode != 0) {
  460. // {Header={RequestID=gz6c6d3da317138514464456410, SessionID=gz6c6d3da317138514464456411, DialogID=, Code=100008, Message=LimitExceeded:超过配额限制: AssetInsufficientError:无可用配额}}
  461. String message = (String) MapUtil.get(resp, "Header.Message");
  462. throw new CustException(message);
  463. } else {
  464. // 2.更新 任务ID、腾讯订单号、备注
  465. String task_id = (String) MapUtil.get(resp, "Payload.TaskId");
  466. aiPersonDTO.setTask_id(task_id);
  467. aiPersonDTO.setQuota_code(virtualmanLaunchDTO.getQuota_code());
  468. aiPersonDTO.setTencent_order_code(virtualmanLaunchDTO.getTencent_order_code());
  469. aiPersonDTO.setLaunch_note(virtualmanLaunchDTO.getLaunch_note());
  470. return aiivhMakeVirtualmanTaskService.updateAiivhMakeVirtualmanTask(aiPersonDTO);
  471. }
  472. }
  473. /**
  474. * [腾讯-数智人] 查询形象/音色定制进度
  475. */
  476. @Transactional
  477. public Map<String, Object> getProgressOfVirtualmanTask(String task_id) {
  478. // -- Http Request --------------------------------------------
  479. Map<String, Object> signature = sdkTencentService.getSignature(appKey, accessToken); // 获得 Signature 签名
  480. // System.out.println("signature: " + signature);
  481. String url = "https://gw.tvs.qq.com/v2/ivh/assetmanager/customservice/getprogress";
  482. URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  483. .queryParam("appkey", signature.get("appkey"))
  484. .queryParam("signature", "{signature}")
  485. .queryParam("timestamp", signature.get("timestamp"))
  486. .build(signature.get("signature"));
  487. String jsonStr = "{\"Payload\": {\"TaskId\": \"" + task_id + "\"}}";
  488. JSONObject bodyData = new JSONObject(jsonStr);
  489. // System.out.println("task_id: " + task_id);
  490. // System.out.println("bodyData: " + bodyData);
  491. // Http 远程调用
  492. Map<String, Object> resp = WebClient.create()
  493. .method(HttpMethod.POST)
  494. .uri(uriWithParams)
  495. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  496. .bodyValue(bodyData.toString())
  497. .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
  498. .block();
  499. // // 临时数据 (模拟成功)
  500. // Map<String, Object> tempPayload = new LinkedHashMap<>();
  501. // tempPayload.put("StageInfo", "{\"TrainResult\":[\"url1\",\"url2\"],\"RejectReason\":\"\",\"TrainRecord\":[{\"StartTime\":\"2023-11-01 23:00:17\",\"Record\":[\"url1\",\"url2\"]}]}");
  502. // tempPayload.put("Status", "MAKING");
  503. // tempPayload.put("StatusV2", "WAIT_PROCESS");
  504. // tempPayload.put("FailMessage", "");
  505. // tempPayload.put("StageV2", "CONFIRM");
  506. // resp.put("Payload", tempPayload);
  507. System.out.println("getProgressOfVirtualmanTask:");
  508. System.out.println(resp);
  509. // 判断 任务是否查询成功 (是,则更新数据库记录)
  510. Integer respHeaderCode = (Integer) MapUtil.get(resp, "Header.Code");
  511. if (respHeaderCode == 0) {
  512. // 任务失败
  513. String respPayloadStage = (String) MapUtil.get(resp, "Payload.Stage");
  514. String respPayloadStageInfo = (String) MapUtil.get(resp, "Payload.StageInfo");
  515. System.out.println("-- respPayloadStageInfo --------");
  516. System.out.println(respPayloadStageInfo);
  517. System.out.println("--------------------------------");
  518. // if (respPayloadStage.equals("END")) {
  519. // throw new RuntimeException(respPayloadStageInfo);
  520. // }
  521. String respPayloadStatus = (String) MapUtil.get(resp, "Payload.Status");
  522. // 定制任务完成后,
  523. // - 失败,记得把状态改为 1未使用
  524. // - 成功,记得把 quota 状态改为 3已使用
  525. AiivhQuotaDTO aiivhQuotaDTO = new AiivhQuotaDTO();
  526. if ("FAIL".equals(respPayloadStatus)) {
  527. aiivhQuotaDTO.setQuota_status(1);
  528. }
  529. if ("SUCCESS".equals(respPayloadStatus)) {
  530. aiivhQuotaDTO.setQuota_status(3);
  531. }
  532. aiivhQuotaMapper.updateAiivhQuota(aiivhQuotaDTO);
  533. // String respPayloadFailMessage = (String) MapUtil.get(resp, "Payload.FailMessage");
  534. /**
  535. * 更新任务
  536. */
  537. AiPersonDTO aiPersonDTO = new AiPersonDTO();
  538. // [查询] 自身 UserId
  539. Long user_id = httpRequestAspect.getUserId();
  540. aiPersonDTO.setUser_id(user_id);
  541. aiPersonDTO.setTask_id(task_id);
  542. aiPersonDTO.setTask_status(respPayloadStatus);
  543. aiPersonDTO.setTask_status_info(respPayloadStageInfo);
  544. aiPersonDTO.setStage_status(respPayloadStage); // 返回值 { Payload } 跟文档不一样,文档是 { StageV2 },实际上是返回 { Stage }
  545. // 返回值 { StageInfo } 跟文档不一样,文档是:
  546. // {\"TrainResult\":[\"url1\",\"url2\"],\"RejectReason\":\"\",\"TrainRecord\":[{\"StartTime\":\"2023-11-01 23:00:17\",\"Record\":[\"url1\",\"url2\"]}]}
  547. // 实际上是:
  548. // {"TrainResult":["https://videomaker-resources.ivh.qq.com/broadcast/Basic/video/78f7fb6c6fec4311921ede313f3c7cd5.mp4","https://videomaker-resources.ivh.qq.com/broadcast/Basic/video/259044134def40f69fdfdc1cd6c6c952.mp4"],"RejectReason":"","TrainRecord":[{"StartTime":"2024-03-21 18:06:59","Record":["https://videomaker-resources.ivh.qq.com/broadcast/Basic/video/78f7fb6c6fec4311921ede313f3c7cd5.mp4","https://videomaker-resources.ivh.qq.com/broadcast/Basic/video/259044134def40f69fdfdc1cd6c6c952.mp4"]}]}
  549. if (!respPayloadStageInfo.isEmpty()) {
  550. JSONObject StageInfoObject = JSONUtil.parseObj(respPayloadStageInfo);
  551. String result = JSONUtil.toJsonStr(StageInfoObject);
  552. aiPersonDTO.setResult(result);
  553. }
  554. System.out.println("aiPersonDTO:");
  555. System.out.println(aiPersonDTO);
  556. // [Post] 根据 Payload 返回的状态来更新
  557. aiivhMakeVirtualmanTaskService.updateAiivhMakeVirtualmanTask(aiPersonDTO);
  558. }
  559. return resp;
  560. }
  561. @Transactional
  562. public List<Map<String, Object>> getQueueOfVirtualmanTask(String task_id) {
  563. RList<String> rList = redissonClient.getList("makeVirtualTaskQueue");
  564. List<String> queueList = rList.readAll();
  565. List<Map<String, Object>> result = new ArrayList<>();
  566. if (queueList != null && queueList.size() > 0) {
  567. for (String queue : queueList) {
  568. result.add(JSONUtil.parseObj(queue));
  569. }
  570. }
  571. return result;
  572. }
  573. /**
  574. * [腾讯-数智人] 确认形象/音色定制效果
  575. */
  576. @Transactional
  577. public Map<String, Object> confirmVirtualmanTask(TencentCustomserviceConfirmDTO tencentCustomserviceConfirmDTO) {
  578. try {
  579. TencentCustomserviceConfirmPlayloadDTO payload = (TencentCustomserviceConfirmPlayloadDTO) tencentCustomserviceConfirmDTO.getPayload();
  580. // 基于实体类,将 anchor_name 转换为 AnchorName (将下划线转为大驼峰)
  581. Map<String, Object> resultMap = new HashMap<>();
  582. convertObjectToMap(tencentCustomserviceConfirmDTO, resultMap);
  583. String bodyData = JSONUtil.toJsonStr(resultMap);
  584. // -- Http Request --------------------------------------------
  585. Map<String, Object> signature = sdkTencentService.getSignature(appKey, accessToken); // 获得 Signature 签名
  586. String url = "https://gw.tvs.qq.com/v2/ivh/assetmanager/customservice/customerconfirm";
  587. URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  588. .queryParam("appkey", signature.get("appkey"))
  589. .queryParam("signature", "{signature}")
  590. .queryParam("timestamp", signature.get("timestamp"))
  591. .build(signature.get("signature"));
  592. // Http 远程调用
  593. Map<String, Object> resp = WebClient.create()
  594. .method(HttpMethod.POST)
  595. .uri(uriWithParams)
  596. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  597. .bodyValue(bodyData)
  598. .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
  599. })
  600. .block();
  601. System.out.println(resp);
  602. // // 临时数据 (模拟成功)
  603. // Map<String, Object> tempPayload = new LinkedHashMap<>();
  604. // tempPayload.put("Operate", "AGREE");
  605. //// tempPayload.put("Operate", "REJECT");
  606. //// tempPayload.put("Reason", "REJECT 就是 拒绝");
  607. // resp.put("Payload", tempPayload);
  608. // 判断 任务是否确认成功 (是,则更新数据库记录)
  609. Integer respHeaderCode = (Integer) MapUtil.get(resp, "Header.Code");
  610. if (respHeaderCode == 0) {
  611. AiPersonDTO aiPersonDTO = new AiPersonDTO();
  612. // [查询] 自身 UserId
  613. Long user_id = httpRequestAspect.getUserId();
  614. aiPersonDTO.setUser_id(user_id);
  615. aiPersonDTO.setTask_id(payload.getTask_id());
  616. String Operate = (String) MapUtil.get(resp, "Payload.Operate");
  617. aiPersonDTO.setConfirm_status(Operate);
  618. aiPersonDTO.setConfirm_status_info((String) MapUtil.get(resp, "Payload.Reason"));
  619. /**
  620. * 更新定制数智人任务
  621. */
  622. aiivhMakeVirtualmanTaskService.updateAiivhMakeVirtualmanTask(aiPersonDTO);
  623. /**
  624. * 更新形象 (仅更新 stage_status)
  625. */
  626. if (Operate == "AGREE") {
  627. AiivhAnchorDTO aiivhAnchorDTO = new AiivhAnchorDTO();
  628. //Map<String, Object> detail = aiPersonMapper.queryAiPersonDetail(aiPersonDTO);
  629. Map<String, Object> detail = aiivhMakeVirtualmanTaskService.queryAiivhMakeVirtualmanTaskDetail(aiPersonDTO);
  630. aiivhAnchorDTO.setUser_id(user_id);
  631. aiivhAnchorDTO.setAnchor_name((String) detail.get("anchor_name"));
  632. aiivhAnchorDTO.setStage_status((String) detail.get("stage_status"));
  633. System.out.println(aiivhAnchorDTO);
  634. aiivhMakeVirtualmanTaskService.updateAiPersoneVirtualmanCustom(aiivhAnchorDTO);
  635. }
  636. }
  637. return resp;
  638. } catch (IllegalAccessException e) {
  639. throw new RuntimeException(e);
  640. }
  641. }
  642. // /**
  643. // * 腾讯-数智人-个人资产管理-分页查询小样本形象列表接口
  644. // * https://cloud.tencent.com/document/product/1240/93404
  645. // */
  646. // @Transactional
  647. // public Map<String, Object> getCustomVirtualman(Integer pageNum, Integer pageSize) {
  648. // Map<String, Object> payload = new HashMap<>();
  649. // payload.put("VirtualmanTypeCode", "small_sample_2d");
  650. //// String[] anchor_codes = new String[]{"imagetrain_100035906086_3416"};
  651. //// payload.put("AnchorCodes", anchor_codes);
  652. // payload.put("PageIndex", pageNum);
  653. // payload.put("PageSize", pageSize);
  654. //
  655. //
  656. // Map<String, Object> resultMap = Collections.singletonMap("Payload", payload);
  657. // String bodyData = JSONUtil.toJsonStr(resultMap);
  658. //
  659. // // -- Http Request --------------------------------------------
  660. // /**
  661. // * 这里使用我自己的空白密钥,因为只有未创建自定义形象的账号,才能返回全部形象
  662. // */
  663. // Map<String, Object> signature = sdkTencentService.getSignature(appKey, accessToken); // 获得 Signature 签名
  664. //
  665. // String url = "https://gw.tvs.qq.com/v2/ivh/crmserver/customerassetservice/describesmallsampleimage";
  666. // URI uriWithParams = UriComponentsBuilder.fromUriString(url)
  667. // .queryParam("appkey", signature.get("appkey"))
  668. // .queryParam("signature", "{signature}")
  669. // .queryParam("timestamp", signature.get("timestamp"))
  670. // .build(signature.get("signature"));
  671. //
  672. // // Http 远程调用
  673. // Map<String, Object> resp = WebClient.create()
  674. // .method(HttpMethod.POST)
  675. // .uri(uriWithParams)
  676. // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  677. // .bodyValue(bodyData.toString())
  678. // .retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
  679. // })
  680. // .block();
  681. //
  682. // Integer respHeaderCode = (Integer) MapUtil.get(resp, "Header.Code");
  683. // if (respHeaderCode == 0) {
  684. // List<Map<String, Object>> virtualmans = (List<Map<String, Object>>) MapUtil.get(resp, "Payload.Virtualmans");
  685. // Map<String, Object> response = new LinkedHashMap<>();
  686. // // 遍历 virtualmans 列表,并将 key 由大驼峰改为下划线命名
  687. // virtualmans = ListUtil.convertToUnderscoreCase(virtualmans);
  688. //
  689. //
  690. // // 分页查询
  691. // if (pageNum != null && pageSize != null) {
  692. // PageHelper.startPage(pageNum, pageSize);
  693. // PageHelper.getLocalPage().setPageSizeZero(true);
  694. // }
  695. // PageInfoResult pageInfoResult = new PageInfoResult(virtualmans);
  696. // return pageInfoResult.toMap();
  697. // }
  698. //
  699. // return resp;
  700. // }
  701. /**
  702. * 获得全部形象列表 (循环调用接口,至到获得全部数据)
  703. * List<Map<String, Object>> virtualmans = getAllVirtualman(pageIndex, pageSize);
  704. */
  705. private List<Map<String, Object>> getAllVirtualmanNoEmpty(Integer pageIndex, Integer pageSize) {
  706. // 初始化变量
  707. List<Map<String, Object>> timbre_assets = new ArrayList<>();
  708. // 第一次调用 initTimbre 方法,并将当前页的数据追加到 timbre_assets 列表中
  709. Map<String, Object> resp = initCustomVirtualman(pageIndex, pageSize);
  710. Integer total = (Integer) MapUtil.get(resp, "Payload.Total");
  711. timbre_assets.addAll((List<Map<String, Object>>) MapUtil.get(resp, "Payload.Virtualmans"));
  712. // 循环获取所有页的数据
  713. while (total != null && total > 0 && pageIndex * pageSize <= total) {
  714. // 准备下一次查询,页码加1
  715. pageIndex++;
  716. System.out.println("pageIndex: " + pageIndex);
  717. // 再次调用 initTimbre 方法获取下一页的数据,并将当前页的数据追加到 timbre_assets 列表中
  718. resp = initCustomVirtualman(pageIndex, pageSize);
  719. total = (Integer) MapUtil.get(resp, "Payload.Total");
  720. timbre_assets.addAll((List<Map<String, Object>>) MapUtil.get(resp, "Payload.Virtualmans"));
  721. }
  722. return timbre_assets;
  723. }
  724. /**
  725. * [内部服务] 获得形象详细信息 (通过音色名称)
  726. */
  727. @Transactional
  728. public Map<String, Object> getVirtualmanObject(String anchor_name) {
  729. // 循环获得数据列表,直到 total 小于 pageSize
  730. List<Map<String, Object>> virtualmans = getAllVirtualmanNoEmpty(1, 100);
  731. // 遍历 virtualmans 列表,并将 key 由大驼峰改为下划线命名
  732. virtualmans = ListUtil.convertToUnderscoreCase(virtualmans);
  733. // 遍历 匹配 anchor_name
  734. Map<String, Object> result = new LinkedHashMap<>();
  735. for (Map<String, Object> virtualman : virtualmans) {
  736. String virtualman_anchor_name = (String) virtualman.get("anchor_name");
  737. // System.out.println("virtualman_anchor_name: " + virtualman_anchor_name + ", anchor_name: " + anchor_name);
  738. if (virtualman_anchor_name.equals(anchor_name)) {
  739. result = virtualman;
  740. System.out.println(virtualman);
  741. }
  742. }
  743. return result;
  744. }
  745. }