package top.lvzhiqiang.service.impl; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpGroupRobotService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; import me.chanjar.weixin.cp.bean.article.NewArticle; import me.chanjar.weixin.cp.bean.message.WxCpMessage; import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; import org.apache.commons.lang3.time.DurationFormatUtils; import org.jsoup.Connection; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StopWatch; import top.lvzhiqiang.config.InitRunner; import top.lvzhiqiang.config.WorkWeixinProperties; import top.lvzhiqiang.dto.CoinYoutubeYt2140LiveChapterDTO; import top.lvzhiqiang.dto.R; import top.lvzhiqiang.entity.*; import top.lvzhiqiang.enumeration.ResultCodeEnum; import top.lvzhiqiang.exception.BusinessException; import top.lvzhiqiang.exception.ParameterException; import top.lvzhiqiang.mapper.*; import top.lvzhiqiang.service.CoinService; import top.lvzhiqiang.util.*; import javax.annotation.Resource; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.net.Proxy; import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Coin ServiceImpl * * @author lvzhiqiang * 2023/9/5 15:23 */ @Service @Slf4j public class CoinServiceImpl implements CoinService { /** * 任务告警方式-应用文本卡片 */ public static final String JOB_ALARM_MODE_APP_TEXT_CARD = "1"; /** * 任务告警方式-群聊机器人 */ public static final String JOB_ALARM_MODE_CHAT_BOT = "2"; /** * 任务告警方式(1:应用文本卡片,2:群聊机器人文本消息) */ public static String JOB_ALARM_MODE = "1"; // 所有REST请求的header都必须包含以下key: private static final Map basicHeaderMap = new HashMap<>(); private static final Map basicHeaderMap4OKX = new HashMap<>(); // 主域名 URL private static final String mainUrl = "https://api.bitget.com"; // 私钥,由系统随机生成,用于签名的生成。 private static final String secretKey = "1fdd0fc2976bea80189ba13710e12825ca3ef6c5e25a0d76fd03f8f6cd4a61d9"; private static final String secretKey4OKX = "32AC470662FBB633374B9A41950995A9"; @Resource private CoinMapper coinMapper; @Resource private WxCpService wxCpService; @Autowired(required = false) private WorkWeixinProperties properties; private final Map orderMap = new ConcurrentHashMap<>(); private final Map mixMap = new ConcurrentHashMap<>(); private final static ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(10); private final ForkJoinPool forkJoinPool = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool2 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool3 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool4 = new ForkJoinPool(16); private final ForkJoinPool forkJoinPool5 = new ForkJoinPool(16); private static final DecimalFormat df1 = new DecimalFormat("#,##0.00"); private static final DecimalFormat df2 = new DecimalFormat("#,##0"); private static final WxCpServiceImpl wxCpService4News; @Resource private CoinApiConfigMapper coinApiConfigMapper; @Resource private PictureInfoMapper pictureInfoMapper; @Resource private MusicInfoMapper musicInfoMapper; @Resource private CoinYoutubeMapper coinYoutubeMapper; @Resource private GoldenQuotesMapper goldenQuotesMapper; @Resource private BookmarkMapper bookmarkMapper; @Resource private RedissonClient redissonClient; @Resource private RedisUtils redisUtils; static { // API KEY作为一个字符串。 basicHeaderMap.put("ACCESS-KEY", "bg_433d37306df0e8901c6d107c6d9e9111"); // 使用base64编码签名(请参阅签名消息)。 basicHeaderMap.put("ACCESS-SIGN", ""); // 您请求的时间戳。 basicHeaderMap.put("ACCESS-TIMESTAMP", ""); // 您在创建API KEY时设置的口令。 basicHeaderMap.put("ACCESS-PASSPHRASE", "7f934f62f2701bee932204580d115228"); // 统一设置为application/json。 basicHeaderMap.put("Content-Type", "application/json"); // 支持多语言, 如:中文(zh-CN),英语(en-US) basicHeaderMap.put("locale", "zh-CN"); // 字符串类型的APIKey basicHeaderMap4OKX.put("OK-ACCESS-KEY", "25e4f515-5efd-4bb9-a934-3949b21d9f10"); // 使用HMAC SHA256哈希函数获得哈希值,再使用Base-64编码(请参阅签名) basicHeaderMap4OKX.put("OK-ACCESS-SIGN", ""); // 发起请求的时间(UTC),如:2020-12-08T09:08:57.715Z basicHeaderMap4OKX.put("OK-ACCESS-TIMESTAMP", ""); // 您在创建API密钥时指定的Passphrase basicHeaderMap4OKX.put("OK-ACCESS-PASSPHRASE", "tmvxeGY#Q#Y2qm8"); // 统一设置为application/json basicHeaderMap4OKX.put("Content-Type", "application/json"); df1.setRoundingMode(RoundingMode.HALF_UP); WxCpDefaultConfigImpl wxCpDefaultConfig = new WxCpDefaultConfigImpl(); wxCpDefaultConfig.setCorpId("ww95a4adba56acb55f"); wxCpDefaultConfig.setAgentId(1000004); wxCpDefaultConfig.setCorpSecret("hG50gbVZ8pXm3tSzY3BpwTzbrMmm6sTf8_bPfJG_6Yc"); wxCpService4News = new WxCpServiceImpl(); wxCpService4News.setWxCpConfigStorage(wxCpDefaultConfig); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void syncData(String startTime, String endTime, String pageSize) { // 获取全部历史委托 Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("startTime", startTime); paramMap.put("endTime", endTime); paramMap.put("pageSize", pageSize); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = null; try { response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList"); if (orderList != null && orderList.size() > 0) { coinMapper.insertHistoryOrderList(JSONArray.parseArray(orderList.toJSONString(), CoinHistoryOrder.class)); log.warn("syncData->insertHistoryOrderList,startTime={},endTime={},size={}", startTime, endTime, orderList.size()); } } catch (Exception e) { log.error("syncData->insertHistoryOrderList error,response={}", response, e); } } @Override public void syncData4TraderList() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 获取交易员列表 Map paramMap = new LinkedHashMap<>(); paramMap.put("sortRule", "composite"); paramMap.put("sortFlag", "desc"); paramMap.put("languageType", "en-US"); paramMap.put("pageSize", "20"); int i = 0; String url = "/api/mix/v1/trace/traderList"; JSONObject response; int totalNum = 0; for (; ; ) { paramMap.put("pageNo", String.valueOf(++i)); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); response = requestApi4Common(url, signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray dataList = response.getJSONArray("data"); if (dataList.size() == 0) { break; } try { Thread.sleep(5000L); syncData4TraderListSub(dataList); } catch (Exception e) { log.error("syncData4TraderListSub error,paramMap={},response={}", paramMap, response.toJSONString(), e); } totalNum += dataList.size(); } log.warn("syncData4TraderList 结束:time={},totalNum={}", stopWatch.getTotalTimeSeconds(), totalNum); } @Override public void syncCoinmarketcapCMap() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); String coinmarketcapApikey = InitRunner.dicCodeMap.get("coinmarketcap_apikey").getCodeValue(); String coinmarketcapIdmapUrl = InitRunner.dicCodeMap.get("coinmarketcap_idmap_url").getCodeValue(); String coinmarketcapIdmapParams4listingStatus = InitRunner.dicCodeMap.get("coinmarketcap_idmap_params_listing_status").getCodeValue(); String coinmarketcapIdmapParams4aux = InitRunner.dicCodeMap.get("coinmarketcap_idmap_params_aux").getCodeValue(); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); headerMap.put("X-CMC_PRO_API_KEY", coinmarketcapApikey); String[] listingStatusArr = coinmarketcapIdmapParams4listingStatus.split(","); Map paramMap = new LinkedHashMap<>(); int MAX_NUMBER = 1000; int MAX_NUMBER2 = 5000; Long totalNum = 0L; for (String listingStatus : listingStatusArr) { paramMap.put("listing_status", listingStatus); paramMap.put("aux", coinmarketcapIdmapParams4aux); try { int j = 0; Long totalNum2 = 0L; for (; ; ) { Thread.sleep(3000L); j++; paramMap.put("start", String.valueOf((j - 1) * MAX_NUMBER2 + 1)); paramMap.put("limit", String.valueOf(MAX_NUMBER2)); Connection.Response response = JsoupUtil.requestBody(coinmarketcapIdmapUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap); JSONObject result = JSONObject.parseObject(response.body()); JSONArray dataJA = result.getJSONArray("data"); List cmcMapList = new ArrayList<>(); CoinCmcMap coinCmcMap; for (int i = 0; i < dataJA.size(); i++) { JSONObject dataJO = dataJA.getJSONObject(i); coinCmcMap = new CoinCmcMap(); coinCmcMap.setCmcId(dataJO.getLong("id")); coinCmcMap.setCmcRank(dataJO.getLong("rank")); coinCmcMap.setName(dataJO.getString("name")); coinCmcMap.setSymbol(dataJO.getString("symbol")); coinCmcMap.setSlug(dataJO.getString("slug")); coinCmcMap.setIsActive(dataJO.getInteger("is_active")); coinCmcMap.setStatus(dataJO.getString("status")); coinCmcMap.setFirstHistoricalData(DateUtils.stringutcToLocalDateTime(dataJO.getString("first_historical_data"))); coinCmcMap.setLastHistoricalData(DateUtils.stringutcToLocalDateTime(dataJO.getString("last_historical_data"))); coinCmcMap.setPlatform(dataJO.getString("platform")); cmcMapList.add(coinCmcMap); } // 新增或者更新 Stream.iterate(0, n -> n + 1).limit((cmcMapList.size() + MAX_NUMBER - 1) / MAX_NUMBER) .forEach(i -> { List list = cmcMapList.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toList()); coinMapper.insertCmcMapList(list); }); totalNum += cmcMapList.size(); totalNum2 += cmcMapList.size(); if (dataJA.size() < MAX_NUMBER2) { break; } } log.warn("syncCoinmarketcapCMap {} success,totalNum={}", listingStatus, totalNum2); } catch (Exception e) { log.error("syncCoinmarketcapCMap {} error", listingStatus, e); } } log.warn("syncCoinmarketcapCMap 结束:time={},totalNum={}", stopWatch.getTotalTimeSeconds(), totalNum); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void syncData4TraderListSub(JSONArray dataList) { coinMapper.insertMixTradeList(parseMixTradeList(dataList)); } @Override public String watchlistDetail(Integer userId, String symbol, String operationType) { CoinWatchlist coinWatchlist = coinMapper.findWatchlistUserBySymbolAndUserId(symbol, userId); if (coinWatchlist == null) { throw new BusinessException(1, "symbol不存在!"); } if ("detail".equals(operationType)) { return parseWatchlistOther(symbol) + MarkdownToHtmlUtils.markdownToHtmlExtensions(coinWatchlist.getRemark()); } else if ("update".equals(operationType)) { return coinWatchlist.getRemark(); } else { return "暂不支持该操作!"; } } @Override public Object watchlistUpdate(String symbol, Integer userId, String remark, String score) { CoinWatchlist coinWatchlist = new CoinWatchlist(); coinWatchlist.setSymbol(symbol); coinWatchlist.setUserId(userId); coinWatchlist.setRemark(remark); coinWatchlist.setScore(score); int num = coinMapper.updateCoinWatchlistRemarkAndScore(coinWatchlist); return num; } private List parseMixTradeList(JSONArray dataList) { List mixTraderList = JSONArray.parseArray(dataList.toJSONString(), CoinTrader.class); mixTraderList.stream().forEach(e -> { Map columnMap = e.getColumnList().stream().filter(Objects::nonNull) .collect(Collectors.toMap( object -> { JSONObject item = (JSONObject) object; return item.getString("describe"); }, object -> { JSONObject item = (JSONObject) object; return item.getString("value"); } )); e.setRoi(columnMap.get("ROI")); e.setTotalProfit(columnMap.containsKey("Total PnL") ? columnMap.get("Total PnL").replace("$", "").replace(",", "") : null); e.setTotalFollowersProfit(columnMap.containsKey("Total followers PnL") ? columnMap.get("Total followers PnL").replace("$", "").replace(",", "") : null); e.setAum(columnMap.containsKey("AUM") ? columnMap.get("AUM").replace("$", "").replace(",", "") : null); e.setMaxCallbackRate(columnMap.get("Max drawdown")); e.setLast3wWinRate(columnMap.get("Last 3W win rate")); e.setAverageWinRate(StringUtils.isNotEmpty(e.getAverageWinRate()) ? new BigDecimal(e.getAverageWinRate()).setScale(2, RoundingMode.HALF_UP).toPlainString() : "0.00"); e.setTraderNickName(StringUtils.isNotEmpty(e.getTraderNickName()) ? e.getTraderNickName() : "--"); }); return mixTraderList; } @Override public String orderDetail(String trackingNo) { Map paramMap = new LinkedHashMap<>(); paramMap.put("traderId", "b1b5467f8bb73f53ac97"); paramMap.put("pageSize", "20"); StringBuffer sb = new StringBuffer(); // 交易员当前带单列表筛选 for (int j = 1; j < 5; j++) { try { paramMap.put("pageNo", j + ""); JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); String trackingNo1 = order.getString("trackingNo"); if (trackingNo.equals(trackingNo1)) { sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append("
交易对").append(order.getString("symbol")).append("
持仓方向").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("
杠杆倍数").append(order.getString("leverage")).append("
开仓均价").append(order.getString("openPrice")).append("
开仓时间").append(DateUtils.longToString(order.getLong("openTime"))).append("
此笔订单跟单人数").append(order.getString("followerNum")).append("
保证金").append(order.getString("marginAmount")).append("
止盈价").append(order.getString("takeProfitPrice")).append("
止损价").append(order.getString("stopLossPrice")).append("
交易员").append("hale").append("
"); break; } } } catch (Exception e) { } } // 交易员历史带单列表筛选 if (sb.length() == 0) { for (int j = 1; j < 5; j++) { try { paramMap.put("pageNo", j + ""); JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/historyList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); String trackingNo1 = order.getString("trackingNo"); if (trackingNo.equals(trackingNo1)) { sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append("
交易对").append(order.getString("symbol")).append("
持仓方向").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("
杠杆倍数").append(order.getString("leverage")).append("
开仓均价").append(order.getString("openPrice")).append("
开仓时间").append(DateUtils.longToString(order.getLong("openTime"))).append("
此笔订单跟单人数").append(order.getString("followerNum")).append("
保证金").append(order.getString("marginAmount")).append("
平仓均价").append(order.getString("closePrice")).append("
平仓时间").append(DateUtils.longToString(order.getLong("closeTime"))).append("
平仓数量").append(order.getString("closeAmount")).append("
交易员").append("hale").append("
"); break; } } } catch (Exception e) { } } } return sb.toString(); } @Override public String orderDetail2(String orderId, String symbol) { Map paramMap = new LinkedHashMap<>(); paramMap.put("symbol", symbol); paramMap.put("orderId", orderId); StringBuffer sb = new StringBuffer(""); // 获取订单详情 try { String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common("/api/mix/v1/order/detail", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONObject order = response.getJSONObject("data"); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); } catch (Exception e) { log.error("orderDetail2 error,orderId={},symbol={}", orderId, symbol, e); } sb.append("
交易对").append(order.getString("symbol")).append("
交易方向").append(InitRunner.publicParamsMap.get("side").getString(order.getString("side"))).append("
杠杆倍数").append(order.getString("leverage")).append("
成交均价").append(order.getString("priceAvg")).append("
委托价格").append(order.getString("price")).append("
手续费").append(order.getString("fee")).append("
订单状态").append(InitRunner.publicParamsMap.get("state").getString(order.getString("state"))).append("
交易类型").append(InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType"))).append("
总盈亏").append(order.getString("totalProfits")).append("
预设止盈价格").append(order.getString("presetTakeProfitPrice")).append("
预设止损价格").append(order.getString("presetStopLossPrice")).append("
创建时间").append(DateUtils.longToString(order.getLong("cTime"))).append("
更新时间").append(DateUtils.longToString(order.getLong("uTime"))).append("
"); return sb.toString(); } @Override public String monitorJob() { // BITGET开仓平仓监控报警 scheduler.scheduleWithFixedDelay(() -> { monitorBitgetMixOrder(); }, 0, 10, TimeUnit.SECONDS); // OKX开仓平仓监控报警 scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("okx-mix-order"))) { return; } LocalDateTime endTime = LocalDateTime.now(); // 查看历史持仓信息 Map paramMap = new LinkedHashMap<>(); paramMap.put("instType", "SWAP"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); try { JSONObject response = requestApi4Common4OKX("/api/v5/account/positions-history", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray orderList = response.getJSONArray("data"); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); //LocalDateTime cTime = DateUtils.longToLocalDateTime(order.getLong("cTime")); String orderId = "okx" + order.getString("posId"); String symbol = order.getString("ccy"); if (!orderMap.containsKey(orderId)) { orderMap.put(orderId, "1"); log.warn("okx ={}", order); String content = "
交易对:" + symbol + "
"; JSONObject params = new JSONObject(); params.put("title", "OKX报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail2/" + orderId + "/" + symbol); params.put("btnTxt", "订单详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null); } } } catch (Exception e) { log.error("okx-mix-order error", e); } }, 0, 10, TimeUnit.SECONDS); scheduler.scheduleWithFixedDelay(() -> { monitorBitgetMixReturnrate(); }, 0, 10, TimeUnit.SECONDS); // BITGET跟单员监控报警 scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("bitget-mix-trader"))) { return; } try { List monitorTraderList = coinMapper.findMonitorTraderList(); forkJoinPool5.submit(() -> monitorTraderList.parallelStream().forEach(e -> { LocalDateTime endTime = LocalDateTime.now(); // 交易员当前带单列表 Map paramMap = new LinkedHashMap<>(); String[] split = e.split("\\|"); paramMap.put("traderId", split[0]); paramMap.put("pageNo", "1"); paramMap.put("pageSize", "20"); try { JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap); JSONArray orderList = response.getJSONArray("data"); if (null != orderList) { for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); LocalDateTime openTime = DateUtils.longToLocalDateTime(order.getLong("openTime")); String trackingNo = order.getString("trackingNo"); if (Duration.between(openTime, endTime).getSeconds() < 50 && !orderMap.containsKey(trackingNo)) { orderMap.put(trackingNo, "1"); String content = "
交易对:" + order.getString("symbol") + "
" + "
持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide")) + "
" + "
杠杆倍数:" + order.getString("leverage") + "
" + "
开仓均价:" + order.getString("openPrice") + "
" + "
止盈价:" + order.getString("takeProfitPrice") + "
" + "
止损价:" + order.getString("stopLossPrice") + "
" + "
交易员:" + split[1] + "
" + "
开仓时间:" + DateUtils.longToString(order.getLong("openTime")) + "
"; JSONObject params = new JSONObject(); params.put("title", "BITGET交易员开单报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail/" + order.getString("trackingNo")); params.put("btnTxt", "跟单详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null); } } } } catch (Exception ex) { log.error("bitget-mix-trader error,param={}", paramMap, ex); } })).join(); } catch (Exception e) { log.error("bitget-mix-trader top error", e); } }, 0, 10, TimeUnit.SECONDS); // 星球日报新闻快讯监控报警 scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("news-odaily"))) { return; } try { Connection.Response response = JsoupUtil.requestBody("https://www.odaily.news/v1/openapi/feeds", JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); JSONObject result = JSONObject.parseObject(response.body()); JSONArray newsList = result.getJSONObject("data").getJSONArray("arr_news"); LocalDateTime endTime = LocalDateTime.now(); for (int i = 0; i < newsList.size(); i++) { if (i == 5) { break; } JSONObject news = newsList.getJSONObject(i); String id = "Odaily" + news.getString("id"); String publishedAt = news.getString("published_at"); LocalDateTime publishedAtTime = DateUtils.stringToLocalDateTime(publishedAt); if (Duration.between(publishedAtTime, endTime).getSeconds() < 20 && !orderMap.containsKey(id)) { orderMap.put(id, "1"); String type = news.getString("type"); String title = news.getString("title"); String link = news.getString("link"); String content = ""; JSONObject params = new JSONObject(); params.put("title", "Odaily监控报警"); params.put("btnTxt", "新闻详情"); params.put("logUrl", link); params.put("user", "@all"); params.put("agentId", 1000004); if ("newsflashes".equals(type)) { String newsUrl = news.getString("news_url"); content = "
标题:" + title + "
" + "
类型:" + "新闻快讯" + "
" + "
发布时间:" + publishedAt + "
" + "
描述:" + news.getString("description").replace("\n", "    ") + "
"; } else if ("posts".equals(type)) { content = "
标题:" + title + "
" + "
类型:" + "帖子" + "
" + "
发布时间:" + publishedAt + "
" + "
描述:" + news.getString("summary").replace("\n", "    ") + "
"; } SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, wxCpService4News); } } } catch (Exception e) { log.error("news-odaily top error", e); } }, 0, 10, TimeUnit.SECONDS); // 律动日报新闻快讯监控报警 scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("news-theblockbeats"))) { return; } try { Map paramMap = new HashMap<>(); paramMap.put("size", "10"); paramMap.put("page", "1"); Connection.Response response = JsoupUtil.requestBody("https://api.theblockbeats.news/v1/open-api/open-flash", JsoupUtil.HTTP_GET, InitRunner.proxy, null, paramMap); JSONObject result = JSONObject.parseObject(response.body()); JSONArray newsList = result.getJSONObject("data").getJSONArray("data"); LocalDateTime endTime = LocalDateTime.now(); for (int i = 0; i < newsList.size(); i++) { if (i == 5) { break; } JSONObject news = newsList.getJSONObject(i); String id = "BlockBeats" + news.getString("id"); String createTime = news.getString("create_time"); LocalDateTime createTimeTime = DateUtils.longToLocalDateTime_(Long.valueOf(createTime)); createTime = DateUtils.localDateTimeToString(createTimeTime); if (Duration.between(createTimeTime, endTime).getSeconds() < 20 && !orderMap.containsKey(id)) { orderMap.put(id, "1"); String title = news.getString("title"); String content = news.getString("content"); String pic = news.getString("pic"); String link = news.getString("link"); String url = news.getString("url"); JSONObject params = new JSONObject(); params.put("title", "BlockBeats监控报警"); params.put("btnTxt", "新闻详情"); params.put("logUrl", link); params.put("user", "@all"); params.put("agentId", 1000004); if (StringUtils.isEmpty(pic)) { // 文本卡片 String contentStr = "
标题:" + title + "
" + "
类型:" + "新闻快讯" + "
" + "
发布时间:" + createTime + "
" + "
描述:" + content.replace("\n", "    ") + "
"; if (title.contains("Upbit") || title.contains("upbit")) { JSONObject params4Upbit = new JSONObject(); params4Upbit.put("title", "BlockBeats监控报警"); params4Upbit.put("logUrl", link); params4Upbit.put("btnTxt", "新闻详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params4Upbit, null); } SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params, wxCpService4News); } else { // 图文 NewArticle article1 = new NewArticle(); article1.setUrl(link); article1.setPicUrl(pic); article1.setDescription(content); article1.setTitle(title); if (title.contains("Upbit") || title.contains("upbit")) { JSONObject params4Upbit = new JSONObject(); params4Upbit.put("title", "BlockBeats监控报警"); params4Upbit.put("logUrl", link); params4Upbit.put("btnTxt", "新闻详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4NEWS(params4Upbit, null, article1); } SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4NEWS(params, wxCpService4News, article1); } } } } catch (Exception e) { log.error("news-theblockbeats top error", e); } }, 0, 10, TimeUnit.SECONDS); // coingecko scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("watchlist-coingecko-cmc"))) { log.warn("watchlist-coingecko-cmc scheduler return"); return; } log.warn("watchlist-coingecko-cmc scheduler start"); int MAX_NUMBER = 100; Map params = new HashMap<>(); params.put("sortField", Collections.singletonList("create_time")); params.put("sort", "desc"); List watchlistListCKO = coinMapper.findWatchlistList(params); Stream.iterate(0, n -> n + 1).limit((watchlistListCKO.size() + MAX_NUMBER - 1) / MAX_NUMBER) .forEach(i -> { try { Thread.sleep(5000L); } catch (InterruptedException e) { } Map coinWatchlistMap4CoingeckoId = watchlistListCKO.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist)); parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId); }); List watchlistListCMC = coinMapper.findWatchlistList(params); Stream.iterate(0, n -> n + 1).limit((watchlistListCMC.size() + MAX_NUMBER - 1) / MAX_NUMBER) .forEach(i -> { try { Thread.sleep(5000L); } catch (InterruptedException e) { } Map coinWatchlistMap4CmcId = watchlistListCMC.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist)); parseWatchlistMap4CmC(coinWatchlistMap4CmcId); }); // CoinCurrencyHolding同步 params.put("sortField", Collections.singletonList("cch.buy_time")); params.put("sort", "desc"); params.put("status", "1"); List currentHoldingList = coinMapper.findCurrentHoldingList(params); parseCurrentHoldingMap4Coingecko(currentHoldingList); log.warn("watchlist-coingecko-cmc scheduler end"); }, 0, 1, TimeUnit.HOURS); // Upbit交易所监控报警 scheduler.scheduleWithFixedDelay(() -> { if (!"1".equals(getMonitorJobStatus("upbit-digitalasset-notices"))) { return; } try { Map paramMap = new HashMap<>(); paramMap.put("page", "1"); paramMap.put("per_page", "20"); paramMap.put("thread_name", "general"); Connection.Response response = JsoupUtil.requestBody("https://api-manager.upbit.com/api/v1/notices", JsoupUtil.HTTP_GET, InitRunner.proxy, null, paramMap); JSONObject result = JSONObject.parseObject(response.body()); JSONArray noticeList = result.getJSONObject("data").getJSONArray("list"); for (int i = 0; i < noticeList.size(); i++) { if (i == 5) { break; } JSONObject notice = noticeList.getJSONObject(i); String idOri = notice.getString("id"); String id = "Upbit" + idOri; String title = notice.getString("title"); if (title.contains("New digital asset on KRW Market") && !orderMap.containsKey(id)) { orderMap.put(id, "1"); String createTime = notice.getString("created_at"); String updateTime = notice.getString("updated_at"); String viewCount = notice.getString("view_count"); JSONObject params = new JSONObject(); params.put("title", "Upbit监控报警"); params.put("btnTxt", "通知详情"); params.put("logUrl", "https://sg-api-manager.upbit.com/api/v1/notices/" + idOri); // 文本卡片 String contentStr = "
标题:" + title + "
" + "
发布时间:" + createTime + "
" + "
更新时间:" + updateTime + "
" + "
查看次数:" + viewCount + "
"; SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params, null); } } } catch (Exception e) { log.error("upbit-digitalasset-notices top error", e); try { Thread.sleep(600000L); } catch (InterruptedException ex) { } } }, 0, 10, TimeUnit.SECONDS); return null; } private void parseCurrentHoldingMap4Coingecko(List currentHoldingList) { String coingeckoCoinsMarketsUrl = InitRunner.dicCodeMap.get("coingecko_coins_markets_url").getCodeValue(); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); Map paramMap = new LinkedHashMap<>(); paramMap.put("ids", currentHoldingList.stream().map(CoinCurrencyHolding::getCoingeckoId).distinct().collect(Collectors.joining(","))); paramMap.put("vs_currency", "usd"); AtomicInteger i = new AtomicInteger(); Connection.Response response = null; try { response = JsoupUtil.requestBody(coingeckoCoinsMarketsUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap); JSONArray result = JSONArray.parseArray(response.body()); Map> currencyHoldingMap = currentHoldingList.stream().collect(Collectors.groupingBy(CoinCurrencyHolding::getCoingeckoId)); for (int j = 0; j < result.size(); j++) { JSONObject marketData = result.getJSONObject(j); String id = marketData.getString("id"); if (currencyHoldingMap.containsKey(id)) { List currencyHoldingSubList = currencyHoldingMap.get(id); for (CoinCurrencyHolding currencyHolding : currencyHoldingSubList) { // 市场价格 if (marketData.containsKey("current_price") && null != marketData.get("current_price")) { String markPrice = marketData.getBigDecimal("current_price").toPlainString(); currencyHolding.setCurrentPrice(markPrice); } // 入场总额 if (StringUtils.isNotEmpty(currencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(currencyHolding.getBuyQuantity())) { currencyHolding.setBuyAmount(new BigDecimal(currencyHolding.getBuyPrice()).multiply(new BigDecimal(currencyHolding.getBuyQuantity())).setScale(2, RoundingMode.HALF_UP)); } // 当前总额 if (StringUtils.isNotEmpty(currencyHolding.getCurrentPrice()) && StringUtils.isNotEmpty(currencyHolding.getCurrentQuantity())) { currencyHolding.setCurrentAmount(new BigDecimal(currencyHolding.getCurrentPrice()).multiply(new BigDecimal(currencyHolding.getCurrentQuantity())).setScale(2, RoundingMode.HALF_UP)); } // 涨跌幅比例 if (StringUtils.isNotEmpty(currencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(currencyHolding.getCurrentPrice())) { BigDecimal oriChangePercentage = currencyHolding.getChangePercentage(); BigDecimal changePercentage = new BigDecimal(currencyHolding.getCurrentPrice()).divide(new BigDecimal(currencyHolding.getBuyPrice()), 2, RoundingMode.HALF_UP); currencyHolding.setChangePercentage(changePercentage); // 监控报警 if (oriChangePercentage != null && !"USDT".equals(currencyHolding.getSymbol())) { BigDecimal roundedDown = changePercentage.setScale(0, RoundingMode.FLOOR); if (changePercentage.compareTo(roundedDown) > 0 && oriChangePercentage.compareTo(roundedDown) < 0) { String content = "
币种名称:" + currencyHolding.getSymbol() + "
" + "
入场价格:" + currencyHolding.getBuyPrice() + "
" + "
当前价格:" + currencyHolding.getCurrentPrice() + "
" + "
入场总额:" + currencyHolding.getBuyAmount() + "
" + "
当前总额:" + currencyHolding.getCurrentAmount() + "
" + "
所属平台:" + currencyHolding.getExchangeCategory() + "
" + "
买入时间:" + currencyHolding.getBuyTime() + "
" + "
涨跌幅:" + currencyHolding.getChangePercentage() + "
"; JSONObject params = new JSONObject(); params.put("title", "持仓币种盈利报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin.html"); params.put("btnTxt", "详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null); } } } coinMapper.updateCurrentHolding(currencyHolding); } } } } catch (Exception e) { log.error("parseCurrentHoldingMap4Coingecko error,response={},size={},i={}", response != null ? response.body() : "--", currentHoldingList.size(), i.get(), e); } } @Override public Object getCurrentHoldingTotalAmout(JSONObject params) { BigDecimal totalAmout = coinMapper.getCurrentHoldingTotalAmout(params.toJavaObject(Map.class)); return totalAmout; } @Override public Object login(String username, String password) { Integer exist = coinMapper.existUserByUsernameAndPassword(username, password); if (exist != null) { return R.ok().data("success"); } else { return R.error().message("用户名或者密码错误!"); } } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public R insertOrUpdateCurrentHolding(JSONObject params) { String operationType = params.getString("operationType"); if ("buy".equals(operationType)) { String symbol = params.getString("symbol"); String buyPrice = params.getString("buyPrice"); String buyQuantity = params.getString("buyQuantity"); String currentQuantity = params.getString("currentQuantity"); String exchangeCategoryId = params.getString("exchangeCategory"); String coingeckoId = params.getString("coingeckoId"); String buyTime = params.getString("buyTime"); String remark = params.getString("remark"); if (StringUtils.isEmpty(symbol) || StringUtils.isEmpty(buyPrice) || StringUtils.isEmpty(buyQuantity) || StringUtils.isEmpty(currentQuantity) || StringUtils.isEmpty(exchangeCategoryId)) { throw new ParameterException("必填参数不能为空!"); } symbol = symbol.trim(); Integer existCmcMapBySymbol = coinMapper.existCmcMapBySymbol(symbol); if (existCmcMapBySymbol == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "symbol不存在!"); } if (StringUtils.isEmpty(coingeckoId)) { coingeckoId = coinMapper.findCoingeckoIdBySymbol(symbol); if (StringUtils.isEmpty(coingeckoId)) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "coingeckoId不存在!"); } } CoinCurrencyHolding coinCurrencyHolding = new CoinCurrencyHolding(); coinCurrencyHolding.setSymbol(symbol); coinCurrencyHolding.setBuyPrice(buyPrice); coinCurrencyHolding.setBuyQuantity(buyQuantity); coinCurrencyHolding.setCurrentQuantity(currentQuantity); coinCurrencyHolding.setExchangeCategoryId(Integer.valueOf(exchangeCategoryId)); coinCurrencyHolding.setCoingeckoId(coingeckoId); coinCurrencyHolding.setBuyTime(StringUtils.isNotEmpty(buyTime) ? LocalDateTime.parse(buyTime, DateUtils.dateTimeFormatter) : LocalDateTime.now()); coinCurrencyHolding.setStatus(1); coinCurrencyHolding.setRemark(remark); coinMapper.buyCurrentHolding(coinCurrencyHolding); List currentHoldingList = new ArrayList<>(); currentHoldingList.add(coinCurrencyHolding); parseCurrentHoldingMap4Coingecko(currentHoldingList); } else if ("sell".equals(operationType)) { String id = params.getString("id"); String sellPrice = params.getString("sellPrice"); String sellTime = params.getString("sellTime"); String sellAmount = params.getString("sellAmount"); String remark = params.getString("remark"); if (StringUtils.isEmpty(id) || StringUtils.isEmpty(sellPrice)) { throw new ParameterException("必填参数不能为空!"); } id = id.trim(); CoinCurrencyHolding coinCurrencyHolding = coinMapper.findCurrencyHoldingById(id); if (coinCurrencyHolding == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "id不存在!"); } coinCurrencyHolding.setSellTime(StringUtils.isNotEmpty(sellTime) ? LocalDateTime.parse(sellTime, DateUtils.dateTimeFormatter) : LocalDateTime.now()); coinCurrencyHolding.setSellPrice(sellPrice); coinCurrencyHolding.setStatus(2); if (StringUtils.isEmpty(sellAmount)) { coinCurrencyHolding.setSellAmount(new BigDecimal(sellPrice).multiply(new BigDecimal(coinCurrencyHolding.getCurrentQuantity())).setScale(2,RoundingMode.HALF_UP)); }else{ coinCurrencyHolding.setSellAmount(new BigDecimal(sellAmount).setScale(2,RoundingMode.HALF_UP)); } if (StringUtils.isNotEmpty(remark)) { coinCurrencyHolding.setRemark(remark + (StringUtils.isNotEmpty(coinCurrencyHolding.getRemark()) ? System.lineSeparator() + coinCurrencyHolding.getRemark() : "")); } // 涨跌幅比例 if (StringUtils.isNotEmpty(coinCurrencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(sellPrice)) { BigDecimal changePercentage = new BigDecimal(sellPrice).divide(new BigDecimal(coinCurrencyHolding.getBuyPrice()), 2, RoundingMode.HALF_UP); coinCurrencyHolding.setChangePercentage(changePercentage); } coinMapper.updateCurrentHolding(coinCurrencyHolding); // 更新余额 CoinCurrencyHolding usdtCurrencyHolding = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", coinCurrencyHolding.getExchangeCategoryId()); if (usdtCurrencyHolding == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "USDT不存在!"); } usdtCurrencyHolding.setCurrentAmount(usdtCurrencyHolding.getCurrentAmount().add(coinCurrencyHolding.getSellAmount())); coinMapper.updateCurrentHolding(usdtCurrencyHolding); } else if ("transfer".equals(operationType)) { String remitter = params.getString("remitter"); String payee = params.getString("payee"); String amount = params.getString("amount"); if (StringUtils.isEmpty(remitter) || StringUtils.isEmpty(payee) || StringUtils.isEmpty(amount)) { throw new ParameterException("必填参数不能为空!"); } // 更新余额 CoinCurrencyHolding usdtCurrencyHolding4Remitter = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", Integer.valueOf(remitter)); if (usdtCurrencyHolding4Remitter == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "汇款人USDT不存在!"); } CoinCurrencyHolding usdtCurrencyHolding4Payee = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", Integer.valueOf(payee)); if (usdtCurrencyHolding4Payee == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "收款人USDT不存在!"); } usdtCurrencyHolding4Remitter.setCurrentAmount(usdtCurrencyHolding4Remitter.getCurrentAmount().subtract(new BigDecimal(amount))); usdtCurrencyHolding4Payee.setCurrentAmount(usdtCurrencyHolding4Payee.getCurrentAmount().add(new BigDecimal(amount))); coinMapper.updateCurrentHolding(usdtCurrencyHolding4Remitter); coinMapper.updateCurrentHolding(usdtCurrencyHolding4Payee); } else if ("modifyCurrentAmount".equals(operationType)) { String id = params.getString("id"); String currentAmount = params.getString("currentAmount"); if (StringUtils.isEmpty(id) || StringUtils.isEmpty(currentAmount)) { throw new ParameterException("必填参数不能为空!"); } id = id.trim(); CoinCurrencyHolding coinCurrencyHolding = coinMapper.findCurrencyHoldingById(id); if (coinCurrencyHolding == null) { throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "id不存在!"); } coinCurrencyHolding.setCurrentAmount(new BigDecimal(currentAmount).setScale(2, RoundingMode.HALF_UP)); coinMapper.updateCurrentHolding(coinCurrencyHolding); } else { throw new ParameterException("暂不支持的操作!"); } return R.ok().data("success"); } @Override public void monitorBitgetMixOrder() { if (!"1".equals(getMonitorJobStatus("bitget-mix-order"))) { return; } LocalDateTime endTime = LocalDateTime.now(); // 全部历史委托列表 Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("startTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime.minusMinutes(1)))); //paramMap.put("startTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime.minusDays(3)))); paramMap.put("endTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime))); paramMap.put("pageSize", "100"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); try { JSONObject response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList"); if (null == orderList) { return; } log.warn("bitget-mix-order exec,orderList={}", orderList.size()); for (int i = 0; i < orderList.size(); i++) { JSONObject order = orderList.getJSONObject(i); LocalDateTime cTime = DateUtils.longToLocalDateTime(order.getLong("cTime")); String orderId = order.getString("orderId"); String symbol = order.getString("symbol"); if (Duration.between(cTime, endTime).getSeconds() < 200 && !orderMap.containsKey(orderId)) { orderMap.put(orderId, "1"); log.warn("bitget-mix-order exec,orderId={}", orderId); String content = "
交易对:" + order.getString("symbol") + "
" + "
交易方向:" + InitRunner.publicParamsMap.get("tradeSide").getString(order.getString("tradeSide")) + "
" + "
杠杆倍数:" + order.getString("leverage") + "
" + "
成交均价:" + order.getString("priceAvg") + "
" + "
委托价格:" + order.getString("price") + "
" + "
订单状态:" + InitRunner.publicParamsMap.get("state").getString(order.getString("state")) + "
" + "
订单类型:" + InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType")) + "
" + "
订单时间:" + DateUtils.longToString(order.getLong("cTime")) + "
"; JSONObject params = new JSONObject(); params.put("title", (order.getString("side").contains("open") ? "BITGET合约开单" : "BITGET合约平单") + "报警"); params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail2/" + orderId + "/" + symbol); params.put("btnTxt", "订单详情"); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null); } } } catch (Exception e) { log.error("bitget-mix-order error", e); } } @Override public void monitorBitgetMixReturnrate() { if (!"1".equals(getMonitorJobStatus("bitget-mix-returnrate"))) { return; } // BITGET全部合约仓位信息V2 Map paramMap = new HashMap<>(); paramMap.put("productType", "umcbl"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); try { JSONObject response = requestApi4Common("/api/mix/v1/position/allPosition-v2", signQueryString, null, JsoupUtil.HTTP_GET, paramMap); JSONArray mixList = response.getJSONArray("data"); for (int i = 0; i < mixList.size(); i++) { JSONObject mixData = mixList.getJSONObject(i); String symbol = mixData.getString("symbol"); String margin = mixData.getString("margin"); String averageOpenPrice = mixData.getString("averageOpenPrice"); String key = symbol + margin + averageOpenPrice; // 回报率=未实现盈亏/保证金 // 持仓方向 long:多头 short:空头 String holdSide = mixData.getString("holdSide"); BigDecimal returnRate = new BigDecimal(mixData.getString("unrealizedPL")).divide(new BigDecimal(margin), 4, RoundingMode.HALF_UP); for (int j = 1; j <= 10; j++) { BigDecimal grid = BigDecimal.valueOf(0.5).multiply(BigDecimal.valueOf(j)); BigDecimal minusGrid = BigDecimal.valueOf(-0.5).multiply(BigDecimal.valueOf(j)); if (returnRate.compareTo(grid) < 0) { if (mixMap.containsKey(key)) { mixMap.get(key).put("returnRate", returnRate); } else { JSONObject jsonObject = new JSONObject(); jsonObject.put("returnRate", returnRate); mixMap.put(key, jsonObject); } break; } if (returnRate.compareTo(grid) > 0) { if (mixMap.containsKey(key)) { mixMap.get(key).put("returnRate", returnRate); if (mixMap.get(key).containsKey(grid.toPlainString())) { continue; } else { mixMap.get(key).put(grid.toPlainString(), true); String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol; String last = "--"; try { Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last"); } catch (Exception e) { } String content = "币对名称:" + symbol + "\n" + "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" + "杠杆倍数:" + mixData.getString("leverage") + "\n" + "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" + "当前价格:" + last + "\n" + "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString(); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null); } } else { JSONObject jsonObject = new JSONObject(); jsonObject.put("returnRate", returnRate); jsonObject.put(grid.toPlainString(), true); mixMap.put(key, jsonObject); String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol; String last = "--"; try { Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last"); } catch (Exception e) { } String content = "币对名称:" + symbol + "\n" + "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" + "杠杆倍数:" + mixData.getString("leverage") + "\n" + "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" + "当前价格:" + last + "\n" + "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString(); SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null); //break; } } } } } catch (Exception e) { log.error("bitget-mix-returnrate error", e); } } private void initCexSpotFlag(Map coinWatchlistOtherMap4Symbol) { // spot String coingeckoExchangeTickersUrl = InitRunner.dicCodeMap.get("coingecko_exchange_tickers_url").getCodeValue(); String[] coingeckoExchangeIdArr = InitRunner.dicCodeMap.get("coingecko_exchange_ids").getCodeValue().split(","); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); Map paramMap = new LinkedHashMap<>(); paramMap.put("coin_ids", StringUtils.join(coinWatchlistOtherMap4Symbol.keySet(), ",")); int iii = 0; JSONArray result = null; for (String coingeckoExchangeId : coingeckoExchangeIdArr) { iii++; try { Thread.sleep(10000L); Connection.Response response = JsoupUtil.requestBody(String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap); result = JSONObject.parseObject(response.body()).getJSONArray("tickers"); if (result == null) { Thread.sleep(60000L); response = JsoupUtil.requestBody(String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap); result = JSONObject.parseObject(response.body()).getJSONArray("tickers"); } Map resultMap = result.stream().collect(Collectors.toMap(i -> { JSONObject a = (JSONObject) i; String k = a.getString("base").concat(a.getString("target")); return k; }, i -> (JSONObject) i, (existing, replacement) -> existing)); for (CoinWatchlistOther coinWatchlistOther : coinWatchlistOtherMap4Symbol.values()) { String baseTarget = coinWatchlistOther.getSymbol().concat("USDT"); if (iii == 1) { coinWatchlistOther.setCexSpot(""); } if (resultMap.containsKey(baseTarget)) { coinWatchlistOther.setCexSpot(coinWatchlistOther.getCexSpot() + "1"); } else { coinWatchlistOther.setCexSpot(coinWatchlistOther.getCexSpot() + "0"); } } } catch (Exception e) { log.error("initCexSpotFlag error,url={},paramMap={},result={}", String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), paramMap, result, e); throw new RuntimeException("initCexSpotFlag error"); } } } /** * 解析交易所现货+合约标志 */ @Override @Async public void syncCexFlag(String symbol) { List watchlistOtherList; if (StringUtils.isNotEmpty(symbol)) { CoinWatchlistOther watchlistOther = coinMapper.findWatchlistOtherBySymbol2(symbol); if (watchlistOther == null) { return; } watchlistOtherList = new ArrayList<>(); watchlistOtherList.add(watchlistOther); } else { watchlistOtherList = coinMapper.findAllCoinWatchlistOther(); } // Perpetual String coingeckoExchangeFuturesTickersUrl = InitRunner.dicCodeMap.get("coingecko_exchange_futures_tickers_url").getCodeValue(); String[] coingeckoExchangeFutruesIdArr = InitRunner.dicCodeMap.get("coingecko_exchange_futures_ids").getCodeValue().split(","); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); Map paramMap = new LinkedHashMap<>(); paramMap.put("include_tickers", "unexpired"); int jjj = 0; JSONArray result = null; for (String coingeckoExchangeFutruesId : coingeckoExchangeFutruesIdArr) { jjj++; try { Thread.sleep(10000L); Connection.Response response = JsoupUtil.requestBody(String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap); result = JSONObject.parseObject(response.body()).getJSONArray("tickers"); if (result == null) { Thread.sleep(10000L); response = JsoupUtil.requestBody(String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap); result = JSONObject.parseObject(response.body()).getJSONArray("tickers"); } Map resultMap = result.stream().collect(Collectors.toMap(i -> { JSONObject a = (JSONObject) i; String k = a.getString("base").concat(a.getString("target")); return k; }, i -> (JSONObject) i, (existing, replacement) -> existing)); for (CoinWatchlistOther coinWatchlistOther : watchlistOtherList) { String baseTarget = coinWatchlistOther.getSymbol().concat("USDT"); if (jjj == 1) { coinWatchlistOther.setCexPerpetual(""); } if (resultMap.containsKey(baseTarget) || resultMap.containsKey("1000".concat(baseTarget))) { coinWatchlistOther.setCexPerpetual(coinWatchlistOther.getCexPerpetual() + "1"); } else { coinWatchlistOther.setCexPerpetual(coinWatchlistOther.getCexPerpetual() + "0"); } } } catch (Exception e) { log.error("initCexFlag error,url={},paramMap={},result={}", String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), paramMap, result, e); throw new RuntimeException("initCexFlag error"); } } int MAX_NUMBER2 = 10; Stream.iterate(0, n -> n + 1).limit((watchlistOtherList.size() + MAX_NUMBER2 - 1) / MAX_NUMBER2) .forEach(i -> { try { Thread.sleep(5000L); } catch (InterruptedException e) { throw new RuntimeException(e); } Map coinWatchlistOtherMap4Symbol = watchlistOtherList.stream().skip((long) i * MAX_NUMBER2).limit(MAX_NUMBER2).collect(Collectors.toMap(CoinWatchlistOther::getCoingeckoId, coinWatchlistOther -> coinWatchlistOther)); initCexSpotFlag(coinWatchlistOtherMap4Symbol); }); boolean fail = watchlistOtherList.stream().anyMatch(e -> e.getCexSpot() == null || e.getCexSpot().length() < 6); if (!fail) { coinMapper.insertOrUpdateCoinWatchlistOtherList(watchlistOtherList); } } public void parseWatchlistMap4Coingecko(Map watchlistMap4Coingecko) { String coingeckoCoinsMarketsUrl = InitRunner.dicCodeMap.get("coingecko_coins_markets_url").getCodeValue(); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); Map paramMap = new LinkedHashMap<>(); paramMap.put("ids", StringUtils.join(watchlistMap4Coingecko.keySet(), ",")); paramMap.put("vs_currency", "usd"); AtomicInteger i = new AtomicInteger(); Connection.Response response = null; try { response = JsoupUtil.requestBody(coingeckoCoinsMarketsUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap); JSONArray result = JSONArray.parseArray(response.body()); for (int j = 0; j < result.size(); j++) { JSONObject marketData = result.getJSONObject(j); String id = marketData.getString("id"); if (watchlistMap4Coingecko.containsKey(id)) { CoinWatchlist coinWatchlist = watchlistMap4Coingecko.get(id); // 总市值排名 if (marketData.containsKey("market_cap_rank") && null != marketData.get("market_cap_rank")) { Integer totalMarketRanking = marketData.getInteger("market_cap_rank"); coinWatchlist.setTotalMarketRanking(totalMarketRanking); } // 总市值 if (marketData.containsKey("market_cap") && null != marketData.get("market_cap")) { BigDecimal totalMarketValue = marketData.getBigDecimal("market_cap").setScale(2, RoundingMode.HALF_UP); coinWatchlist.setTotalMarketValue(totalMarketValue); } // 市场价格 if (marketData.containsKey("current_price") && null != marketData.get("current_price")) { String markPrice = marketData.getBigDecimal("current_price").toPlainString(); coinWatchlist.setMarkPrice(markPrice); } // 24小时价格变化 if (marketData.containsKey("price_change_percentage_24h") && null != marketData.get("price_change_percentage_24h")) { BigDecimal priceChangePercentage24h = marketData.getBigDecimal("price_change_percentage_24h").setScale(2, RoundingMode.HALF_UP); coinWatchlist.setPriceChangePercentage24h(priceChangePercentage24h); } // 历史最高价格 if (marketData.containsKey("ath") && null != marketData.get("ath")) { String highestHistoricalPrice = marketData.getBigDecimal("ath").toPlainString(); coinWatchlist.setHighestHistoricalPrice(highestHistoricalPrice); } // 历史最高点涨幅比例 if (marketData.containsKey("ath_change_percentage") && null != marketData.get("ath_change_percentage")) { BigDecimal athChangePercentage = marketData.getBigDecimal("ath_change_percentage").setScale(2, RoundingMode.HALF_UP); coinWatchlist.setAthChangePercentage(athChangePercentage); } // 历史最高日期 if (marketData.containsKey("ath_date") && null != marketData.get("ath_date")) { LocalDate highestHistoricalDate = LocalDate.parse(marketData.getString("ath_date"), DateUtils.utcTimeFormatter); coinWatchlist.setHighestHistoricalDate(highestHistoricalDate); } // 历史最低价格 if (marketData.containsKey("atl") && null != marketData.get("atl")) { String lowestHistoricalPrice = marketData.getString("atl"); coinWatchlist.setLowestHistoricalPrice(lowestHistoricalPrice); } // 历史最低点涨幅比例 if (marketData.containsKey("atl_change_percentage") && null != marketData.get("atl_change_percentage")) { BigDecimal atlChangePercentage = marketData.getBigDecimal("atl_change_percentage").setScale(2, RoundingMode.HALF_UP); coinWatchlist.setAtlChangePercentage(atlChangePercentage); } // 历史最低日期 if (marketData.containsKey("atl_date") && null != marketData.get("atl_date")) { LocalDate lowestHistoricalDate = LocalDate.parse(marketData.getString("atl_date"), DateUtils.utcTimeFormatter); coinWatchlist.setLowestHistoricalDate(lowestHistoricalDate); } // 涨幅倍数 if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice()) && StringUtils.isNotEmpty(coinWatchlist.getLowestHistoricalPrice())) { BigDecimal increaseMultiple = new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(new BigDecimal(coinWatchlist.getLowestHistoricalPrice()), 0, RoundingMode.HALF_UP); coinWatchlist.setIncreaseMultiple(increaseMultiple.intValue()); } coinMapper.updateCoinWatchlist(coinWatchlist); } } } catch (Exception e) { log.error("parseWatchlistMap4Coingecko error,response={},size={},i={}", response != null ? response.body() : "--", watchlistMap4Coingecko.size(), i.get(), e); } } public void parseWatchlistMap4CmC(Map watchlistMap4CmC) { String coinmarketcapApikey = InitRunner.dicCodeMap.get("coinmarketcap_apikey").getCodeValue(); String coinmarketcapQuotesLatestUrl = InitRunner.dicCodeMap.get("coinmarketcap_quotes_latest_url").getCodeValue(); Map headerMap = new HashMap<>(); headerMap.put("Accept", "application/json"); headerMap.put("Accept-Encoding", "deflate,gzip"); headerMap.put("X-CMC_PRO_API_KEY", coinmarketcapApikey); Map paramMap = new LinkedHashMap<>(); paramMap.put("id", StringUtils.join(watchlistMap4CmC.keySet(), ",")); AtomicInteger i = new AtomicInteger(); try { Connection.Response response = JsoupUtil.requestBody(coinmarketcapQuotesLatestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap); JSONObject result = JSONObject.parseObject(response.body()); JSONObject dataJO = result.getJSONObject("data"); watchlistMap4CmC.forEach((key, value) -> { i.getAndIncrement(); if (dataJO.containsKey(key)) { JSONObject jsonObject = dataJO.getJSONObject(String.valueOf(key)); value.setTotalMarketRanking(jsonObject.getInteger("cmc_rank")); BigDecimal totalMarketValue = null; try { totalMarketValue = jsonObject.getJSONObject("quote").getJSONObject("USD").getBigDecimal("market_cap").setScale(2, RoundingMode.HALF_UP); if (totalMarketValue.compareTo(BigDecimal.ZERO) == 0) { totalMarketValue = jsonObject.getBigDecimal("self_reported_market_cap").setScale(2, RoundingMode.HALF_UP); } } catch (Exception e) { if (jsonObject.containsKey("self_reported_market_cap") && null != jsonObject.getBigDecimal("self_reported_market_cap")) { totalMarketValue = jsonObject.getBigDecimal("self_reported_market_cap").setScale(2, RoundingMode.HALF_UP); } } value.setTotalMarketValue(totalMarketValue); // cmc_url value.setCmcUrl(jsonObject.getString("slug")); // coingecko_url if (StringUtils.isEmpty(value.getCoingeckoUrl())) { value.setCoingeckoUrl(value.getCmcUrl()); } coinMapper.updateCoinWatchlist(value); // 解析CoinWatchlistOther CoinWatchlistOther coinWatchlistOther = new CoinWatchlistOther(); coinWatchlistOther.setSymbol(value.getSymbol()); if (null != jsonObject.get("max_supply")) { coinWatchlistOther.setMaxSupply(jsonObject.getBigDecimal("max_supply").setScale(0, RoundingMode.HALF_UP).toPlainString()); } if (null != jsonObject.get("circulating_supply")) { coinWatchlistOther.setCirculatingSupply(jsonObject.getBigDecimal("circulating_supply").setScale(0, RoundingMode.HALF_UP).toPlainString()); if ("0".equals(coinWatchlistOther.getCirculatingSupply()) && null != jsonObject.get("self_reported_circulating_supply")) { coinWatchlistOther.setCirculatingSupply(jsonObject.getBigDecimal("self_reported_circulating_supply").setScale(0, RoundingMode.HALF_UP).toPlainString()); } } if (null != coinWatchlistOther.getMaxSupply() && null != coinWatchlistOther.getCirculatingSupply()) { coinWatchlistOther.setCirculatingRate(new BigDecimal(coinWatchlistOther.getCirculatingSupply()).divide(new BigDecimal(coinWatchlistOther.getMaxSupply()), 3, RoundingMode.HALF_UP).multiply(new BigDecimal("100")).toPlainString()); } if (null != jsonObject.get("total_supply")) { coinWatchlistOther.setTotalSupply(jsonObject.getBigDecimal("total_supply").setScale(0, RoundingMode.HALF_UP).toPlainString()); } JSONObject usdQuote = jsonObject.getJSONObject("quote").getJSONObject("USD"); if (usdQuote != null && null != usdQuote.get("market_cap")) { coinWatchlistOther.setMarketCap(usdQuote.getBigDecimal("market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString()); if ("0".equals(coinWatchlistOther.getMarketCap()) && null != jsonObject.get("self_reported_market_cap")) { coinWatchlistOther.setMarketCap(jsonObject.getBigDecimal("self_reported_market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString()); } } if (usdQuote != null && null != usdQuote.get("fully_diluted_market_cap")) { coinWatchlistOther.setFullyDilutedMarketCap(usdQuote.getBigDecimal("fully_diluted_market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString()); } coinMapper.insertOrUpdateCoinWatchlistOther(coinWatchlistOther); } }); } catch (Exception e) { log.error("parseWatchlistMap4CmC error,size={},i={}", watchlistMap4CmC.size(), i.get(), e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm4APP_TEXT_CARD(String content, JSONObject params, WxCpService wxCpServiceFinal) { // 文本卡片模式发消息 String title = "监控告警明细"; if (params.containsKey("title")) { title = params.getString("title"); } String logUrl = "https://lvzhiqiang.top"; if (params.containsKey("logUrl")) { logUrl = params.getString("logUrl"); } String btnTxt = "日志详情"; if (params.containsKey("btnTxt")) { btnTxt = params.getString("btnTxt"); } String user = "LvZhiQiang"; if (params.containsKey("user")) { user = params.getString("user"); } String party = ""; if (params.containsKey("party")) { party = params.getString("party"); } String tag = ""; if (params.containsKey("tag")) { tag = params.getString("tag"); } Integer agentId = properties.getAgentId(); if (params.containsKey("agentId")) { agentId = params.getInteger("agentId"); } if (wxCpServiceFinal == null) { wxCpServiceFinal = wxCpService; } WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(agentId) .toUser(user) .toParty(party) .toTag(tag) .title(title).description(content) .url(logUrl).btnTxt(btnTxt) .build(); try { log.info("企业微信推送消息,send message: {}", wxCpMessage); WxCpMessageSendResult sendResult = wxCpServiceFinal.getMessageService().send(wxCpMessage); log.info("企业微信推送消息成功,send result: {}", sendResult); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm4NEWS(JSONObject params, WxCpService wxCpServiceFinal, NewArticle... articles) { // 图文消息 String title = "监控告警明细"; if (params.containsKey("title")) { title = params.getString("title"); } String logUrl = "https://lvzhiqiang.top"; if (params.containsKey("logUrl")) { logUrl = params.getString("logUrl"); } String btnTxt = "日志详情"; if (params.containsKey("btnTxt")) { btnTxt = params.getString("btnTxt"); } String user = "LvZhiQiang"; if (params.containsKey("user")) { user = params.getString("user"); } String party = ""; if (params.containsKey("party")) { party = params.getString("party"); } String tag = ""; if (params.containsKey("tag")) { tag = params.getString("tag"); } Integer agentId = properties.getAgentId(); if (params.containsKey("agentId")) { agentId = params.getInteger("agentId"); } if (wxCpServiceFinal == null) { wxCpServiceFinal = wxCpService; } WxCpMessage wxCpMessage = WxCpMessage.NEWS().agentId(agentId) .toUser(user) .toParty(party) .toTag(tag) .addArticle(articles) .build(); try { log.info("企业微信推送消息,send message: {}", wxCpMessage); WxCpMessageSendResult sendResult = wxCpServiceFinal.getMessageService().send(wxCpMessage); log.info("企业微信推送消息成功,send result: {}", sendResult); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm4CHAT_BOT(String content, JSONObject params) { // 调用企业微信群聊机器人发消息 WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService(); String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940"; List userList = Collections.singletonList("LvZhiQiang"); if (params != null && params.containsKey("user")) { userList = Arrays.asList(params.getString("user").split("[|,]")); } try { log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, userList); groupRobotService.sendText(webhookUrl, content, userList, Collections.emptyList()); log.info("企业微信推送消息成功"); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } @Override @Async("coinTaskExecutor") public void monitorAlarm(String content, String jobAlarmMode) { // 判断告警模式 if (StringUtils.isEmpty(JOB_ALARM_MODE)) { jobAlarmMode = JOB_ALARM_MODE; } // 文本卡片模式发消息 if (JOB_ALARM_MODE_APP_TEXT_CARD.equals(jobAlarmMode)) { String title = "监控告警明细"; String logUrl = "https://lvzhiqiang.top"; String btnTxt = "日志详情"; WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(properties.getAgentId()) .toUser("LvZhiQiang") .toParty("") .toTag("") .title(title).description(content) .url(logUrl).btnTxt(btnTxt) .build(); try { log.info("企业微信推送消息,send message: {}", wxCpMessage); WxCpMessageSendResult sendResult = wxCpService.getMessageService().send(wxCpMessage); log.info("企业微信推送消息成功,send result: {}", sendResult); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } // 调用企业微信群聊机器人发消息 if (JOB_ALARM_MODE_CHAT_BOT.equals(jobAlarmMode)) { WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService(); String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940"; try { log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, "LvZhiQiang"); groupRobotService.sendText(webhookUrl, content, Collections.singletonList("LvZhiQiang"), Collections.emptyList()); log.info("企业微信推送消息成功"); } catch (WxErrorException e) { log.error("企业微信推送消息失败!Detail: ", e); } } } /** * 请求通用API方法 */ private JSONObject requestApi4Common(String requestPath, String signQueryString, String signBody, String httpMethod, Map paramMap) { String timestamp = String.valueOf(System.currentTimeMillis()); Map headerMap = new HashMap<>(); headerMap.putAll(basicHeaderMap); try { String accessSign = CheckSign4Bitget.generate(timestamp, httpMethod, requestPath, signQueryString, signBody, secretKey); headerMap.put("ACCESS-TIMESTAMP", timestamp); headerMap.put("ACCESS-SIGN", accessSign); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } try { String requestUrl = mainUrl + requestPath; if (httpMethod.equals(JsoupUtil.HTTP_GET)) { Connection.Response response = JsoupUtil.requestBody(requestUrl, httpMethod, InitRunner.proxy, headerMap, paramMap); return JSONObject.parseObject(response.body()); } else { Connection.Response response = JsoupUtil.requestBodyJSON(requestUrl, httpMethod, InitRunner.proxy, null, headerMap, paramMap); return JSONObject.parseObject(response.body()); } } catch (Exception e) { throw new RuntimeException(e); } } private JSONObject requestApi4Common4OKX(String requestPath, String signQueryString, String signBody, String httpMethod, Map paramMap) { String timestamp = DateUtils.getUTCTimeStr(); Map headerMap = new HashMap<>(); headerMap.putAll(basicHeaderMap4OKX); try { String accessSign = CheckSign4OKX.generate(timestamp, httpMethod, requestPath, signQueryString, signBody, secretKey4OKX); headerMap.put("OK-ACCESS-TIMESTAMP", timestamp); headerMap.put("OK-ACCESS-SIGN", accessSign); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } try { String requestUrl = "https://www.okx.com" + requestPath; if (httpMethod.equals(JsoupUtil.HTTP_GET)) { Connection.Response response = JsoupUtil.requestBody(requestUrl, httpMethod, InitRunner.proxy, headerMap, paramMap); return JSONObject.parseObject(response.body()); } else { Connection.Response response = JsoupUtil.requestBodyJSON(requestUrl, httpMethod, InitRunner.proxy, null, headerMap, paramMap); return JSONObject.parseObject(response.body()); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public Object mainSearch(JSONObject params) throws Exception { JSONArray result = new JSONArray(); if (params.getString("nameEn").equals("allPositionv2")) { Map paramMap = new HashMap<>(); paramMap.put("productType", "umcbl"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4AllPositionv2(result, params.getInteger("unrealizedPLSort")); } else if (params.getString("nameEn").equals("orderMarginCoinCurrent")) { Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("marginCoin", "USDT"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4OrderMarginCoinCurrent(result, params.getInteger("chaRateSort")); } else if (params.getString("nameEn").equals("orderHistoryProductType")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List historyOrderList = coinMapper.findHistoryOrderList(params.toJavaObject(Map.class)); PageInfo historyOrderPageInfo = new PageInfo<>(historyOrderList); renderMainSearch4OrderHistoryProductType(historyOrderList); //result = (JSONArray) JSON.toJSON(historyOrderList); return historyOrderPageInfo; } else if (params.getString("nameEn").equals("traderList")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List mixTraderList = coinMapper.findMixTraderList(params.toJavaObject(Map.class)); PageInfo coinTraderPageInfo = new PageInfo<>(mixTraderList); renderMainSearch4TraderList(mixTraderList); //result = (JSONArray) JSON.toJSON(mixTraderList); return coinTraderPageInfo; } else if (params.getString("nameEn").equals("watchlist")) { JSONObject coinUser = coinMapper.findUserByUsername(params.getString("userName")); if (coinUser == null) { throw new ParameterException("用户不存在!"); } Integer userId = coinUser.getInteger("id"); params.put("userId",userId); PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); if (params.containsKey("sortField")) { params.put("sortField", Arrays.asList(params.getString("sortField").split(","))); } if (params.containsKey("cexFilterField") && StringUtils.isNotEmpty(params.getString("cexFilterField"))) { String[] cexFilterFieldArr = params.getString("cexFilterField").split(","); params.put("cexFilterName", cexFilterFieldArr[0]); params.put("cexFilterIndex", cexFilterFieldArr[1]); } List watchlistList = coinMapper.findWatchlistList2ByUserId(params.toJavaObject(Map.class)); PageInfo watchlistPageInfo = new PageInfo<>(watchlistList); renderMainSearch4Watchlist(watchlistList, userId); return watchlistPageInfo; } else if (params.getString("nameEn").equals("image")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List fileImageList = pictureInfoMapper.findImageList(params.toJavaObject(Map.class)); PageInfo imagePageInfo = new PageInfo<>(fileImageList); renderMainSearch4Image(fileImageList); return imagePageInfo; } else if (params.getString("nameEn").equals("cmcmap")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List cmcMapList = coinMapper.findCmcMapList(params.toJavaObject(Map.class)); PageInfo cmcMapPageInfo = new PageInfo<>(cmcMapList); renderMainSearch4CmcMap(cmcMapList); return cmcMapPageInfo; } else if (params.getString("nameEn").equals("monitorCurrency")) { List monitorCurrencyList = coinMapper.findMonitorCurrencyList(); Map resultMulti = new ConcurrentHashMap<>(); Arrays.stream(params.getString("url").split(",")).parallel().forEach(e -> { String requestUrl = mainUrl + e; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); resultMulti.put(e, JSONObject.parseObject(response.body()).getJSONArray("data")); } catch (Exception ex) { throw new RuntimeException(ex); } }); result = renderMainSearch4MonitorCurrency(resultMulti, monitorCurrencyList, params.getInteger("changeUtcSort")); } else if (params.getString("nameEn").equals("currentPlan")) { Map paramMap = new LinkedHashMap<>(); paramMap.put("productType", "umcbl"); paramMap.put("isPlan", "plan"); String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap); result = response.getJSONArray("data"); renderMainSearch4CurrentPlan(result, params.getInteger("chaRateSort")); } else if (params.getString("nameEn").equals("music")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); List fileMusicCollectionList = musicInfoMapper.findMusicCollectionList(params.toJavaObject(Map.class)); PageInfo musicCollectionPageInfo = new PageInfo<>(fileMusicCollectionList); renderMainSearch4Music(fileMusicCollectionList); return musicCollectionPageInfo; } else if (params.getString("nameEn").equals("currentHolding")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); if (params.containsKey("sortField")) { params.put("sortField", Arrays.asList(params.getString("sortField").split(","))); } List currentHoldingList = coinMapper.findCurrentHoldingList(params.toJavaObject(Map.class)); PageInfo currentHoldingPageInfo = new PageInfo<>(currentHoldingList); renderMainSearch4CurrencyHolding(currentHoldingList); return currentHoldingPageInfo; } else if (params.getString("nameEn").equals("bookmark")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); if (params.containsKey("sortField")) { params.put("sortField", Arrays.asList(params.getString("sortField").split(","))); } String level1CategoryField = params.getString("level1CategoryField"); String level2CategoryField = params.getString("level2CategoryField"); String level3CategoryField = params.getString("level3CategoryField"); String categoryId = "999999999999999"; if (StringUtils.isNotEmpty(level3CategoryField)) { categoryId = level3CategoryField; } else if (StringUtils.isNotEmpty(level2CategoryField)) { categoryId = level2CategoryField; } else if (StringUtils.isNotEmpty(level1CategoryField)) { categoryId = level1CategoryField; } String path = bookmarkMapper.selectPathByCategoryId(Long.valueOf(categoryId)); List categoryIds = bookmarkMapper.selectCategoryAndChildrenIds(Long.valueOf(categoryId), path); params.put("categoryIds", categoryIds); List bookmarkInfoList = bookmarkMapper.findBookmarkList(params.toJavaObject(Map.class)); PageInfo bookmarkInfoPageInfo = new PageInfo<>(bookmarkInfoList); renderMainSearch4Bookmark(bookmarkInfoList); return bookmarkInfoPageInfo; } else if (params.getString("nameEn").equals("youtubeLive")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); if (params.containsKey("sortField")) { params.put("sortField", Arrays.asList(params.getString("sortField").split(","))); } List yt2140LiveChapterList = coinYoutubeMapper.findYoutubeLiveList(params.toJavaObject(Map.class)); PageInfo yt2140LiveChapterPageInfo = new PageInfo<>(yt2140LiveChapterList); renderMainSearch4YoutubeYt2140LiveChapter(yt2140LiveChapterList); return yt2140LiveChapterPageInfo; } else if (params.getString("nameEn").equals("goldenQuotes")) { PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true); if (params.containsKey("sortField")) { params.put("sortField", Arrays.asList(params.getString("sortField").split(","))); } List goldenQuotesList = goldenQuotesMapper.findGoldenQuotesList(params.toJavaObject(Map.class)); PageInfo goldenQuotesPageInfo = new PageInfo<>(goldenQuotesList); renderMainSearch4GoldenQuotes(goldenQuotesList); return goldenQuotesPageInfo; } return result; } private void renderMainSearch4GoldenQuotes(List goldenQuotesList) { for (GoldenQuotes goldenQuotes : goldenQuotesList) { // 是否收藏 goldenQuotes.setIsPinnedStr(1 == goldenQuotes.getIsPinned() ? "是" : "否"); goldenQuotes.setTags(StringUtils.isNotEmpty(goldenQuotes.getTags()) ? goldenQuotes.getTags() : ""); String content = goldenQuotes.getContent().length() > 50 ? (goldenQuotes.getContent().substring(0, 50) + "...") : goldenQuotes.getContent(); goldenQuotes.setContent("" + content + " "); String remark; if (StringUtils.isNotEmpty(goldenQuotes.getRemark())) { if (goldenQuotes.getRemark().length() > 15) { remark = goldenQuotes.getRemark().substring(0, 15) + "..."; } else { remark = goldenQuotes.getRemark(); } } else { remark = ""; } goldenQuotes.setRemark("" + remark + " "); if (StringUtils.isNotEmpty(goldenQuotes.getOriginalUrl())) { goldenQuotes.setSourcePlatform("" + goldenQuotes.getSourcePlatform() + ""); } } } private void renderMainSearch4YoutubeYt2140LiveChapter(List yt2140LiveChapterList) { for (CoinYoutubeYt2140LiveChapterDTO yt2140LiveChapter : yt2140LiveChapterList) { String chapterUrl = yt2140LiveChapter.getOriginalUrl().concat("&t=").concat(String.valueOf(yt2140LiveChapter.getStartTime())).concat("s"); // 顺序 yt2140LiveChapter.setChapterSort(yt2140LiveChapter.getSort() + "/" + yt2140LiveChapter.getChapterCount()); // 开始时间 yt2140LiveChapter.setReadableStartTime("" + formatTime(yt2140LiveChapter.getStartTime()) + ""); // 结束时间 yt2140LiveChapter.setReadableEndTime(formatTime(yt2140LiveChapter.getEndTime() - 1)); } } public String formatTime(long totalSeconds) { if (totalSeconds >= 3600) { return DurationFormatUtils.formatDuration(totalSeconds * 1000, "H:mm:ss"); // 2:20:20 } else { return DurationFormatUtils.formatDuration(totalSeconds * 1000, "mm:ss"); // 20:20 } } private void renderMainSearch4Bookmark(List bookmarkInfoList) { for (BookmarkInfo bookmarkInfo : bookmarkInfoList) { // 名称 bookmarkInfo.setTitle("" + bookmarkInfo.getTitle() + ""); // 是否收藏 bookmarkInfo.setIsFavoriteStr(bookmarkInfo.getIsFavorite() == 1 ? "是" : "否"); // 状态 bookmarkInfo.setStatusStr(bookmarkInfo.getStatus() == 1 ? "正常" : "失效"); // tags bookmarkInfo.setTags(StringUtils.isEmpty(bookmarkInfo.getTags()) ? "--" : bookmarkInfo.getTags()); // sourceUrl if (StringUtils.isNotEmpty(bookmarkInfo.getSourceUrl())) { bookmarkInfo.setSourceUrl("" + "goGO" + ""); } else { bookmarkInfo.setSourceUrl("--"); } // 备注 String remark; if (StringUtils.isNotEmpty(bookmarkInfo.getRemark())) { if (bookmarkInfo.getRemark().length() > 20) { remark = bookmarkInfo.getRemark().substring(0, 20) + "..."; } else { remark = bookmarkInfo.getRemark(); } } else { remark = "--"; } bookmarkInfo.setRemark("" + remark + " "); } } private void renderMainSearch4CurrencyHolding(List currentHoldingList) { for (CoinCurrencyHolding coinCurrencyHolding : currentHoldingList) { // 名称 coinCurrencyHolding.setSymbolStyle(" style=\"background-color:rgba(70,169,244,.72);font-weight: bold;\""); // 当前价格 coinCurrencyHolding.setCurrentPriceStyle(" style=\"color:#252B31;background-color:#C4ADE9;\""); if (StringUtils.isNotEmpty(coinCurrencyHolding.getCurrentPrice())) { coinCurrencyHolding.setCurrentPrice(new BigDecimal(coinCurrencyHolding.getCurrentPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString()); } // 入场价格 if (StringUtils.isNotEmpty(coinCurrencyHolding.getBuyPrice())) { coinCurrencyHolding.setBuyPrice(new BigDecimal(coinCurrencyHolding.getBuyPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString()); } coinCurrencyHolding.setStatusStr(coinCurrencyHolding.getStatus() == 1 ? "正常" : "失效"); // 涨跌幅比例 if (coinCurrencyHolding.getChangePercentage() == null) { } else if (coinCurrencyHolding.getChangePercentage().compareTo(BigDecimal.ONE) < 0) { coinCurrencyHolding.setChangePercentageStyle(" style=\"color:#000000;background-color:#f1a8a4;\""); } else { coinCurrencyHolding.setChangePercentageStyle(" style=\"color:#000000;background-color:#aad6f5;\""); } } } private void renderMainSearch4CmcMap(List cmcMapList) { for (CoinCmcMap coinCmcMap : cmcMapList) { String platform = coinCmcMap.getPlatform(); if (StringUtils.isNotEmpty(platform)) { JSONObject jsonObject = JSONObject.parseObject(platform); jsonObject.remove("token_address"); coinCmcMap.setPlatform(jsonObject.toJSONString()); } } } private void renderMainSearch4Image(List fileImageList) { // String ftpBaseurl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue(); for (FileImage fileImage : fileImageList) { // String newPath = "" + fileImage.getNewName() + ""; // fileImage.setNewName(newPath); // fileImage.setOldName("" + fileImage.getOldName() + " "); fileImage.setRemark("" + fileImage.getRemark() + " "); } } private void renderMainSearch4Music(List fileMusicCollectionList) { List fileMusicCategoryList = coinApiConfigMapper.findFileMusicCategoryList(); Map fileMusicCategoryMap = fileMusicCategoryList.stream().collect(Collectors.toMap(e -> e.getString("id"), e -> e.getString("categoryName"))); String ftpBasePath = InitRunner.dicCodeMap.get("ftp_music_basepath").getCodeValue(); String ftpBaseUrl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue(); String[] scoreTitleArr = {"很差", "较差", "还行", "推荐", "力荐"}; for (FileMusicCollection fileMusicCollection : fileMusicCollectionList) { String categoryName = Arrays.stream(fileMusicCollection.getCategoryId().split(",")).map(original -> fileMusicCategoryMap.getOrDefault(original, original)).collect(Collectors.joining(",")); fileMusicCollection.setCategoryName(categoryName); String remark = StringUtils.isNotEmpty(fileMusicCollection.getRemark()) && fileMusicCollection.getRemark().length() > 20 ? (fileMusicCollection.getRemark().substring(0, 20) + "...") : fileMusicCollection.getRemark(); fileMusicCollection.setRemark("" + remark + " "); if (StringUtils.isNotEmpty(fileMusicCollection.getLowQualityUrl())) { fileMusicCollection.setLowQualityUrl(ftpBaseUrl + ftpBasePath + fileMusicCollection.getLowQualityUrl()); } if (StringUtils.isNotEmpty(fileMusicCollection.getHighQualityUrl())) { fileMusicCollection.setHighQualityUrl(ftpBaseUrl + ftpBasePath + fileMusicCollection.getHighQualityUrl()); } // 优先级 Integer score = Integer.valueOf(fileMusicCollection.getScore()); score = score > 5 ? 5 : score; StringBuffer scoreSB = new StringBuffer("
    "); for (int i = 0; i < scoreTitleArr.length; i++) { String style = (i + 1) <= score ? "fa-star" : "fa-star-o"; scoreSB.append("
  • "); } scoreSB.append("
"); fileMusicCollection.setScore(scoreSB.toString()); } } private void renderMainSearch4Watchlist(List watchlistList, Integer userId) { BigDecimal bigDecimal10000 = new BigDecimal("10000"); List popularTrackCategoryList = Arrays.asList("DePIN", "AI", "RWA", "大饼生态", "以太Layer-2", "Restaking再质押", "NFT|链游|元宇宙", "WEB3社交"); // 赛道分类预处理 List trackCategoryList = coinApiConfigMapper.findTrackCategoryListByUserId(userId).stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList()); List trackCategory2List = coinApiConfigMapper.findTrackCategory2ListByUserId(userId).stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList()); Map trackCategoryMap = new HashMap<>(); Map trackCategory2Map = new HashMap<>(); List colorList = coinApiConfigMapper.findColorStyleList(); int j = 0; for (int i = 0; i < trackCategoryList.size(); i++) { if (j > colorList.size() - 1) { j = 0; } String popularTrackCategoryStyle = popularTrackCategoryList.contains(trackCategoryList.get(i)) ? "font-weight:bold;" : ""; trackCategoryMap.put(trackCategoryList.get(i), colorList.get(j) + popularTrackCategoryStyle); j++; } j = 0; for (int i = 0; i < trackCategory2List.size(); i++) { if (j > colorList.size() - 1) { j = 0; } String popularTrackCategoryStyle = popularTrackCategoryList.contains(trackCategory2List.get(i)) ? "font-weight:bold;" : ""; trackCategory2Map.put(trackCategory2List.get(i), colorList.get(j) + popularTrackCategoryStyle); j++; } String[] scoreTitleArr = {"很差", "较差", "还行", "推荐", "力荐"}; String regex = "[+-]?\\d*\\.?\\d*[eE][+-]?\\d+"; // 科学计数法正则表达式 for (CoinWatchlist coinWatchlist : watchlistList) { // 优先级 Integer score = Integer.valueOf(coinWatchlist.getScore()); score = score > 5 ? 5 : score; StringBuffer scoreSB = new StringBuffer("
    "); for (int i = 0; i < scoreTitleArr.length; i++) { String style = (i + 1) <= score ? "fa-star" : "fa-star-o"; scoreSB.append("
  • "); } scoreSB.append("
"); coinWatchlist.setScore(scoreSB.toString()); // 流通市值 if (null != coinWatchlist.getTotalMarketValue()) { BigDecimal divide = coinWatchlist.getTotalMarketValue().divide(bigDecimal10000, 8, RoundingMode.HALF_UP); if (divide.compareTo(bigDecimal10000) <= 0) { coinWatchlist.setTotalMarketValueStr(divide.setScale(2, RoundingMode.HALF_UP) + "万"); } else { divide = divide.divide(bigDecimal10000, 2, RoundingMode.HALF_UP); coinWatchlist.setTotalMarketValueStr(divide + "亿"); } coinWatchlist.setTotalMarketValueStr("" + coinWatchlist.getTotalMarketValueStr() + " "); } // 赛道分类 String[] trackCategoryArr = coinWatchlist.getTrackCategory().split(","); StringBuffer sb = new StringBuffer(); String trackCategoryLength = ""; for (int i = 0; i < trackCategoryArr.length; i++) { if (i == trackCategoryArr.length - 1) { if (trackCategoryArr.length > 1 && trackCategoryLength.length() > 10) { sb.append("
"); } sb.append("" + trackCategoryArr[i] + " "); } else { trackCategoryLength += trackCategoryArr[i]; sb.append("" + trackCategoryArr[i] + " "); } } coinWatchlist.setTrackCategoryStyle(" style=\"padding:0em 0.3em;\""); coinWatchlist.setTrackCategory(sb.toString()); String[] trackCategory2Arr = coinWatchlist.getTrackCategory2().split(","); StringBuffer sb2 = new StringBuffer(); for (int i = 0; i < trackCategory2Arr.length; i++) { sb2.append("" + trackCategory2Arr[i] + " "); if (i % 2 == 1 && i != trackCategory2Arr.length - 1) { sb2.append("
"); } } coinWatchlist.setTrackCategory2Style(" style=\"padding:0em 0.3em;\""); coinWatchlist.setTrackCategory2(sb2.toString()); // 名称 coinWatchlist.setSymbolStyle(" style=\"background-color:rgba(70,169,244,.72);font-weight: bold;\""); // 市场价格 coinWatchlist.setMarkPriceStyle(" style=\"color:#252B31;background-color:#C4ADE9;\""); if (StringUtils.isNotEmpty(coinWatchlist.getMarkPrice())) { if (!coinWatchlist.getMarkPrice().matches(regex)) { //coinWatchlist.setMarkPrice(new BigDecimal(coinWatchlist.getMarkPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString()); coinWatchlist.setMarkPrice(formatNumber(coinWatchlist.getMarkPrice())); } } if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice())) { if (!coinWatchlist.getHighestHistoricalPrice().matches(regex)) { //coinWatchlist.setHighestHistoricalPrice(new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString()); coinWatchlist.setHighestHistoricalPrice(formatNumber(coinWatchlist.getHighestHistoricalPrice())); } } if (StringUtils.isNotEmpty(coinWatchlist.getLowestHistoricalPrice())) { if (!coinWatchlist.getLowestHistoricalPrice().matches(regex)) { //coinWatchlist.setLowestHistoricalPrice(new BigDecimal(coinWatchlist.getLowestHistoricalPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString()); coinWatchlist.setLowestHistoricalPrice(formatNumber(coinWatchlist.getLowestHistoricalPrice())); } } // 24小时价格变化 if (coinWatchlist.getPriceChangePercentage24h() == null) { } else if (coinWatchlist.getPriceChangePercentage24h().compareTo(BigDecimal.ZERO) < 0) { coinWatchlist.setPriceChangePercentage24hStyle(" style=\"color:#000000;background-color:#f1a8a4;\""); } else { coinWatchlist.setPriceChangePercentage24hStyle(" style=\"color:#000000;background-color:#aad6f5;\""); } /*// 历史最高点涨幅比例 if (coinWatchlist.getAthChangePercentage() == null) { } else if (coinWatchlist.getAthChangePercentage().compareTo(BigDecimal.ZERO) < 0) { coinWatchlist.setAthChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { coinWatchlist.setAthChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 历史最低点涨幅比例 if (coinWatchlist.getAtlChangePercentage() == null) { } else if (coinWatchlist.getAtlChangePercentage().compareTo(BigDecimal.ZERO) < 0) { coinWatchlist.setAtlChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { coinWatchlist.setAtlChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#1DA2B4;\""); }*/ // 涨幅倍数2 if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice()) && StringUtils.isNotEmpty(coinWatchlist.getMarkPrice())) { BigDecimal increaseMultiple = new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(new BigDecimal(coinWatchlist.getMarkPrice()), 1, RoundingMode.HALF_UP); coinWatchlist.setIncreaseMultiple2(increaseMultiple.toPlainString()); if (increaseMultiple.compareTo(new BigDecimal("50")) >= 0) { coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#5a964e;\""); } else if (increaseMultiple.compareTo(new BigDecimal("10")) >= 0) { coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#9cc494;\""); } else { coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#dae8d7;\""); } } } } private static String formatNumber(String str) { double number = Double.parseDouble(str); int dotIndex = str.indexOf('.'); if (dotIndex == -1) { return str; // 无小数点,直接返回 } // 检查小数点后连续0的数量 int zeroCount = 0; for (int i = dotIndex + 1; i < str.length(); i++) { if (str.charAt(i) == '0') { zeroCount++; } else { break; // 遇到非0字符停止计数 } } // 如果0的数量 >=5,用科学计数法;否则用普通小数 if (zeroCount >= 5) { DecimalFormat df = new DecimalFormat("0.###E0"); // 科学计数法 return df.format(number).replace("E-0", "E-"); // 修正E-09为E-9 } else { return new BigDecimal(str).divide(BigDecimal.ONE, new MathContext(3)).stripTrailingZeros().toPlainString(); } } private void renderMainSearch4TraderList(List mixTraderList) { for (CoinTrader mixTrader : mixTraderList) { mixTrader.setLastTradeTime(DateUtils.longToString(Long.valueOf(mixTrader.getLastTradeTime()))); } } /** * 渲染获取当前计划委托(止盈止损)列表 * * @param result */ private void renderMainSearch4CurrentPlan(JSONArray result, Integer chaRateSort) { forkJoinPool3.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 订单状态 jsonObject.put("status", InitRunner.publicParamsMap.get("status").getString(jsonObject.getString("status"))); // 交易类型 jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType"))); // 订单类型 jsonObject.put("planType", InitRunner.publicParamsMap.get("planType").getString(jsonObject.getString("planType"))); // 开单方向 String side = jsonObject.getString("side"); jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side)); if (side.equals("open_long")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (side.equals("open_short")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\""); } // 触发类型 jsonObject.put("triggerType", InitRunner.publicParamsMap.get("triggerType").getString(jsonObject.getString("triggerType"))); jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("uTime", StringUtils.isEmpty(jsonObject.getString("uTime")) ? "--" : DateUtils.longToString(jsonObject.getLong("uTime"))); // 获取合约标记价格 String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice"); BigDecimal chaRate = BigDecimal.ZERO; BigDecimal triggerPriceDecimal = new BigDecimal(jsonObject.getString("triggerPrice")); BigDecimal markPriceDecimal = new BigDecimal(markPrice); if (markPriceDecimal.compareTo(triggerPriceDecimal) < 0) { chaRate = markPriceDecimal.divide(triggerPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } else if (markPriceDecimal.compareTo(triggerPriceDecimal) > 0) { chaRate = triggerPriceDecimal.divide(markPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } jsonObject.put("markPrice", markPrice); jsonObject.put("markPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); jsonObject.put("chaRate", chaRate); jsonObject.put("chaRateStyle", " style=\"color:#FFFFFF;background-color:#5EA294;\""); } catch (Exception ex) { throw new RuntimeException(ex); } })).join(); if (chaRateSort != 0) { Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate")))); } } /** * 渲染监控币种列表 * * @param monitorCurrencyList */ private JSONArray renderMainSearch4MonitorCurrency(Map resultMulti, List monitorCurrencyList, Integer changeUtcSort) { Map monitorCurrencyMap4Mix = monitorCurrencyList.stream().filter(e -> e.getType().equals("2")).collect(Collectors.toMap(CoinMonitorCurrency::getSymbol, Function.identity(), (key1, key2) -> key1)); Set symbolSet4Mix = monitorCurrencyMap4Mix.keySet(); Map monitorCurrencyMap4Spot = monitorCurrencyList.stream().filter(e -> e.getType().contains("1")).collect(Collectors.toMap(CoinMonitorCurrency::getSymbol, Function.identity(), (key1, key2) -> key1)); Set symbolSet4Spot = monitorCurrencyMap4Spot.keySet(); JSONArray array4Spot = resultMulti.get("/api/spot/v1/market/tickers").stream() .filter(iter -> symbolSet4Spot.contains(((JSONObject) iter).getString("symbol"))) .collect(Collectors.toCollection(JSONArray::new)); JSONArray array4Mix = resultMulti.get("/api/mix/v1/market/tickers?productType=umcbl").stream() .filter(iter -> symbolSet4Mix.contains(((JSONObject) iter).getString("symbol"))) .collect(Collectors.toCollection(JSONArray::new)); forkJoinPool.submit(() -> array4Spot.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; jsonObject.put("changeUtc", jsonObject.getBigDecimal("changeUtc").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("change", jsonObject.getBigDecimal("change").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("ts", DateUtils.longToString(jsonObject.getLong("ts"))); jsonObject.put("category", monitorCurrencyMap4Spot.get(jsonObject.getString("symbol")).getCategory()); // UTC0时涨跌幅 if (jsonObject.getBigDecimal("changeUtc").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 24小时涨跌幅 if (jsonObject.getBigDecimal("change").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 币对名称 String symbol = jsonObject.getString("symbol").replace("USDT", ""); if ("BTC".equals(symbol) || "ETH".equals(symbol)) { jsonObject.put("symbol", "" + symbol + "USDT"); } else { jsonObject.put("symbol", "" + symbol + "USDT"); } // 标记价格 jsonObject.put("closeStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); // 基础币量 计价币量 usdt币量 jsonObject.put("baseVol", readableFileSize(jsonObject.getDouble("baseVol"))); jsonObject.put("quoteVol", readableFileSize(jsonObject.getDouble("quoteVol"))); jsonObject.put("usdtVol", readableFileSize(jsonObject.getDouble("usdtVol"))); })).join(); forkJoinPool.submit(() -> array4Mix.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; jsonObject.put("changeUtc", jsonObject.getBigDecimal("chgUtc").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("change", jsonObject.getBigDecimal("priceChangePercent").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP)); jsonObject.put("ts", DateUtils.longToString(jsonObject.getLong("timestamp"))); jsonObject.put("category", monitorCurrencyMap4Mix.get(jsonObject.getString("symbol")).getCategory()); // UTC0时涨跌幅 if (jsonObject.getBigDecimal("changeUtc").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 24小时涨跌幅 if (jsonObject.getBigDecimal("change").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 币对名称 String symbol = jsonObject.getString("symbol").replace("USDT_UMCBL", ""); jsonObject.put("symbol", "" + symbol + "USDT_UMCBL"); // 标记价格 jsonObject.put("close", jsonObject.getString("last")); jsonObject.put("closeStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); // 基础币量 计价币量 usdt币量 jsonObject.put("baseVol", readableFileSize(jsonObject.getDouble("baseVolume"))); jsonObject.put("quoteVol", readableFileSize(jsonObject.getDouble("quoteVolume"))); jsonObject.put("usdtVol", readableFileSize(jsonObject.getDouble("quoteVolume"))); // 其他字段兼容 jsonObject.put("openUtc0", jsonObject.getString("openUtc")); jsonObject.put("buyOne", jsonObject.getString("bestBid")); jsonObject.put("sellOne", jsonObject.getString("bestAsk")); })).join(); array4Spot.addAll(array4Mix); if (changeUtcSort != 0) { Collections.sort(array4Spot, (o1, o2) -> changeUtcSort * (((JSONObject) o1).getBigDecimal("changeUtc").compareTo(((JSONObject) o2).getBigDecimal("changeUtc")))); } return array4Spot; } /** * 渲染获取全部历史委托 * * @param historyOrderList */ private void renderMainSearch4OrderHistoryProductType(List historyOrderList) { for (CoinHistoryOrder coinHistoryOrder : historyOrderList) { // 币种名称 coinHistoryOrder.setSymbol(coinHistoryOrder.getSymbol().replace("USDT_UMCBL", "")); // 订单状态 coinHistoryOrder.setState(InitRunner.publicParamsMap.get("state").getString(coinHistoryOrder.getState())); // 开单方向 coinHistoryOrder.setSide(InitRunner.publicParamsMap.get("side").getString(coinHistoryOrder.getSide())); // 总盈亏 String TotalProfits = "0E-8"; if (!coinHistoryOrder.getTotalProfits().contains("0E-8")) { TotalProfits = new BigDecimal(coinHistoryOrder.getTotalProfits()).setScale(2, RoundingMode.HALF_UP).toPlainString(); } coinHistoryOrder.setTotalProfits(TotalProfits); // 手续费 String fee = "0E-8"; if (!coinHistoryOrder.getFee().contains("0E-8")) { fee = new BigDecimal(coinHistoryOrder.getFee()).setScale(2, RoundingMode.HALF_UP).toPlainString(); } coinHistoryOrder.setFee(fee); // 持仓方向 coinHistoryOrder.setPosSide(InitRunner.publicParamsMap.get("posSide").getString(coinHistoryOrder.getPosSide())); // 仓位模式 coinHistoryOrder.setMarginMode(InitRunner.publicParamsMap.get("marginMode").getString(coinHistoryOrder.getMarginMode())); // 交易类型 coinHistoryOrder.setOrderType(InitRunner.publicParamsMap.get("orderType").getString(coinHistoryOrder.getOrderType())); // 交易方向 coinHistoryOrder.setTradeSide(InitRunner.publicParamsMap.get("tradeSide").getString(coinHistoryOrder.getTradeSide())); // 持仓模式 coinHistoryOrder.setHoldMode(InitRunner.publicParamsMap.get("holdMode").getString(coinHistoryOrder.getHoldMode())); // orderSource coinHistoryOrder.setOrderSource(InitRunner.publicParamsMap.get("orderSource").getString(coinHistoryOrder.getOrderSource())); coinHistoryOrder.setCTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getCTime()))); coinHistoryOrder.setUTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getUTime()))); } } /** * 渲染获取全部当前委托 * * @param result */ private void renderMainSearch4OrderMarginCoinCurrent(JSONArray result, Integer chaRateSort) { forkJoinPool2.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 订单状态 jsonObject.put("state", InitRunner.publicParamsMap.get("state").getString(jsonObject.getString("state"))); // 开单方向 String side = jsonObject.getString("side"); jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side)); if (side.equals("open_long")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (side.equals("open_short")) { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\""); } // 交易类型 jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType"))); // 止盈止损 jsonObject.put("presetTakeProfitPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice")); jsonObject.put("presetStopLossPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice")); // 持仓模式 jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode"))); // orderSource jsonObject.put("orderSource", InitRunner.publicParamsMap.get("orderSource").getString(jsonObject.getString("orderSource"))); // 仓位模式 jsonObject.put("marginMode", InitRunner.publicParamsMap.get("marginMode").getString(jsonObject.getString("marginMode"))); jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("uTime", DateUtils.longToString(jsonObject.getLong("uTime"))); // 获取合约标记价格 String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice"); BigDecimal chaRate = BigDecimal.ZERO; if ("open_short".equals(side)) { chaRate = new BigDecimal(markPrice).divide(new BigDecimal(jsonObject.getString("price")), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } else if ("open_long".equals(side)) { chaRate = new BigDecimal(jsonObject.getString("price")).divide(new BigDecimal(markPrice), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); } jsonObject.put("markPrice", markPrice); jsonObject.put("markPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); jsonObject.put("chaRate", chaRate); jsonObject.put("chaRateStyle", " style=\"color:#FFFFFF;background-color:#5EA294;\""); } catch (Exception ex) { throw new RuntimeException(ex); } })).join(); if (chaRateSort != 0) { Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate")))); } } /** * 渲染获取全部合约仓位信息V2 * * @param result */ private void renderMainSearch4AllPositionv2(JSONArray result, Integer unrealizedPLSort) { forkJoinPool4.submit(() -> result.parallelStream().forEach(e -> { JSONObject jsonObject = (JSONObject) e; // 币对名称 String symbol = jsonObject.getString("symbol"); jsonObject.put("symbol", "" + symbol.replace("USDT_UMCBL", "") + "USDT_UMCBL"); // 持仓方向 String holdSide = jsonObject.getString("holdSide"); jsonObject.put("holdSide", InitRunner.publicParamsMap.get("holdSide").getString(holdSide)); if (holdSide.equals("long")) { jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } else if (holdSide.equals("short")) { jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\""); } // 保证金模式 jsonObject.put("marginMode", InitRunner.publicParamsMap.get("marginMode").getString(jsonObject.getString("marginMode"))); // 持仓模式 jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode"))); // 最近更新时间 保证金数量 (保证金币种) 平均开仓价 未实现盈亏 预估强平价 jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime"))); jsonObject.put("margin", new BigDecimal(jsonObject.getString("margin")).setScale(4, RoundingMode.HALF_UP)); jsonObject.put("averageOpenPrice", new BigDecimal(jsonObject.getString("averageOpenPrice")).divide(BigDecimal.ONE, new MathContext(4))); jsonObject.put("unrealizedPL", new BigDecimal(jsonObject.getString("unrealizedPL")).setScale(4, RoundingMode.HALF_UP)); jsonObject.put("liquidationPrice", new BigDecimal(jsonObject.getString("liquidationPrice")).divide(BigDecimal.ONE, new MathContext(4))); // 未实现盈亏 if (jsonObject.getBigDecimal("unrealizedPL").compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 回报率=未实现盈亏/保证金 BigDecimal returnRate = jsonObject.getBigDecimal("unrealizedPL").divide(jsonObject.getBigDecimal("margin"), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP); jsonObject.put("returnRate", returnRate); if (returnRate.compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } // 获取当前资金费率 String requestUrl = mainUrl + "/api/mix/v1/market/current-fundRate?symbol=" + symbol; try { Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null); String fundingRate = JSONObject.parseObject(response.body()).getJSONObject("data").getString("fundingRate"); if (new BigDecimal(fundingRate).compareTo(BigDecimal.ZERO) < 0) { jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\""); } else { jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\""); } jsonObject.put("fundingRate", new BigDecimal(fundingRate).multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP).toPlainString() + "%"); } catch (Exception ex) { throw new RuntimeException(ex); } // 标记价格 jsonObject.put("marketPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\""); })).join(); if (unrealizedPLSort != 0) { Collections.sort(result, (o1, o2) -> unrealizedPLSort * (((JSONObject) o1).getBigDecimal("unrealizedPL").compareTo(((JSONObject) o2).getBigDecimal("unrealizedPL")))); } } /** * Java实现字节转换,可以自动转换为B、KB、MB、GB、TB * * @param size * @return */ private String readableFileSize(double size) { if (size <= 0) { return "0"; } final String[] units = new String[]{"B", "K", "M", "G", "T"}; int digitGroups = (int) (Math.log10(size) / Math.log10(1000)); return df1.format(size / Math.pow(1000, digitGroups)) + units[digitGroups]; } public String getMonitorJobStatus(String jobName) { Map monitorJobConfig = getMonitorJobConfig(); if (null != monitorJobConfig && monitorJobConfig.containsKey(jobName)) { return monitorJobConfig.get(jobName).getString("job_status"); } return null; } @Override public Map getMonitorJobConfig() { String cacheKey = "coin:monitor:job:list"; // 1. 缓存有,直接返回 if (redisUtils.hasKey(cacheKey) && redisUtils.get(cacheKey) != null) { return (Map) redisUtils.get(cacheKey); } // 加锁防止同时对一个数据发送多次请求 RLock lock = redissonClient.getLock("lock:" + cacheKey); try { // 2. 尝试加分布式锁,最多等待30秒,上锁以后60秒自动解锁 boolean lockFlag = lock.tryLock(30, 60, TimeUnit.SECONDS); if (lockFlag) { // 3. 加锁成功,二次检查,缓存有,直接返回 if (redisUtils.hasKey(cacheKey) && redisUtils.get(cacheKey) != null) { return (Map) redisUtils.get(cacheKey); } // 4. 查数据库,并且按给定的时长加到缓存中 Map monitorJobConfigMap = coinMapper.findMonitorJobConfig(); // 缓存监控任务配置信息 if (monitorJobConfigMap != null) { redisUtils.set(cacheKey, monitorJobConfigMap, 60, TimeUnit.MINUTES); } return monitorJobConfigMap; } else { log.error("getMonitorJobConfig 加锁失败 error,lockFlag: false"); return null; } } catch (Exception e) { log.error("getMonitorJobConfig Exception", e); return InitRunner.monitorJobConfigMap; } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } @Override public Object mainSearchDetail(String userName, String nameEn, String id) { if ("image".equals(nameEn)) { FileImage fileImage = pictureInfoMapper.findFileImageById(Long.valueOf(id)); String ftpBaseurl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue(); String ftpBasePath = InitRunner.dicCodeMap.get("ftp_basepath").getCodeValue(); String ftpThumbnailBasePath = InitRunner.dicCodeMap.get("ftp_thumbnail_basepath").getCodeValue(); String path = fileImage.getPath(); fileImage.setPath(ftpBaseurl + ftpBasePath + path); fileImage.setThumbnailPath(ftpBaseurl + ftpThumbnailBasePath + path); return fileImage; } else if ("watchlist".equals(nameEn)) { Integer userId; if (StringUtils.isEmpty(userName)) { throw new ParameterException("userName为空!"); } else { JSONObject coinUser = coinMapper.findUserByUsername(userName); if (coinUser == null) { throw new ParameterException("用户不存在!"); } userId = coinUser.getInteger("id"); } CoinWatchlist coinWatchlist = coinMapper.findWatchlistUserBySymbolAndUserId(id, userId); String watchlistOtherStr = parseWatchlistOther(id); coinWatchlist.setRemark(watchlistOtherStr + MarkdownToHtmlUtils.markdownToHtmlExtensions(coinWatchlist.getRemark())); coinWatchlist.setFeixiaohaoUrl("https://www.feixiaohao.com/currencies/" + coinWatchlist.getFeixiaohaoUrl()); coinWatchlist.setCmcUrl("https://coinmarketcap.com/zh/currencies/" + coinWatchlist.getCmcUrl()); coinWatchlist.setCoingeckoUrl("https://www.coingecko.com/zh/%E6%95%B0%E5%AD%97%E8%B4%A7%E5%B8%81/" + coinWatchlist.getCoingeckoUrl()); return coinWatchlist; } return null; } private String parseWatchlistOther(String id) { CoinWatchlistOther coinWatchlistOther = coinMapper.findWatchlistOtherBySymbol(id); StringBuffer sb = new StringBuffer(); sb.append(""); sb.append("").append(""); sb.append("").append(""); sb.append("").append(""); sb.append("").append(""); sb.append("").append(""); sb.append("").append(""); String[] coingeckoExchangeNameArr = InitRunner.dicCodeMap.get("coingecko_exchange_names").getCodeValue().split(","); List coingeckoExchangeNameList = new ArrayList<>(); if (StringUtils.isNotEmpty(coinWatchlistOther.getCexSpot()) && coingeckoExchangeNameArr.length == coinWatchlistOther.getCexSpot().length()) { for (int i = 0; i < coingeckoExchangeNameArr.length; i++) { if ("1".equals(String.valueOf(coinWatchlistOther.getCexSpot().charAt(i)))) { coingeckoExchangeNameList.add(coingeckoExchangeNameArr[i]); } } } sb.append("").append(""); String[] coingeckoExchangeFuturesNameArr = InitRunner.dicCodeMap.get("coingecko_exchange_futures_names").getCodeValue().split(","); List coingeckoExchangeFuturesNameList = new ArrayList<>(); if (StringUtils.isNotEmpty(coinWatchlistOther.getCexPerpetual()) && coingeckoExchangeFuturesNameArr.length == coinWatchlistOther.getCexPerpetual().length()) { for (int i = 0; i < coingeckoExchangeFuturesNameArr.length; i++) { if ("1".equals(String.valueOf(coinWatchlistOther.getCexPerpetual().charAt(i)))) { coingeckoExchangeFuturesNameList.add(coingeckoExchangeFuturesNameArr[i]); } } } sb.append("").append(""); sb.append("
").append("流通市值").append("").append(convertHumanReadable(coinWatchlistOther.getMarketCap())).append("
").append("流通供应量").append("").append(convertHumanReadable(coinWatchlistOther.getCirculatingSupply())).append("
").append("流通率").append("").append(StringUtils.isNotEmpty(coinWatchlistOther.getCirculatingRate()) ? new BigDecimal(coinWatchlistOther.getCirculatingRate()).setScale(1, RoundingMode.HALF_UP) + "%" : "--").append("
").append("总供应量").append("").append(convertHumanReadable(coinWatchlistOther.getTotalSupply())).append("
").append("最大供应量").append("").append(convertHumanReadable(coinWatchlistOther.getMaxSupply())).append("
").append("完全稀释的市值").append("").append(convertHumanReadable(coinWatchlistOther.getFullyDilutedMarketCap())).append("
").append("现货").append("").append(coingeckoExchangeNameList.size() > 0 ? String.join("、", coingeckoExchangeNameList) : "--").append("
").append("合约").append("").append(coingeckoExchangeFuturesNameList.size() > 0 ? String.join("、", coingeckoExchangeFuturesNameList) : "--").append("
"); return sb.toString(); } @Override public void debugTest() { Map params = new HashMap<>(); params.put("sortField", Collections.singletonList("create_time")); params.put("sort", "desc"); ArrayList symbolList = new ArrayList(); symbolList.add("PYTH"); symbolList.add("SNX"); symbolList.add("REEF"); //List watchlistList = coinMapper.findWatchlistList(params); // watchlistList = watchlistList.stream().filter(x -> symbolList.contains(x.getSymbol())).collect(Collectors.toList()); //Map coinWatchlistMap4CoingeckoId = watchlistList.stream().collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist)); //parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId); //Map coinWatchlistMap4CmcId = watchlistList.stream().collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist)); // parseWatchlistMap4CmC(coinWatchlistMap4CmcId); syncCexFlag("PONKE"); } @Override @Async("coinTaskExecutor") public void initWatchlist(CoinWatchlist coinWatchlist2) { Map params = new HashMap<>(); params.put("symbol", coinWatchlist2.getSymbol()); params.put("sortField", Collections.singletonList("create_time")); params.put("sort", "desc"); List watchlistListCKO = coinMapper.findWatchlistList(params); Map coinWatchlistMap4CoingeckoId = watchlistListCKO.stream().collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist)); parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId); List watchlistListCMC = coinMapper.findWatchlistList(params); Map coinWatchlistMap4CmcId = watchlistListCMC.stream().collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist)); parseWatchlistMap4CmC(coinWatchlistMap4CmcId); // 初始化cexflag信息 syncCexFlag(coinWatchlist2.getSymbol()); } public String convertHumanReadable(String number) { if (StringUtils.isEmpty(number)) { return "--"; } BigDecimal bigDecimal10000 = new BigDecimal("10000"); BigDecimal divide = new BigDecimal(number).divide(bigDecimal10000, 8, RoundingMode.HALF_UP); if (divide.compareTo(bigDecimal10000) <= 0) { return divide.setScale(2, RoundingMode.HALF_UP) + "万"; } else { return divide.divide(bigDecimal10000, 2, RoundingMode.HALF_UP) + "亿"; } } }