VaultodyServiceImpl.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. package com.crm.manager.service.impl;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.crm.manager.dao.mapper.TransactionItemMapper;
  4. import com.crm.manager.repository.TransactionItemRepository;
  5. import com.crm.manager.service.SysConfigService;
  6. import com.crm.manager.service.SysVaultodyConfigService;
  7. import com.crm.manager.service.VaultodyService;
  8. import com.crm.manager.util.DateUtils;
  9. import com.crm.rely.backend.core.constant.Constants;
  10. import com.crm.rely.backend.core.dto.base.BaseResultDto;
  11. import com.crm.rely.backend.core.dto.base.PageDto;
  12. import com.crm.rely.backend.core.dto.base.ResultWithPagerDto;
  13. import com.crm.rely.backend.core.exception.ServiceException;
  14. import com.crm.rely.backend.core.pojo.table.SysConfigTable;
  15. import com.crm.rely.backend.model.config.VaultodyConfig;
  16. import com.crm.rely.backend.model.constant.ConfigConstants;
  17. import com.crm.rely.backend.model.dto.vaultody.vaults.TransactionItemDto;
  18. import com.crm.rely.backend.model.dto.vaultody.vaults.VaultTransaction;
  19. import com.crm.rely.backend.model.dto.vaultody.vaults.VaultodyVaultsListDto;
  20. import com.crm.rely.backend.model.dto.vaultody.vaults.response.ResponseData;
  21. import com.crm.rely.backend.model.dto.vaultody.vaults.response.TransactionItem;
  22. import com.crm.rely.backend.model.dto.vaultody.vaults.response.TransactionResponse;
  23. import com.crm.rely.backend.model.dto.vaultody.vaults.response.VaultsListResponseDto;
  24. import com.crm.rely.backend.model.entity.vaultody.vaults.VaultTransactionsEntity;
  25. import com.crm.rely.backend.model.entity.vaultody.vaults.VaultTransactionsSearchEntity;
  26. import com.crm.rely.backend.model.pojo.table.SysVaultodyConfigTable;
  27. import com.crm.rely.backend.model.pojo.table.TransactionItemTable;
  28. import com.crm.rely.backend.util.AESUtil;
  29. import com.crm.rely.backend.util.HttpUtil;
  30. import com.crm.rely.backend.util.UUIDUtil;
  31. import com.google.common.collect.Lists;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.apache.commons.lang3.StringUtils;
  34. import org.jsoup.Connection;
  35. import org.springframework.beans.BeanUtils;
  36. import org.springframework.beans.factory.annotation.Autowired;
  37. import org.springframework.stereotype.Service;
  38. import org.springframework.util.CollectionUtils;
  39. import org.springframework.util.ObjectUtils;
  40. import javax.crypto.Mac;
  41. import javax.crypto.spec.SecretKeySpec;
  42. import java.io.IOException;
  43. import java.nio.charset.StandardCharsets;
  44. import java.util.*;
  45. import java.util.stream.Collectors;
  46. @Slf4j
  47. @Service
  48. public class VaultodyServiceImpl implements VaultodyService {
  49. @Autowired
  50. private SysVaultodyConfigService vaultodyConfigService;
  51. @Autowired
  52. private SysConfigService sysConfigService;
  53. @Autowired
  54. private TransactionItemRepository transactionItemRepository;
  55. @Autowired
  56. private TransactionItemMapper transactionItemMapper;
  57. public static String getSignature(String message, String apiSecret) {
  58. try {
  59. byte[] decodedSecret = Base64.getDecoder().decode(apiSecret);
  60. Mac mac = Mac.getInstance("HmacSHA256");
  61. SecretKeySpec secretKeySpec = new SecretKeySpec(decodedSecret, "HmacSHA256");
  62. mac.init(secretKeySpec);
  63. byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
  64. return Base64.getEncoder().encodeToString(hash);
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. return null;
  69. }
  70. public static void main2(String[] args) {
  71. String s = "{\n" +
  72. " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" +
  73. " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" +
  74. " \"passphrase\": \"7UGMi2*t0h\",\n" +
  75. " \"vaultId\": \"\",\n" +
  76. " \"baseUrl\": \"https://rest.vaultody.com\",\n" +
  77. " \"vaultsListUrl\": \"/vaults/test\",\n" +
  78. " \"network\": \"\",\n" +
  79. " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" +
  80. " \"webhooksPassphrase\": \"\"\n" +
  81. "}";
  82. System.out.println(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"));
  83. System.out.println(AESUtil.decrypt(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"), "bfa5559109f94c78af615bcf00d52060"));
  84. }
  85. public static void main(String[] args) {
  86. String s = "{\n" +
  87. " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" +
  88. " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" +
  89. " \"passphrase\": \"7UGMi2*t0h\",\n" +
  90. " \"vaultId\": \"\",\n" +
  91. " \"baseUrl\": \"https://rest.vaultody.com\",\n" +
  92. " \"vaultsListUrl\": \"/vaults/%s\",\n" +
  93. " \"networkType\": \"test\",\n" +
  94. " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" +
  95. " \"webhooksPassphrase\": \"\",\n" +
  96. " \"vaultodyList\": [\n" +
  97. " {\n" +
  98. " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" +
  99. " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" +
  100. " \"passphrase\": \"7UGMi2*t0h\",\n" +
  101. " \"vaultId\": \"69cb34038d64830006453c0c\",\n" +
  102. " \"baseUrl\": \"https://rest.vaultody.com\",\n" +
  103. " \"vaultsListUrl\": \"/vaults/%s\",\n" +
  104. " \"networkType\": \"test\",\n" +
  105. " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" +
  106. " \"webhooksPassphrase\": \"\"\n" +
  107. " },\n" +
  108. " {\n" +
  109. " \"apiKey\": \"002key\",\n" +
  110. " \"apiSecret\": \"002pwd\",\n" +
  111. " \"passphrase\": \"7UGMi2*t0h\",\n" +
  112. " \"vaultId\": \"002\",\n" +
  113. " \"baseUrl\": \"https://rest.vaultody.com\",\n" +
  114. " \"vaultsListUrl\": \"/vaults/%s\",\n" +
  115. " \"networkType\": \"test\",\n" +
  116. " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" +
  117. " \"webhooksPassphrase\": \"\"\n" +
  118. " }\n" +
  119. " ]\n" +
  120. "}";
  121. System.out.println(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"));
  122. System.out.println(AESUtil.decrypt(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"), "bfa5559109f94c78af615bcf00d52060"));
  123. }
  124. /**
  125. * Coin
  126. */
  127. public static void main1(String[] args) {
  128. try {
  129. List<VaultodyVaultsListDto> dtos = new ArrayList<>();
  130. // ------------------ 配置 ------------------
  131. String apiKey = "6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2";
  132. String apiSecret = "MLjTUAYgxSM2dg=="; // Base64编码的secret
  133. String passphrase = "7UGMi2*t0h";
  134. String method = "GET";
  135. String requestPath = "/vaults/test";
  136. String baseUrl = "https://rest.vaultody.com";
  137. String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写
  138. String body = "{}"; // POST 时 query 通常为空,否则按接口要求填写
  139. // ------------------ 时间戳(秒) ------------------
  140. String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
  141. // ------------------ 构建消息用于签名 ------------------
  142. String message = timestamp + method + requestPath + body + query;
  143. System.out.println("Message: " + message);
  144. String signature = getSignature(message, apiSecret);
  145. System.out.println("Signature: " + signature);
  146. Map<String, String> headers = new HashMap<>();
  147. headers.put("x-api-key", apiKey);
  148. headers.put("x-api-sign", signature);
  149. headers.put("x-api-timestamp", timestamp);
  150. headers.put("x-api-passphrase", passphrase);
  151. headers.put("Content-Type", "application/json");
  152. try {
  153. Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers);
  154. if (response.statusCode() != 200){
  155. System.out.println("Error: " + response.statusMessage());
  156. }
  157. VaultsListResponseDto responseDto = JSON.parseObject(response.body(), VaultsListResponseDto.class);
  158. List<VaultsListResponseDto.Item> items = responseDto.getData().getItems();
  159. for (VaultsListResponseDto.Item item : items){
  160. VaultodyVaultsListDto dto = new VaultodyVaultsListDto();
  161. BeanUtils.copyProperties(item, dto);
  162. dtos.add(dto);
  163. }
  164. System.out.println(JSON.toJSONString(dtos));
  165. } catch (IOException e) {
  166. throw new RuntimeException(e);
  167. }
  168. } catch (Exception e) {
  169. e.printStackTrace();
  170. }
  171. }
  172. /**
  173. * 交易记录接口拼接url
  174. */
  175. public String getPath(String pathTemplate, String vaultId) {
  176. // String pathTemplate = "/vaults/%s/transactions";
  177. String actualPath = String.format(pathTemplate, vaultId);
  178. return actualPath;
  179. }
  180. /**
  181. * 交易记录接口拼接url
  182. */
  183. public String getVaultsPath(String pathTemplate, String networkType) {
  184. // String pathTemplate = "/vaults/{networkType}";
  185. String actualPath = String.format(pathTemplate, networkType);
  186. return actualPath;
  187. }
  188. public VaultodyConfig getVaultodyConfig() {
  189. SysVaultodyConfigTable configTable = vaultodyConfigService.getByCode(ConfigConstants.VAULTODY_MANAGER_CONFIG);
  190. if (configTable == null) {
  191. throw ServiceException.exception(Constants.SYSTEM_ERROR);
  192. }
  193. String aesKey = getPropertyKey();
  194. String property = AESUtil.decrypt(configTable.getValue(), aesKey);
  195. VaultodyConfig vaultodyConfig = JSON.parseObject(property, VaultodyConfig.class);
  196. return vaultodyConfig;
  197. }
  198. public VaultodyConfig getVaultodyConfig(String vaultId) {
  199. SysVaultodyConfigTable configTable = vaultodyConfigService.getByCode(ConfigConstants.VAULTODY_MANAGER_CONFIG);
  200. if (configTable == null) {
  201. throw ServiceException.exception(Constants.SYSTEM_ERROR);
  202. }
  203. String aesKey = getPropertyKey();
  204. String property = AESUtil.decrypt(configTable.getValue(), aesKey);
  205. VaultodyConfig vaultodyConfig = JSON.parseObject(property, VaultodyConfig.class);
  206. List<VaultodyConfig> vaultodyList = vaultodyConfig.getVaultodyList();
  207. if(CollectionUtils.isEmpty(vaultodyList)){
  208. throw ServiceException.exception(Constants.NOT_PERMIT);
  209. }
  210. Map<String, VaultodyConfig> list = vaultodyList.stream().collect(Collectors.toMap(VaultodyConfig::getVaultId, v -> v));
  211. VaultodyConfig config = new VaultodyConfig();
  212. if(!list.containsKey(vaultId)){
  213. throw ServiceException.exception(Constants.NOT_PERMIT);
  214. }
  215. config = list.get(vaultId);
  216. return config;
  217. }
  218. private String getPropertyKey() throws ServiceException {
  219. SysConfigTable table = sysConfigService.getByCode(ConfigConstants.VAULTODY_FINANCE_PROPERTY_KEY);
  220. if (table == null) {
  221. throw ServiceException.exception(Constants.SYSTEM_ERROR);
  222. }
  223. return table.getValue();
  224. }
  225. @Override
  226. public BaseResultDto vaultsList() throws Exception {
  227. List<VaultodyVaultsListDto> dtos = new ArrayList<>();
  228. VaultodyConfig config = getVaultodyConfig();
  229. String apiKey = config.getApiKey();
  230. String apiSecret = config.getApiSecret();
  231. String passphrase = config.getPassphrase();
  232. String method = "GET";
  233. String requestPath = getVaultsPath(config.getVaultsListUrl(), config.getNetworkType());
  234. String baseUrl = config.getBaseUrl();
  235. String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写
  236. String body = "{}";
  237. // ------------------ 时间戳(秒) ------------------
  238. String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
  239. // ------------------ 构建消息用于签名 ------------------
  240. String message = timestamp + method + requestPath + body + query;
  241. String signature = getSignature(message, apiSecret);
  242. System.out.println("Signature: " + signature);
  243. Map<String, String> headers = new HashMap<>();
  244. headers.put("x-api-key", apiKey);
  245. headers.put("x-api-sign", signature);
  246. headers.put("x-api-timestamp", timestamp);
  247. headers.put("x-api-passphrase", passphrase);
  248. headers.put("Content-Type", "application/json");
  249. Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers);
  250. if (response.statusCode() != 200){
  251. return BaseResultDto.error(response.statusMessage());
  252. }
  253. VaultsListResponseDto responseDto = JSON.parseObject(response.body(), VaultsListResponseDto.class);
  254. List<VaultsListResponseDto.Item> items = responseDto.getData().getItems();
  255. for (VaultsListResponseDto.Item item : items){
  256. VaultodyVaultsListDto dto = new VaultodyVaultsListDto();
  257. BeanUtils.copyProperties(item, dto);
  258. dtos.add(dto);
  259. }
  260. return BaseResultDto.success(dtos);
  261. }
  262. public VaultTransaction query3Items(VaultTransactionsEntity entity,VaultodyConfig config) throws Exception{
  263. String apiKey = config.getApiKey();
  264. String apiSecret = config.getApiSecret(); // Base64编码的secret
  265. String passphrase = config.getPassphrase();
  266. String method = "GET";
  267. // String requestPath = "/vaults/"+entity.getVaultId()+"/transactions";
  268. String requestPath = getPath(config.getVaultsTransactionsPathTemplate(), entity.getVaultId());
  269. String baseUrl = config.getBaseUrl();
  270. String query = "{}";
  271. Map<String, String> params = new HashMap();
  272. if(!ObjectUtils.isEmpty(entity.getLimit())){
  273. params.put("limit", String.valueOf(entity.getLimit()));
  274. }
  275. if(StringUtils.isNotBlank(entity.getStartingAfter())){
  276. params.put("startingAfter", entity.getStartingAfter());
  277. }
  278. if (StringUtils.isNotBlank(entity.getContext())){
  279. params.put("context", entity.getContext());
  280. }
  281. // ------------------ 时间戳(秒) ------------------
  282. String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
  283. // ------------------ 构建消息用于签名 ------------------
  284. String message = timestamp + method + requestPath + query + JSON.toJSONString(params);
  285. String signature = getSignature(message, apiSecret);
  286. log.info("Signature: {}",signature);
  287. Map<String, String> headers = new HashMap<>();
  288. headers.put("x-api-key", apiKey);
  289. headers.put("x-api-sign", signature);
  290. headers.put("x-api-timestamp", timestamp);
  291. headers.put("x-api-passphrase", passphrase);
  292. headers.put("Content-Type", "application/json");
  293. Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers, params);
  294. log.info("response.statusCode: {} , response.body: {}",response.statusCode() , response.body());
  295. if (response.statusCode() != 200) {
  296. log.error("response.statusCode() != 200,{}","3Item Error!");
  297. throw ServiceException.exception(Constants.SYSTEM_ERROR);
  298. }
  299. TransactionResponse responseDto = JSON.parseObject(response.body(), TransactionResponse.class);
  300. ResponseData data = responseDto.getData();
  301. VaultTransaction vaultTransaction = getVaultTransaction(data);
  302. return vaultTransaction;
  303. }
  304. private VaultTransaction getVaultTransaction(ResponseData data) {
  305. VaultTransaction vaultTransaction = new VaultTransaction();
  306. vaultTransaction.setHasMore(data.getHasMore());
  307. vaultTransaction.setLimit(data.getLimit());
  308. vaultTransaction.setStartingAfter(data.getStartingAfter());
  309. List<TransactionItemDto> list = Lists.newArrayList();
  310. for (TransactionItem item : data.getItems()) {
  311. TransactionItemDto dto = new TransactionItemDto();
  312. dto.setRequestId(UUIDUtil.getUUID());
  313. dto.setId(item.getId());
  314. dto.setTransactionId(item.getTransactionId());
  315. dto.setStatus(item.getStatus());
  316. dto.setCreatedTimestamp(item.getCreatedTimestamp());
  317. dto.setSenderAddress(item.getSenders().get(0).getAddress());
  318. dto.setSenderIsVaultAddress(item.getSenders().get(0).getIsVaultAddress());
  319. dto.setSenderAmountUnit(item.getSenders().get(0).getAmountUnit());
  320. dto.setSenderAmount(item.getSenders().get(0).getAmount());
  321. dto.setRecipientAddress(item.getRecipients().get(0).getAddress());
  322. dto.setRecipientIsVaultAddress(item.getRecipients().get(0).getIsVaultAddress());
  323. dto.setRecipientAmountUnit(item.getRecipients().get(0).getAmountUnit());
  324. dto.setRecipientAmount(item.getRecipients().get(0).getAmount());
  325. dto.setBlockchain(item.getBlockchain());
  326. dto.setMinedInBlockHeight(item.getMinedInBlockHeight());
  327. dto.setFeeAmount(item.getTransactionFee().getAmount());
  328. dto.setFeeAmountUnit(item.getTransactionFee().getAmountUnit());
  329. dto.setSenderLabel(item.getSenders().get(0).getLabel());
  330. dto.setRecipientLabel(item.getRecipients().get(0).getLabel());
  331. list.add(dto);
  332. }
  333. vaultTransaction.setList(list);
  334. return vaultTransaction;
  335. }
  336. @Override
  337. public void batchSave(List<TransactionItemTable> tables) {
  338. transactionItemRepository.saveAll(tables);
  339. }
  340. @Override
  341. public List<TransactionItemTable> finAllByVaultId(String vaultId) {
  342. return transactionItemRepository.findAllByVaultId(vaultId);
  343. }
  344. @Override
  345. public BaseResultDto searchList(VaultTransactionsSearchEntity entity) throws Exception {
  346. List<TransactionItemTable> tables = new LinkedList<>();
  347. VaultodyConfig vaultodyConfig = getVaultodyConfig(entity.getVaultId());
  348. List<TransactionItemDto> list = queryWithFilter(vaultodyConfig);
  349. if(!CollectionUtils.isEmpty(list)){
  350. for (TransactionItemDto transactionItemDto : list) {
  351. TransactionItemTable table = new TransactionItemTable() ;
  352. BeanUtils.copyProperties(transactionItemDto, table);
  353. table.setVaultId(entity.getVaultId());
  354. table.setItemId(transactionItemDto.getId());
  355. tables.add(table);
  356. }
  357. batchSave(tables);
  358. }
  359. Long startSecond = null;
  360. Long endSecond = null;
  361. if(entity.getStartTime() != null){
  362. startSecond = DateUtils.dateToSecondTimestamp(entity.getStartTime());
  363. }
  364. if (entity.getEndTime() != null){
  365. endSecond = DateUtils.dateToSecondTimestamp(entity.getEndTime());
  366. }
  367. Integer count = transactionItemMapper.countList(entity,startSecond,endSecond);
  368. if (count == null || count <= 0) {
  369. return ResultWithPagerDto.success(new PageDto(), new ArrayList<>());
  370. }
  371. PageDto pageDto = PageDto.format(entity, count);
  372. List<TransactionItemTable> dtos = transactionItemMapper.pageList(entity,startSecond,endSecond);
  373. if (dtos == null || dtos.size() <= 0) {
  374. throw new ServiceException(Constants.SYSTEM_ERROR);
  375. }
  376. return ResultWithPagerDto.success(pageDto, dtos);
  377. }
  378. public List<TransactionItemDto> queryWithFilter(VaultodyConfig config) throws Exception {
  379. VaultTransactionsEntity entity = new VaultTransactionsEntity();
  380. entity.setVaultId(config.getVaultId());
  381. List<TransactionItemDto> result = new ArrayList<>();
  382. // 先获取数据库中已存在的item ID列表
  383. List<String> existingItemIds = recordByVaultId(config.getVaultId());
  384. Set<String> existingIdSet = new HashSet<>(existingItemIds);
  385. VaultTransaction vaultTransaction = query3Items(entity,config);
  386. // 处理第一页数据
  387. if (vaultTransaction.getList() != null && !vaultTransaction.getList().isEmpty()) {
  388. List<TransactionItemDto> filteredList = filterExistingItems(vaultTransaction.getList(), existingIdSet);
  389. result.addAll(filteredList);
  390. }
  391. // 分页查询剩余数据
  392. while (Boolean.TRUE.equals(vaultTransaction.getHasMore())
  393. && vaultTransaction.getList() != null
  394. && !vaultTransaction.getList().isEmpty()) {
  395. String lastId = vaultTransaction.getList().get(vaultTransaction.getList().size() - 1).getId();
  396. entity.setStartingAfter(lastId);
  397. vaultTransaction = query3Items(entity,config);
  398. if (vaultTransaction.getList() != null && !vaultTransaction.getList().isEmpty()) {
  399. List<TransactionItemDto> filteredList = filterExistingItems(vaultTransaction.getList(), existingIdSet);
  400. result.addAll(filteredList);
  401. } else {
  402. break;
  403. }
  404. }
  405. return result;
  406. }
  407. private List<TransactionItemDto> filterExistingItems(List<TransactionItemDto> items, Set<String> existingIdSet) {
  408. if (items == null || items.isEmpty()) {
  409. return Collections.emptyList();
  410. }
  411. return items.stream()
  412. .filter(item -> item != null && item.getId() != null)
  413. .filter(item -> !existingIdSet.contains(item.getId()))
  414. .collect(Collectors.toList());
  415. }
  416. private List<String> recordByVaultId(String vaultId){
  417. List<TransactionItemTable> list = finAllByVaultId(vaultId);
  418. if(CollectionUtils.isEmpty(list)){
  419. return new ArrayList<>();
  420. }
  421. return list.stream().map(TransactionItemTable::getItemId).toList();
  422. }
  423. }