package com.crm.manager.service.impl; import com.alibaba.fastjson2.JSON; import com.crm.manager.dao.mapper.TransactionItemMapper; import com.crm.manager.repository.TransactionItemRepository; import com.crm.manager.service.SysConfigService; import com.crm.manager.service.SysVaultodyConfigService; import com.crm.manager.service.VaultodyService; import com.crm.manager.util.DateUtils; import com.crm.rely.backend.core.constant.Constants; import com.crm.rely.backend.core.dto.base.BaseResultDto; import com.crm.rely.backend.core.dto.base.PageDto; import com.crm.rely.backend.core.dto.base.ResultWithPagerDto; import com.crm.rely.backend.core.exception.ServiceException; import com.crm.rely.backend.core.pojo.table.SysConfigTable; import com.crm.rely.backend.model.config.VaultodyConfig; import com.crm.rely.backend.model.constant.ConfigConstants; import com.crm.rely.backend.model.dto.vaultody.vaults.TransactionItemDto; import com.crm.rely.backend.model.dto.vaultody.vaults.VaultTransaction; import com.crm.rely.backend.model.dto.vaultody.vaults.VaultodyVaultsListDto; import com.crm.rely.backend.model.dto.vaultody.vaults.response.ResponseData; import com.crm.rely.backend.model.dto.vaultody.vaults.response.TransactionItem; import com.crm.rely.backend.model.dto.vaultody.vaults.response.TransactionResponse; import com.crm.rely.backend.model.dto.vaultody.vaults.response.VaultsListResponseDto; import com.crm.rely.backend.model.entity.vaultody.vaults.VaultTransactionsEntity; import com.crm.rely.backend.model.entity.vaultody.vaults.VaultTransactionsSearchEntity; import com.crm.rely.backend.model.pojo.table.SysVaultodyConfigTable; import com.crm.rely.backend.model.pojo.table.TransactionItemTable; import com.crm.rely.backend.util.AESUtil; import com.crm.rely.backend.util.HttpUtil; import com.crm.rely.backend.util.UUIDUtil; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jsoup.Connection; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; @Slf4j @Service public class VaultodyServiceImpl implements VaultodyService { @Autowired private SysVaultodyConfigService vaultodyConfigService; @Autowired private SysConfigService sysConfigService; @Autowired private TransactionItemRepository transactionItemRepository; @Autowired private TransactionItemMapper transactionItemMapper; public static String getSignature(String message, String apiSecret) { try { byte[] decodedSecret = Base64.getDecoder().decode(apiSecret); Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(decodedSecret, "HmacSHA256"); mac.init(secretKeySpec); byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(hash); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main2(String[] args) { String s = "{\n" + " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" + " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" + " \"passphrase\": \"7UGMi2*t0h\",\n" + " \"vaultId\": \"\",\n" + " \"baseUrl\": \"https://rest.vaultody.com\",\n" + " \"vaultsListUrl\": \"/vaults/test\",\n" + " \"network\": \"\",\n" + " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" + " \"webhooksPassphrase\": \"\"\n" + "}"; System.out.println(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060")); System.out.println(AESUtil.decrypt(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"), "bfa5559109f94c78af615bcf00d52060")); } public static void main(String[] args) { String s = "{\n" + " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" + " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" + " \"passphrase\": \"7UGMi2*t0h\",\n" + " \"vaultId\": \"\",\n" + " \"baseUrl\": \"https://rest.vaultody.com\",\n" + " \"vaultsListUrl\": \"/vaults/%s\",\n" + " \"networkType\": \"test\",\n" + " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" + " \"webhooksPassphrase\": \"\",\n" + " \"vaultodyList\": [\n" + " {\n" + " \"apiKey\": \"6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2\",\n" + " \"apiSecret\": \"MLjTUAYgxSM2dg==\",\n" + " \"passphrase\": \"7UGMi2*t0h\",\n" + " \"vaultId\": \"69cb34038d64830006453c0c\",\n" + " \"baseUrl\": \"https://rest.vaultody.com\",\n" + " \"vaultsListUrl\": \"/vaults/%s\",\n" + " \"networkType\": \"test\",\n" + " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" + " \"webhooksPassphrase\": \"\"\n" + " },\n" + " {\n" + " \"apiKey\": \"002key\",\n" + " \"apiSecret\": \"002pwd\",\n" + " \"passphrase\": \"7UGMi2*t0h\",\n" + " \"vaultId\": \"002\",\n" + " \"baseUrl\": \"https://rest.vaultody.com\",\n" + " \"vaultsListUrl\": \"/vaults/%s\",\n" + " \"networkType\": \"test\",\n" + " \"vaultsTransactionsPathTemplate\": \"/vaults/%s/transactions\",\n" + " \"webhooksPassphrase\": \"\"\n" + " }\n" + " ]\n" + "}"; System.out.println(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060")); System.out.println(AESUtil.decrypt(AESUtil.encrypt(s, "bfa5559109f94c78af615bcf00d52060"), "bfa5559109f94c78af615bcf00d52060")); } /** * Coin */ public static void main1(String[] args) { try { List dtos = new ArrayList<>(); // ------------------ 配置 ------------------ String apiKey = "6f3cc6caf513a5cde2df5d3ed805e3703d4d43b2"; String apiSecret = "MLjTUAYgxSM2dg=="; // Base64编码的secret String passphrase = "7UGMi2*t0h"; String method = "GET"; String requestPath = "/vaults/test"; String baseUrl = "https://rest.vaultody.com"; String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写 String body = "{}"; // POST 时 query 通常为空,否则按接口要求填写 // ------------------ 时间戳(秒) ------------------ String timestamp = String.valueOf(System.currentTimeMillis() / 1000); // ------------------ 构建消息用于签名 ------------------ String message = timestamp + method + requestPath + body + query; System.out.println("Message: " + message); String signature = getSignature(message, apiSecret); System.out.println("Signature: " + signature); Map headers = new HashMap<>(); headers.put("x-api-key", apiKey); headers.put("x-api-sign", signature); headers.put("x-api-timestamp", timestamp); headers.put("x-api-passphrase", passphrase); headers.put("Content-Type", "application/json"); try { Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers); if (response.statusCode() != 200){ System.out.println("Error: " + response.statusMessage()); } VaultsListResponseDto responseDto = JSON.parseObject(response.body(), VaultsListResponseDto.class); List items = responseDto.getData().getItems(); for (VaultsListResponseDto.Item item : items){ VaultodyVaultsListDto dto = new VaultodyVaultsListDto(); BeanUtils.copyProperties(item, dto); dtos.add(dto); } System.out.println(JSON.toJSONString(dtos)); } catch (IOException e) { throw new RuntimeException(e); } } catch (Exception e) { e.printStackTrace(); } } /** * 交易记录接口拼接url */ public String getPath(String pathTemplate, String vaultId) { // String pathTemplate = "/vaults/%s/transactions"; String actualPath = String.format(pathTemplate, vaultId); return actualPath; } /** * 交易记录接口拼接url */ public String getVaultsPath(String pathTemplate, String networkType) { // String pathTemplate = "/vaults/{networkType}"; String actualPath = String.format(pathTemplate, networkType); return actualPath; } public VaultodyConfig getVaultodyConfig() { SysVaultodyConfigTable configTable = vaultodyConfigService.getByCode(ConfigConstants.VAULTODY_MANAGER_CONFIG); if (configTable == null) { throw ServiceException.exception(Constants.SYSTEM_ERROR); } String aesKey = getPropertyKey(); String property = AESUtil.decrypt(configTable.getValue(), aesKey); VaultodyConfig vaultodyConfig = JSON.parseObject(property, VaultodyConfig.class); return vaultodyConfig; } public VaultodyConfig getVaultodyConfig(String vaultId) { SysVaultodyConfigTable configTable = vaultodyConfigService.getByCode(ConfigConstants.VAULTODY_MANAGER_CONFIG); if (configTable == null) { throw ServiceException.exception(Constants.SYSTEM_ERROR); } String aesKey = getPropertyKey(); String property = AESUtil.decrypt(configTable.getValue(), aesKey); VaultodyConfig vaultodyConfig = JSON.parseObject(property, VaultodyConfig.class); List vaultodyList = vaultodyConfig.getVaultodyList(); if(CollectionUtils.isEmpty(vaultodyList)){ throw ServiceException.exception(Constants.NOT_PERMIT); } Map list = vaultodyList.stream().collect(Collectors.toMap(VaultodyConfig::getVaultId, v -> v)); VaultodyConfig config = new VaultodyConfig(); if(!list.containsKey(vaultId)){ throw ServiceException.exception(Constants.NOT_PERMIT); } config = list.get(vaultId); return config; } private String getPropertyKey() throws ServiceException { SysConfigTable table = sysConfigService.getByCode(ConfigConstants.VAULTODY_FINANCE_PROPERTY_KEY); if (table == null) { throw ServiceException.exception(Constants.SYSTEM_ERROR); } return table.getValue(); } @Override public BaseResultDto vaultsList() throws Exception { List dtos = new ArrayList<>(); VaultodyConfig config = getVaultodyConfig(); String apiKey = config.getApiKey(); String apiSecret = config.getApiSecret(); String passphrase = config.getPassphrase(); String method = "GET"; String requestPath = getVaultsPath(config.getVaultsListUrl(), config.getNetworkType()); String baseUrl = config.getBaseUrl(); String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写 String body = "{}"; // ------------------ 时间戳(秒) ------------------ String timestamp = String.valueOf(System.currentTimeMillis() / 1000); // ------------------ 构建消息用于签名 ------------------ String message = timestamp + method + requestPath + body + query; String signature = getSignature(message, apiSecret); System.out.println("Signature: " + signature); Map headers = new HashMap<>(); headers.put("x-api-key", apiKey); headers.put("x-api-sign", signature); headers.put("x-api-timestamp", timestamp); headers.put("x-api-passphrase", passphrase); headers.put("Content-Type", "application/json"); Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers); if (response.statusCode() != 200){ return BaseResultDto.error(response.statusMessage()); } VaultsListResponseDto responseDto = JSON.parseObject(response.body(), VaultsListResponseDto.class); List items = responseDto.getData().getItems(); for (VaultsListResponseDto.Item item : items){ VaultodyVaultsListDto dto = new VaultodyVaultsListDto(); BeanUtils.copyProperties(item, dto); dtos.add(dto); } return BaseResultDto.success(dtos); } public VaultTransaction query3Items(VaultTransactionsEntity entity,VaultodyConfig config) throws Exception{ String apiKey = config.getApiKey(); String apiSecret = config.getApiSecret(); // Base64编码的secret String passphrase = config.getPassphrase(); String method = "GET"; // String requestPath = "/vaults/"+entity.getVaultId()+"/transactions"; String requestPath = getPath(config.getVaultsTransactionsPathTemplate(), entity.getVaultId()); String baseUrl = config.getBaseUrl(); String query = "{}"; Map params = new HashMap(); if(!ObjectUtils.isEmpty(entity.getLimit())){ params.put("limit", String.valueOf(entity.getLimit())); } if(StringUtils.isNotBlank(entity.getStartingAfter())){ params.put("startingAfter", entity.getStartingAfter()); } if (StringUtils.isNotBlank(entity.getContext())){ params.put("context", entity.getContext()); } // ------------------ 时间戳(秒) ------------------ String timestamp = String.valueOf(System.currentTimeMillis() / 1000); // ------------------ 构建消息用于签名 ------------------ String message = timestamp + method + requestPath + query + JSON.toJSONString(params); String signature = getSignature(message, apiSecret); log.info("Signature: {}",signature); Map headers = new HashMap<>(); headers.put("x-api-key", apiKey); headers.put("x-api-sign", signature); headers.put("x-api-timestamp", timestamp); headers.put("x-api-passphrase", passphrase); headers.put("Content-Type", "application/json"); Connection.Response response = HttpUtil.get(baseUrl + requestPath, headers, params); log.info("response.statusCode: {} , response.body: {}",response.statusCode() , response.body()); if (response.statusCode() != 200) { log.error("response.statusCode() != 200,{}","3Item Error!"); throw ServiceException.exception(Constants.SYSTEM_ERROR); } TransactionResponse responseDto = JSON.parseObject(response.body(), TransactionResponse.class); ResponseData data = responseDto.getData(); VaultTransaction vaultTransaction = getVaultTransaction(data); return vaultTransaction; } private VaultTransaction getVaultTransaction(ResponseData data) { VaultTransaction vaultTransaction = new VaultTransaction(); vaultTransaction.setHasMore(data.getHasMore()); vaultTransaction.setLimit(data.getLimit()); vaultTransaction.setStartingAfter(data.getStartingAfter()); List list = Lists.newArrayList(); for (TransactionItem item : data.getItems()) { TransactionItemDto dto = new TransactionItemDto(); dto.setRequestId(UUIDUtil.getUUID()); dto.setId(item.getId()); dto.setTransactionId(item.getTransactionId()); dto.setStatus(item.getStatus()); dto.setCreatedTimestamp(item.getCreatedTimestamp()); dto.setSenderAddress(item.getSenders().get(0).getAddress()); dto.setSenderIsVaultAddress(item.getSenders().get(0).getIsVaultAddress()); dto.setSenderAmountUnit(item.getSenders().get(0).getAmountUnit()); dto.setSenderAmount(item.getSenders().get(0).getAmount()); dto.setRecipientAddress(item.getRecipients().get(0).getAddress()); dto.setRecipientIsVaultAddress(item.getRecipients().get(0).getIsVaultAddress()); dto.setRecipientAmountUnit(item.getRecipients().get(0).getAmountUnit()); dto.setRecipientAmount(item.getRecipients().get(0).getAmount()); dto.setBlockchain(item.getBlockchain()); dto.setMinedInBlockHeight(item.getMinedInBlockHeight()); dto.setFeeAmount(item.getTransactionFee().getAmount()); dto.setFeeAmountUnit(item.getTransactionFee().getAmountUnit()); dto.setSenderLabel(item.getSenders().get(0).getLabel()); dto.setRecipientLabel(item.getRecipients().get(0).getLabel()); list.add(dto); } vaultTransaction.setList(list); return vaultTransaction; } @Override public void batchSave(List tables) { transactionItemRepository.saveAll(tables); } @Override public List finAllByVaultId(String vaultId) { return transactionItemRepository.findAllByVaultId(vaultId); } @Override public BaseResultDto searchList(VaultTransactionsSearchEntity entity) throws Exception { List tables = new LinkedList<>(); VaultodyConfig vaultodyConfig = getVaultodyConfig(entity.getVaultId()); List list = queryWithFilter(vaultodyConfig); if(!CollectionUtils.isEmpty(list)){ for (TransactionItemDto transactionItemDto : list) { TransactionItemTable table = new TransactionItemTable() ; BeanUtils.copyProperties(transactionItemDto, table); table.setVaultId(entity.getVaultId()); table.setItemId(transactionItemDto.getId()); tables.add(table); } batchSave(tables); } Long startSecond = null; Long endSecond = null; if(entity.getStartTime() != null){ startSecond = DateUtils.dateToSecondTimestamp(entity.getStartTime()); } if (entity.getEndTime() != null){ endSecond = DateUtils.dateToSecondTimestamp(entity.getEndTime()); } Integer count = transactionItemMapper.countList(entity,startSecond,endSecond); if (count == null || count <= 0) { return ResultWithPagerDto.success(new PageDto(), new ArrayList<>()); } PageDto pageDto = PageDto.format(entity, count); List dtos = transactionItemMapper.pageList(entity,startSecond,endSecond); if (dtos == null || dtos.size() <= 0) { throw new ServiceException(Constants.SYSTEM_ERROR); } return ResultWithPagerDto.success(pageDto, dtos); } public List queryWithFilter(VaultodyConfig config) throws Exception { VaultTransactionsEntity entity = new VaultTransactionsEntity(); entity.setVaultId(config.getVaultId()); List result = new ArrayList<>(); // 先获取数据库中已存在的item ID列表 List existingItemIds = recordByVaultId(config.getVaultId()); Set existingIdSet = new HashSet<>(existingItemIds); VaultTransaction vaultTransaction = query3Items(entity,config); // 处理第一页数据 if (vaultTransaction.getList() != null && !vaultTransaction.getList().isEmpty()) { List filteredList = filterExistingItems(vaultTransaction.getList(), existingIdSet); result.addAll(filteredList); } // 分页查询剩余数据 while (Boolean.TRUE.equals(vaultTransaction.getHasMore()) && vaultTransaction.getList() != null && !vaultTransaction.getList().isEmpty()) { String lastId = vaultTransaction.getList().get(vaultTransaction.getList().size() - 1).getId(); entity.setStartingAfter(lastId); vaultTransaction = query3Items(entity,config); if (vaultTransaction.getList() != null && !vaultTransaction.getList().isEmpty()) { List filteredList = filterExistingItems(vaultTransaction.getList(), existingIdSet); result.addAll(filteredList); } else { break; } } return result; } private List filterExistingItems(List items, Set existingIdSet) { if (items == null || items.isEmpty()) { return Collections.emptyList(); } return items.stream() .filter(item -> item != null && item.getId() != null) .filter(item -> !existingIdSet.contains(item.getId())) .collect(Collectors.toList()); } private List recordByVaultId(String vaultId){ List list = finAllByVaultId(vaultId); if(CollectionUtils.isEmpty(list)){ return new ArrayList<>(); } return list.stream().map(TransactionItemTable::getItemId).toList(); } }