CoinServiceImpl.java 160 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934
  1. package top.lvzhiqiang.service.impl;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.github.pagehelper.PageHelper;
  5. import com.github.pagehelper.PageInfo;
  6. import lombok.extern.slf4j.Slf4j;
  7. import me.chanjar.weixin.common.error.WxErrorException;
  8. import me.chanjar.weixin.cp.api.WxCpGroupRobotService;
  9. import me.chanjar.weixin.cp.api.WxCpService;
  10. import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
  11. import me.chanjar.weixin.cp.bean.article.NewArticle;
  12. import me.chanjar.weixin.cp.bean.message.WxCpMessage;
  13. import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
  14. import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
  15. import org.apache.commons.lang3.time.DurationFormatUtils;
  16. import org.jsoup.Connection;
  17. import org.redisson.api.RLock;
  18. import org.redisson.api.RedissonClient;
  19. import org.springframework.beans.BeanUtils;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.scheduling.annotation.Async;
  22. import org.springframework.stereotype.Service;
  23. import org.springframework.transaction.annotation.Propagation;
  24. import org.springframework.transaction.annotation.Transactional;
  25. import org.springframework.util.StopWatch;
  26. import top.lvzhiqiang.config.InitRunner;
  27. import top.lvzhiqiang.config.WorkWeixinProperties;
  28. import top.lvzhiqiang.dto.CoinYoutubeYt2140LiveChapterDTO;
  29. import top.lvzhiqiang.dto.R;
  30. import top.lvzhiqiang.entity.*;
  31. import top.lvzhiqiang.enumeration.ResultCodeEnum;
  32. import top.lvzhiqiang.exception.BusinessException;
  33. import top.lvzhiqiang.exception.ParameterException;
  34. import top.lvzhiqiang.mapper.*;
  35. import top.lvzhiqiang.service.CoinService;
  36. import top.lvzhiqiang.util.*;
  37. import javax.annotation.Resource;
  38. import java.io.UnsupportedEncodingException;
  39. import java.math.BigDecimal;
  40. import java.math.MathContext;
  41. import java.math.RoundingMode;
  42. import java.net.Proxy;
  43. import java.security.InvalidKeyException;
  44. import java.text.DecimalFormat;
  45. import java.time.Duration;
  46. import java.time.LocalDate;
  47. import java.time.LocalDateTime;
  48. import java.util.*;
  49. import java.util.concurrent.*;
  50. import java.util.concurrent.atomic.AtomicInteger;
  51. import java.util.function.Function;
  52. import java.util.stream.Collectors;
  53. import java.util.stream.Stream;
  54. /**
  55. * Coin ServiceImpl
  56. *
  57. * @author lvzhiqiang
  58. * 2023/9/5 15:23
  59. */
  60. @Service
  61. @Slf4j
  62. public class CoinServiceImpl implements CoinService {
  63. /**
  64. * 任务告警方式-应用文本卡片
  65. */
  66. public static final String JOB_ALARM_MODE_APP_TEXT_CARD = "1";
  67. /**
  68. * 任务告警方式-群聊机器人
  69. */
  70. public static final String JOB_ALARM_MODE_CHAT_BOT = "2";
  71. /**
  72. * 任务告警方式(1:应用文本卡片,2:群聊机器人文本消息)
  73. */
  74. public static String JOB_ALARM_MODE = "1";
  75. // 所有REST请求的header都必须包含以下key:
  76. private static final Map<String, String> basicHeaderMap = new HashMap<>();
  77. private static final Map<String, String> basicHeaderMap4OKX = new HashMap<>();
  78. // 主域名 URL
  79. private static final String mainUrl = "https://api.bitget.com";
  80. // 私钥,由系统随机生成,用于签名的生成。
  81. private static final String secretKey = "1fdd0fc2976bea80189ba13710e12825ca3ef6c5e25a0d76fd03f8f6cd4a61d9";
  82. private static final String secretKey4OKX = "32AC470662FBB633374B9A41950995A9";
  83. @Resource
  84. private CoinMapper coinMapper;
  85. @Resource
  86. private WxCpService wxCpService;
  87. @Autowired(required = false)
  88. private WorkWeixinProperties properties;
  89. private final Map<String, String> orderMap = new ConcurrentHashMap<>();
  90. private final Map<String, JSONObject> mixMap = new ConcurrentHashMap<>();
  91. private final static ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(10);
  92. private final ForkJoinPool forkJoinPool = new ForkJoinPool(16);
  93. private final ForkJoinPool forkJoinPool2 = new ForkJoinPool(16);
  94. private final ForkJoinPool forkJoinPool3 = new ForkJoinPool(16);
  95. private final ForkJoinPool forkJoinPool4 = new ForkJoinPool(16);
  96. private final ForkJoinPool forkJoinPool5 = new ForkJoinPool(16);
  97. private static final DecimalFormat df1 = new DecimalFormat("#,##0.00");
  98. private static final DecimalFormat df2 = new DecimalFormat("#,##0");
  99. private static final WxCpServiceImpl wxCpService4News;
  100. @Resource
  101. private CoinApiConfigMapper coinApiConfigMapper;
  102. @Resource
  103. private PictureInfoMapper pictureInfoMapper;
  104. @Resource
  105. private MusicInfoMapper musicInfoMapper;
  106. @Resource
  107. private CoinYoutubeMapper coinYoutubeMapper;
  108. @Resource
  109. private GoldenQuotesMapper goldenQuotesMapper;
  110. @Resource
  111. private BookmarkMapper bookmarkMapper;
  112. @Resource
  113. private CoinGateMapper coinGateMapper;
  114. @Resource
  115. private RedissonClient redissonClient;
  116. @Resource
  117. private RedisUtils redisUtils;
  118. static {
  119. // API KEY作为一个字符串。
  120. basicHeaderMap.put("ACCESS-KEY", "bg_433d37306df0e8901c6d107c6d9e9111");
  121. // 使用base64编码签名(请参阅签名消息)。
  122. basicHeaderMap.put("ACCESS-SIGN", "");
  123. // 您请求的时间戳。
  124. basicHeaderMap.put("ACCESS-TIMESTAMP", "");
  125. // 您在创建API KEY时设置的口令。
  126. basicHeaderMap.put("ACCESS-PASSPHRASE", "7f934f62f2701bee932204580d115228");
  127. // 统一设置为application/json。
  128. basicHeaderMap.put("Content-Type", "application/json");
  129. // 支持多语言, 如:中文(zh-CN),英语(en-US)
  130. basicHeaderMap.put("locale", "zh-CN");
  131. // 字符串类型的APIKey
  132. basicHeaderMap4OKX.put("OK-ACCESS-KEY", "25e4f515-5efd-4bb9-a934-3949b21d9f10");
  133. // 使用HMAC SHA256哈希函数获得哈希值,再使用Base-64编码(请参阅签名)
  134. basicHeaderMap4OKX.put("OK-ACCESS-SIGN", "");
  135. // 发起请求的时间(UTC),如:2020-12-08T09:08:57.715Z
  136. basicHeaderMap4OKX.put("OK-ACCESS-TIMESTAMP", "");
  137. // 您在创建API密钥时指定的Passphrase
  138. basicHeaderMap4OKX.put("OK-ACCESS-PASSPHRASE", "tmvxeGY#Q#Y2qm8");
  139. // 统一设置为application/json
  140. basicHeaderMap4OKX.put("Content-Type", "application/json");
  141. df1.setRoundingMode(RoundingMode.HALF_UP);
  142. WxCpDefaultConfigImpl wxCpDefaultConfig = new WxCpDefaultConfigImpl();
  143. wxCpDefaultConfig.setCorpId("ww95a4adba56acb55f");
  144. wxCpDefaultConfig.setAgentId(1000004);
  145. wxCpDefaultConfig.setCorpSecret("hG50gbVZ8pXm3tSzY3BpwTzbrMmm6sTf8_bPfJG_6Yc");
  146. wxCpService4News = new WxCpServiceImpl();
  147. wxCpService4News.setWxCpConfigStorage(wxCpDefaultConfig);
  148. }
  149. @Override
  150. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  151. public void syncData(String startTime, String endTime, String pageSize) {
  152. // 获取全部历史委托
  153. Map<String, String> paramMap = new LinkedHashMap<>();
  154. paramMap.put("productType", "umcbl");
  155. paramMap.put("startTime", startTime);
  156. paramMap.put("endTime", endTime);
  157. paramMap.put("pageSize", pageSize);
  158. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  159. JSONObject response = null;
  160. try {
  161. response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  162. JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList");
  163. if (orderList != null && orderList.size() > 0) {
  164. coinMapper.insertHistoryOrderList(JSONArray.parseArray(orderList.toJSONString(), CoinHistoryOrder.class));
  165. log.warn("syncData->insertHistoryOrderList,startTime={},endTime={},size={}", startTime, endTime, orderList.size());
  166. }
  167. } catch (Exception e) {
  168. log.error("syncData->insertHistoryOrderList error,response={}", response, e);
  169. }
  170. }
  171. @Override
  172. public void syncData4TraderList() {
  173. StopWatch stopWatch = new StopWatch();
  174. stopWatch.start();
  175. // 获取交易员列表
  176. Map<String, String> paramMap = new LinkedHashMap<>();
  177. paramMap.put("sortRule", "composite");
  178. paramMap.put("sortFlag", "desc");
  179. paramMap.put("languageType", "en-US");
  180. paramMap.put("pageSize", "20");
  181. int i = 0;
  182. String url = "/api/mix/v1/trace/traderList";
  183. JSONObject response;
  184. int totalNum = 0;
  185. for (; ; ) {
  186. paramMap.put("pageNo", String.valueOf(++i));
  187. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  188. response = requestApi4Common(url, signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  189. JSONArray dataList = response.getJSONArray("data");
  190. if (dataList.size() == 0) {
  191. break;
  192. }
  193. try {
  194. Thread.sleep(5000L);
  195. syncData4TraderListSub(dataList);
  196. } catch (Exception e) {
  197. log.error("syncData4TraderListSub error,paramMap={},response={}", paramMap, response.toJSONString(), e);
  198. }
  199. totalNum += dataList.size();
  200. }
  201. log.warn("syncData4TraderList 结束:time={},totalNum={}", stopWatch.getTotalTimeSeconds(), totalNum);
  202. }
  203. @Override
  204. public void syncCoinmarketcapCMap() {
  205. StopWatch stopWatch = new StopWatch();
  206. stopWatch.start();
  207. String coinmarketcapApikey = InitRunner.dicCodeMap.get("coinmarketcap_apikey").getCodeValue();
  208. String coinmarketcapIdmapUrl = InitRunner.dicCodeMap.get("coinmarketcap_idmap_url").getCodeValue();
  209. String coinmarketcapIdmapParams4listingStatus = InitRunner.dicCodeMap.get("coinmarketcap_idmap_params_listing_status").getCodeValue();
  210. String coinmarketcapIdmapParams4aux = InitRunner.dicCodeMap.get("coinmarketcap_idmap_params_aux").getCodeValue();
  211. Map<String, String> headerMap = new HashMap<>();
  212. headerMap.put("Accept", "application/json");
  213. headerMap.put("Accept-Encoding", "deflate,gzip");
  214. headerMap.put("X-CMC_PRO_API_KEY", coinmarketcapApikey);
  215. String[] listingStatusArr = coinmarketcapIdmapParams4listingStatus.split(",");
  216. Map<String, String> paramMap = new LinkedHashMap<>();
  217. int MAX_NUMBER = 1000;
  218. int MAX_NUMBER2 = 5000;
  219. Long totalNum = 0L;
  220. for (String listingStatus : listingStatusArr) {
  221. paramMap.put("listing_status", listingStatus);
  222. paramMap.put("aux", coinmarketcapIdmapParams4aux);
  223. try {
  224. int j = 0;
  225. Long totalNum2 = 0L;
  226. for (; ; ) {
  227. Thread.sleep(3000L);
  228. j++;
  229. paramMap.put("start", String.valueOf((j - 1) * MAX_NUMBER2 + 1));
  230. paramMap.put("limit", String.valueOf(MAX_NUMBER2));
  231. Connection.Response response = JsoupUtil.requestBody(coinmarketcapIdmapUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap);
  232. JSONObject result = JSONObject.parseObject(response.body());
  233. JSONArray dataJA = result.getJSONArray("data");
  234. List<CoinCmcMap> cmcMapList = new ArrayList<>();
  235. CoinCmcMap coinCmcMap;
  236. for (int i = 0; i < dataJA.size(); i++) {
  237. JSONObject dataJO = dataJA.getJSONObject(i);
  238. coinCmcMap = new CoinCmcMap();
  239. coinCmcMap.setCmcId(dataJO.getLong("id"));
  240. coinCmcMap.setCmcRank(dataJO.getLong("rank"));
  241. coinCmcMap.setName(dataJO.getString("name"));
  242. coinCmcMap.setSymbol(dataJO.getString("symbol"));
  243. coinCmcMap.setSlug(dataJO.getString("slug"));
  244. coinCmcMap.setIsActive(dataJO.getInteger("is_active"));
  245. coinCmcMap.setStatus(dataJO.getString("status"));
  246. coinCmcMap.setFirstHistoricalData(DateUtils.stringutcToLocalDateTime(dataJO.getString("first_historical_data")));
  247. coinCmcMap.setLastHistoricalData(DateUtils.stringutcToLocalDateTime(dataJO.getString("last_historical_data")));
  248. coinCmcMap.setPlatform(dataJO.getString("platform"));
  249. cmcMapList.add(coinCmcMap);
  250. }
  251. // 新增或者更新
  252. Stream.iterate(0, n -> n + 1).limit((cmcMapList.size() + MAX_NUMBER - 1) / MAX_NUMBER)
  253. .forEach(i -> {
  254. List<CoinCmcMap> list = cmcMapList.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toList());
  255. coinMapper.insertCmcMapList(list);
  256. });
  257. totalNum += cmcMapList.size();
  258. totalNum2 += cmcMapList.size();
  259. if (dataJA.size() < MAX_NUMBER2) {
  260. break;
  261. }
  262. }
  263. log.warn("syncCoinmarketcapCMap {} success,totalNum={}", listingStatus, totalNum2);
  264. } catch (Exception e) {
  265. log.error("syncCoinmarketcapCMap {} error", listingStatus, e);
  266. }
  267. }
  268. log.warn("syncCoinmarketcapCMap 结束:time={},totalNum={}", stopWatch.getTotalTimeSeconds(), totalNum);
  269. }
  270. @Override
  271. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  272. public void syncData4TraderListSub(JSONArray dataList) {
  273. coinMapper.insertMixTradeList(parseMixTradeList(dataList));
  274. }
  275. @Override
  276. public String watchlistDetail(Integer userId, String symbol, String operationType) {
  277. CoinWatchlist coinWatchlist = coinMapper.findWatchlistUserBySymbolAndUserId(symbol, userId);
  278. if (coinWatchlist == null) {
  279. throw new BusinessException(1, "symbol不存在!");
  280. }
  281. if ("detail".equals(operationType)) {
  282. return parseWatchlistOther(symbol) + MarkdownToHtmlUtils.markdownToHtmlExtensions(coinWatchlist.getRemark());
  283. } else if ("update".equals(operationType)) {
  284. return coinWatchlist.getRemark();
  285. } else {
  286. return "暂不支持该操作!";
  287. }
  288. }
  289. @Override
  290. public Object watchlistUpdate(String symbol, Integer userId, String remark, String score) {
  291. CoinWatchlist coinWatchlist = new CoinWatchlist();
  292. coinWatchlist.setSymbol(symbol);
  293. coinWatchlist.setUserId(userId);
  294. coinWatchlist.setRemark(remark);
  295. coinWatchlist.setScore(score);
  296. int num = coinMapper.updateCoinWatchlistRemarkAndScore(coinWatchlist);
  297. return num;
  298. }
  299. private List<CoinTrader> parseMixTradeList(JSONArray dataList) {
  300. List<CoinTrader> mixTraderList = JSONArray.parseArray(dataList.toJSONString(), CoinTrader.class);
  301. mixTraderList.stream().forEach(e -> {
  302. Map<String, String> columnMap = e.getColumnList().stream().filter(Objects::nonNull)
  303. .collect(Collectors.toMap(
  304. object -> {
  305. JSONObject item = (JSONObject) object;
  306. return item.getString("describe");
  307. },
  308. object -> {
  309. JSONObject item = (JSONObject) object;
  310. return item.getString("value");
  311. }
  312. ));
  313. e.setRoi(columnMap.get("ROI"));
  314. e.setTotalProfit(columnMap.containsKey("Total PnL") ? columnMap.get("Total PnL").replace("$", "").replace(",", "") : null);
  315. e.setTotalFollowersProfit(columnMap.containsKey("Total followers PnL") ? columnMap.get("Total followers PnL").replace("$", "").replace(",", "") : null);
  316. e.setAum(columnMap.containsKey("AUM") ? columnMap.get("AUM").replace("$", "").replace(",", "") : null);
  317. e.setMaxCallbackRate(columnMap.get("Max drawdown"));
  318. e.setLast3wWinRate(columnMap.get("Last 3W win rate"));
  319. e.setAverageWinRate(StringUtils.isNotEmpty(e.getAverageWinRate()) ? new BigDecimal(e.getAverageWinRate()).setScale(2, RoundingMode.HALF_UP).toPlainString() : "0.00");
  320. e.setTraderNickName(StringUtils.isNotEmpty(e.getTraderNickName()) ? e.getTraderNickName() : "--");
  321. });
  322. return mixTraderList;
  323. }
  324. @Override
  325. public String orderDetail(String trackingNo) {
  326. Map<String, String> paramMap = new LinkedHashMap<>();
  327. paramMap.put("traderId", "b1b5467f8bb73f53ac97");
  328. paramMap.put("pageSize", "20");
  329. StringBuffer sb = new StringBuffer();
  330. // 交易员当前带单列表筛选
  331. for (int j = 1; j < 5; j++) {
  332. try {
  333. paramMap.put("pageNo", j + "");
  334. JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap);
  335. JSONArray orderList = response.getJSONArray("data");
  336. for (int i = 0; i < orderList.size(); i++) {
  337. JSONObject order = orderList.getJSONObject(i);
  338. String trackingNo1 = order.getString("trackingNo");
  339. if (trackingNo.equals(trackingNo1)) {
  340. sb.append("<table border=\"1\" cellspacing=\"0\"><tr><th>键</th><th>值</th></tr>");
  341. sb.append("<tr><td>交易对</td><td>").append(order.getString("symbol")).append("</td></tr>");
  342. sb.append("<tr><td>持仓方向</td><td>").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("</td></tr>");
  343. sb.append("<tr><td>杠杆倍数</td><td>").append(order.getString("leverage")).append("</td></tr>");
  344. sb.append("<tr><td>开仓均价</td><td>").append(order.getString("openPrice")).append("</td></tr>");
  345. sb.append("<tr><td>开仓时间</td><td>").append(DateUtils.longToString(order.getLong("openTime"))).append("</td></tr>");
  346. sb.append("<tr><td>此笔订单跟单人数</td><td>").append(order.getString("followerNum")).append("</td></tr>");
  347. sb.append("<tr><td>保证金</td><td>").append(order.getString("marginAmount")).append("</td></tr>");
  348. sb.append("<tr><td>止盈价</td><td>").append(order.getString("takeProfitPrice")).append("</td></tr>");
  349. sb.append("<tr><td>止损价</td><td>").append(order.getString("stopLossPrice")).append("</td></tr>");
  350. sb.append("<tr><td>交易员</td><td>").append("hale").append("</td></tr>");
  351. sb.append("</table>");
  352. break;
  353. }
  354. }
  355. } catch (Exception e) {
  356. }
  357. }
  358. // 交易员历史带单列表筛选
  359. if (sb.length() == 0) {
  360. for (int j = 1; j < 5; j++) {
  361. try {
  362. paramMap.put("pageNo", j + "");
  363. JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/historyList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap);
  364. JSONArray orderList = response.getJSONArray("data");
  365. for (int i = 0; i < orderList.size(); i++) {
  366. JSONObject order = orderList.getJSONObject(i);
  367. String trackingNo1 = order.getString("trackingNo");
  368. if (trackingNo.equals(trackingNo1)) {
  369. sb.append("<table border=\"1\" cellspacing=\"0\" style=\"font-size: 20px;\"><tr><th>键</th><th>值</th></tr>");
  370. sb.append("<tr><td>交易对</td><td>").append(order.getString("symbol")).append("</td></tr>");
  371. sb.append("<tr><td>持仓方向</td><td>").append(InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide"))).append("</td></tr>");
  372. sb.append("<tr><td>杠杆倍数</td><td>").append(order.getString("leverage")).append("</td></tr>");
  373. sb.append("<tr><td>开仓均价</td><td>").append(order.getString("openPrice")).append("</td></tr>");
  374. sb.append("<tr><td>开仓时间</td><td>").append(DateUtils.longToString(order.getLong("openTime"))).append("</td></tr>");
  375. sb.append("<tr><td>此笔订单跟单人数</td><td>").append(order.getString("followerNum")).append("</td></tr>");
  376. sb.append("<tr><td>保证金</td><td>").append(order.getString("marginAmount")).append("</td></tr>");
  377. sb.append("<tr><td>平仓均价</td><td>").append(order.getString("closePrice")).append("</td></tr>");
  378. sb.append("<tr><td>平仓时间</td><td>").append(DateUtils.longToString(order.getLong("closeTime"))).append("</td></tr>");
  379. sb.append("<tr><td>平仓数量</td><td>").append(order.getString("closeAmount")).append("</td></tr>");
  380. sb.append("<tr><td>交易员</td><td>").append("hale").append("</td></tr>");
  381. sb.append("</table>");
  382. break;
  383. }
  384. }
  385. } catch (Exception e) {
  386. }
  387. }
  388. }
  389. return sb.toString();
  390. }
  391. @Override
  392. public String orderDetail2(String orderId, String symbol) {
  393. Map<String, String> paramMap = new LinkedHashMap<>();
  394. paramMap.put("symbol", symbol);
  395. paramMap.put("orderId", orderId);
  396. StringBuffer sb = new StringBuffer("<table border=\"1\" cellspacing=\"0\" style=\"font-size: 20px;\"><tr><th>键</th><th>值</th></tr>");
  397. // 获取订单详情
  398. try {
  399. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  400. JSONObject response = requestApi4Common("/api/mix/v1/order/detail", signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  401. JSONObject order = response.getJSONObject("data");
  402. sb.append("<tr><td>交易对</td><td>").append(order.getString("symbol")).append("</td></tr>");
  403. sb.append("<tr><td>交易方向</td><td>").append(InitRunner.publicParamsMap.get("side").getString(order.getString("side"))).append("</td></tr>");
  404. sb.append("<tr><td>杠杆倍数</td><td>").append(order.getString("leverage")).append("</td></tr>");
  405. sb.append("<tr><td>成交均价</td><td>").append(order.getString("priceAvg")).append("</td></tr>");
  406. sb.append("<tr><td>委托价格</td><td>").append(order.getString("price")).append("</td></tr>");
  407. sb.append("<tr><td>手续费</td><td>").append(order.getString("fee")).append("</td></tr>");
  408. sb.append("<tr><td>订单状态</td><td>").append(InitRunner.publicParamsMap.get("state").getString(order.getString("state"))).append("</td></tr>");
  409. sb.append("<tr><td>交易类型</td><td>").append(InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType"))).append("</td></tr>");
  410. sb.append("<tr><td>总盈亏</td><td>").append(order.getString("totalProfits")).append("</td></tr>");
  411. sb.append("<tr><td>预设止盈价格</td><td>").append(order.getString("presetTakeProfitPrice")).append("</td></tr>");
  412. sb.append("<tr><td>预设止损价格</td><td>").append(order.getString("presetStopLossPrice")).append("</td></tr>");
  413. sb.append("<tr><td>创建时间</td><td>").append(DateUtils.longToString(order.getLong("cTime"))).append("</td></tr>");
  414. sb.append("<tr><td>更新时间</td><td>").append(DateUtils.longToString(order.getLong("uTime"))).append("</td></tr>");
  415. } catch (Exception e) {
  416. log.error("orderDetail2 error,orderId={},symbol={}", orderId, symbol, e);
  417. }
  418. sb.append("</table>");
  419. return sb.toString();
  420. }
  421. @Override
  422. public String monitorJob() {
  423. // BITGET开仓平仓监控报警
  424. scheduler.scheduleWithFixedDelay(() -> {
  425. monitorBitgetMixOrder();
  426. }, 0, 10, TimeUnit.SECONDS);
  427. // OKX开仓平仓监控报警
  428. scheduler.scheduleWithFixedDelay(() -> {
  429. if (!"1".equals(getMonitorJobStatus("okx-mix-order"))) {
  430. return;
  431. }
  432. LocalDateTime endTime = LocalDateTime.now();
  433. // 查看历史持仓信息
  434. Map<String, String> paramMap = new LinkedHashMap<>();
  435. paramMap.put("instType", "SWAP");
  436. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  437. try {
  438. JSONObject response = requestApi4Common4OKX("/api/v5/account/positions-history", signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  439. JSONArray orderList = response.getJSONArray("data");
  440. for (int i = 0; i < orderList.size(); i++) {
  441. JSONObject order = orderList.getJSONObject(i);
  442. //LocalDateTime cTime = DateUtils.longToLocalDateTime(order.getLong("cTime"));
  443. String orderId = "okx" + order.getString("posId");
  444. String symbol = order.getString("ccy");
  445. if (!orderMap.containsKey(orderId)) {
  446. orderMap.put(orderId, "1");
  447. log.warn("okx ={}", order);
  448. String content = "<div class=\"highlight\">交易对:" + symbol + "</div>";
  449. JSONObject params = new JSONObject();
  450. params.put("title", "OKX报警");
  451. params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail2/" + orderId + "/" + symbol);
  452. params.put("btnTxt", "订单详情");
  453. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null);
  454. }
  455. }
  456. } catch (Exception e) {
  457. log.error("okx-mix-order error", e);
  458. }
  459. }, 0, 10, TimeUnit.SECONDS);
  460. scheduler.scheduleWithFixedDelay(() -> {
  461. monitorBitgetMixReturnrate();
  462. }, 0, 10, TimeUnit.SECONDS);
  463. // BITGET跟单员监控报警
  464. scheduler.scheduleWithFixedDelay(() -> {
  465. if (!"1".equals(getMonitorJobStatus("bitget-mix-trader"))) {
  466. return;
  467. }
  468. try {
  469. List<String> monitorTraderList = coinMapper.findMonitorTraderList();
  470. forkJoinPool5.submit(() -> monitorTraderList.parallelStream().forEach(e -> {
  471. LocalDateTime endTime = LocalDateTime.now();
  472. // 交易员当前带单列表
  473. Map<String, String> paramMap = new LinkedHashMap<>();
  474. String[] split = e.split("\\|");
  475. paramMap.put("traderId", split[0]);
  476. paramMap.put("pageNo", "1");
  477. paramMap.put("pageSize", "20");
  478. try {
  479. JSONObject response = requestApi4Common("/api/mix/v1/trace/report/order/currentList", null, JSONObject.toJSONString(paramMap), JsoupUtil.HTTP_POST, paramMap);
  480. JSONArray orderList = response.getJSONArray("data");
  481. if (null != orderList) {
  482. for (int i = 0; i < orderList.size(); i++) {
  483. JSONObject order = orderList.getJSONObject(i);
  484. LocalDateTime openTime = DateUtils.longToLocalDateTime(order.getLong("openTime"));
  485. String trackingNo = order.getString("trackingNo");
  486. if (Duration.between(openTime, endTime).getSeconds() < 50 && !orderMap.containsKey(trackingNo)) {
  487. orderMap.put(trackingNo, "1");
  488. String content = "<div class=\"highlight\">交易对:" + order.getString("symbol") + "</div>" +
  489. "<div>持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(order.getString("holdSide")) + "</div>" +
  490. "<div>杠杆倍数:" + order.getString("leverage") + "</div>" +
  491. "<div>开仓均价:" + order.getString("openPrice") + "</div>" +
  492. "<div>止盈价:" + order.getString("takeProfitPrice") + "</div>" +
  493. "<div>止损价:" + order.getString("stopLossPrice") + "</div>" +
  494. "<div >交易员:" + split[1] + "</div>" +
  495. "<div class=\"gray\">开仓时间:" + DateUtils.longToString(order.getLong("openTime")) + "</div>";
  496. JSONObject params = new JSONObject();
  497. params.put("title", "BITGET交易员开单报警");
  498. params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail/" + order.getString("trackingNo"));
  499. params.put("btnTxt", "跟单详情");
  500. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null);
  501. }
  502. }
  503. }
  504. } catch (Exception ex) {
  505. log.error("bitget-mix-trader error,param={}", paramMap, ex);
  506. }
  507. })).join();
  508. } catch (Exception e) {
  509. log.error("bitget-mix-trader top error", e);
  510. }
  511. }, 0, 10, TimeUnit.SECONDS);
  512. // 星球日报新闻快讯监控报警
  513. scheduler.scheduleWithFixedDelay(() -> {
  514. if (!"1".equals(getMonitorJobStatus("news-odaily"))) {
  515. return;
  516. }
  517. try {
  518. Connection.Response response = JsoupUtil.requestBody("https://www.odaily.news/v1/openapi/feeds", JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  519. JSONObject result = JSONObject.parseObject(response.body());
  520. JSONArray newsList = result.getJSONObject("data").getJSONArray("arr_news");
  521. LocalDateTime endTime = LocalDateTime.now();
  522. for (int i = 0; i < newsList.size(); i++) {
  523. if (i == 5) {
  524. break;
  525. }
  526. JSONObject news = newsList.getJSONObject(i);
  527. String id = "Odaily" + news.getString("id");
  528. String publishedAt = news.getString("published_at");
  529. LocalDateTime publishedAtTime = DateUtils.stringToLocalDateTime(publishedAt);
  530. if (Duration.between(publishedAtTime, endTime).getSeconds() < 20 && !orderMap.containsKey(id)) {
  531. orderMap.put(id, "1");
  532. String type = news.getString("type");
  533. String title = news.getString("title");
  534. String link = news.getString("link");
  535. String content = "";
  536. JSONObject params = new JSONObject();
  537. params.put("title", "Odaily监控报警");
  538. params.put("btnTxt", "新闻详情");
  539. params.put("logUrl", link);
  540. params.put("user", "@all");
  541. params.put("agentId", 1000004);
  542. if ("newsflashes".equals(type)) {
  543. String newsUrl = news.getString("news_url");
  544. content = "<div class=\"highlight\">标题:" + title + "</div>" +
  545. "<div>类型:" + "新闻快讯" + "</div>" +
  546. "<div>发布时间:" + publishedAt + "</div>" +
  547. "<div class=\"gray\">描述:" + news.getString("description").replace("\n", "&nbsp;&nbsp;&nbsp;&nbsp;") + "</div>";
  548. } else if ("posts".equals(type)) {
  549. content = "<div class=\"highlight\">标题:" + title + "</div>" +
  550. "<div>类型:" + "帖子" + "</div>" +
  551. "<div>发布时间:" + publishedAt + "</div>" +
  552. "<div class=\"gray\">描述:" + news.getString("summary").replace("\n", "&nbsp;&nbsp;&nbsp;&nbsp;") + "</div>";
  553. }
  554. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, wxCpService4News);
  555. }
  556. }
  557. } catch (Exception e) {
  558. log.error("news-odaily top error", e);
  559. }
  560. }, 0, 10, TimeUnit.SECONDS);
  561. // 律动日报新闻快讯监控报警
  562. scheduler.scheduleWithFixedDelay(() -> {
  563. if (!"1".equals(getMonitorJobStatus("news-theblockbeats"))) {
  564. return;
  565. }
  566. try {
  567. Map<String, String> paramMap = new HashMap<>();
  568. paramMap.put("size", "10");
  569. paramMap.put("page", "1");
  570. Connection.Response response = JsoupUtil.requestBody("https://api.theblockbeats.news/v1/open-api/open-flash", JsoupUtil.HTTP_GET, InitRunner.proxy, null, paramMap);
  571. JSONObject result = JSONObject.parseObject(response.body());
  572. JSONArray newsList = result.getJSONObject("data").getJSONArray("data");
  573. LocalDateTime endTime = LocalDateTime.now();
  574. for (int i = 0; i < newsList.size(); i++) {
  575. if (i == 5) {
  576. break;
  577. }
  578. JSONObject news = newsList.getJSONObject(i);
  579. String id = "BlockBeats" + news.getString("id");
  580. String createTime = news.getString("create_time");
  581. LocalDateTime createTimeTime = DateUtils.longToLocalDateTime_(Long.valueOf(createTime));
  582. createTime = DateUtils.localDateTimeToString(createTimeTime);
  583. if (Duration.between(createTimeTime, endTime).getSeconds() < 20 && !orderMap.containsKey(id)) {
  584. orderMap.put(id, "1");
  585. String title = news.getString("title");
  586. String content = news.getString("content");
  587. String pic = news.getString("pic");
  588. String link = news.getString("link");
  589. String url = news.getString("url");
  590. JSONObject params = new JSONObject();
  591. params.put("title", "BlockBeats监控报警");
  592. params.put("btnTxt", "新闻详情");
  593. params.put("logUrl", link);
  594. params.put("user", "@all");
  595. params.put("agentId", 1000004);
  596. if (StringUtils.isEmpty(pic)) {
  597. // 文本卡片
  598. String contentStr = "<div class=\"highlight\">标题:" + title + "</div>" +
  599. "<div>类型:" + "新闻快讯" + "</div>" +
  600. "<div>发布时间:" + createTime + "</div>" +
  601. "<div class=\"gray\">描述:" + content.replace("\n", "&nbsp;&nbsp;&nbsp;&nbsp;") + "</div>";
  602. if (title.contains("Upbit") || title.contains("upbit")) {
  603. JSONObject params4Upbit = new JSONObject();
  604. params4Upbit.put("title", "BlockBeats监控报警");
  605. params4Upbit.put("logUrl", link);
  606. params4Upbit.put("btnTxt", "新闻详情");
  607. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params4Upbit, null);
  608. }
  609. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params, wxCpService4News);
  610. } else {
  611. // 图文
  612. NewArticle article1 = new NewArticle();
  613. article1.setUrl(link);
  614. article1.setPicUrl(pic);
  615. article1.setDescription(content);
  616. article1.setTitle(title);
  617. if (title.contains("Upbit") || title.contains("upbit")) {
  618. JSONObject params4Upbit = new JSONObject();
  619. params4Upbit.put("title", "BlockBeats监控报警");
  620. params4Upbit.put("logUrl", link);
  621. params4Upbit.put("btnTxt", "新闻详情");
  622. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4NEWS(params4Upbit, null, article1);
  623. }
  624. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4NEWS(params, wxCpService4News, article1);
  625. }
  626. }
  627. }
  628. } catch (Exception e) {
  629. log.error("news-theblockbeats top error", e);
  630. }
  631. }, 0, 10, TimeUnit.SECONDS);
  632. // coingecko
  633. scheduler.scheduleWithFixedDelay(() -> {
  634. if (!"1".equals(getMonitorJobStatus("watchlist-coingecko-cmc"))) {
  635. log.warn("watchlist-coingecko-cmc scheduler return");
  636. return;
  637. }
  638. log.warn("watchlist-coingecko-cmc scheduler start");
  639. int MAX_NUMBER = 100;
  640. Map<String, Object> params = new HashMap<>();
  641. params.put("sortField", Collections.singletonList("create_time"));
  642. params.put("sort", "desc");
  643. List<CoinWatchlist> watchlistListCKO = coinMapper.findWatchlistList(params);
  644. Stream.iterate(0, n -> n + 1).limit((watchlistListCKO.size() + MAX_NUMBER - 1) / MAX_NUMBER)
  645. .forEach(i -> {
  646. try {
  647. Thread.sleep(5000L);
  648. } catch (InterruptedException e) {
  649. }
  650. Map<String, CoinWatchlist> coinWatchlistMap4CoingeckoId = watchlistListCKO.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist));
  651. parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId);
  652. });
  653. List<CoinWatchlist> watchlistListCMC = coinMapper.findWatchlistList(params);
  654. Stream.iterate(0, n -> n + 1).limit((watchlistListCMC.size() + MAX_NUMBER - 1) / MAX_NUMBER)
  655. .forEach(i -> {
  656. try {
  657. Thread.sleep(5000L);
  658. } catch (InterruptedException e) {
  659. }
  660. Map<Long, CoinWatchlist> coinWatchlistMap4CmcId = watchlistListCMC.stream().skip((long) i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist));
  661. parseWatchlistMap4CmC(coinWatchlistMap4CmcId);
  662. });
  663. // CoinCurrencyHolding同步
  664. params.put("sortField", Collections.singletonList("cch.buy_time"));
  665. params.put("sort", "desc");
  666. params.put("status", "1");
  667. List<CoinCurrencyHolding> currentHoldingList = coinMapper.findCurrentHoldingList(params);
  668. parseCurrentHoldingMap4Coingecko(currentHoldingList);
  669. log.warn("watchlist-coingecko-cmc scheduler end");
  670. }, 0, 1, TimeUnit.HOURS);
  671. // Upbit交易所监控报警
  672. scheduler.scheduleWithFixedDelay(() -> {
  673. if (!"1".equals(getMonitorJobStatus("upbit-digitalasset-notices"))) {
  674. return;
  675. }
  676. try {
  677. Map<String, String> paramMap = new HashMap<>();
  678. paramMap.put("page", "1");
  679. paramMap.put("per_page", "20");
  680. paramMap.put("thread_name", "general");
  681. Connection.Response response = JsoupUtil.requestBody("https://api-manager.upbit.com/api/v1/notices", JsoupUtil.HTTP_GET, InitRunner.proxy, null, paramMap);
  682. JSONObject result = JSONObject.parseObject(response.body());
  683. JSONArray noticeList = result.getJSONObject("data").getJSONArray("list");
  684. for (int i = 0; i < noticeList.size(); i++) {
  685. if (i == 5) {
  686. break;
  687. }
  688. JSONObject notice = noticeList.getJSONObject(i);
  689. String idOri = notice.getString("id");
  690. String id = "Upbit" + idOri;
  691. String title = notice.getString("title");
  692. if (title.contains("New digital asset on KRW Market") && !orderMap.containsKey(id)) {
  693. orderMap.put(id, "1");
  694. String createTime = notice.getString("created_at");
  695. String updateTime = notice.getString("updated_at");
  696. String viewCount = notice.getString("view_count");
  697. JSONObject params = new JSONObject();
  698. params.put("title", "Upbit监控报警");
  699. params.put("btnTxt", "通知详情");
  700. params.put("logUrl", "https://sg-api-manager.upbit.com/api/v1/notices/" + idOri);
  701. // 文本卡片
  702. String contentStr = "<div class=\"highlight\">标题:" + title + "</div>" +
  703. "<div>发布时间:" + createTime + "</div>" +
  704. "<div>更新时间:" + updateTime + "</div>" +
  705. "<div class=\"gray\">查看次数:" + viewCount + "</div>";
  706. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(contentStr, params, null);
  707. }
  708. }
  709. } catch (Exception e) {
  710. log.error("upbit-digitalasset-notices top error", e);
  711. try {
  712. Thread.sleep(600000L);
  713. } catch (InterruptedException ex) {
  714. }
  715. }
  716. }, 0, 10, TimeUnit.SECONDS);
  717. return null;
  718. }
  719. private void parseCurrentHoldingMap4Coingecko(List<CoinCurrencyHolding> currentHoldingList) {
  720. String coingeckoCoinsMarketsUrl = InitRunner.dicCodeMap.get("coingecko_coins_markets_url").getCodeValue();
  721. Map<String, String> headerMap = new HashMap<>();
  722. headerMap.put("Accept", "application/json");
  723. headerMap.put("Accept-Encoding", "deflate,gzip");
  724. Map<String, String> paramMap = new LinkedHashMap<>();
  725. paramMap.put("ids", currentHoldingList.stream().map(CoinCurrencyHolding::getCoingeckoId).distinct().collect(Collectors.joining(",")));
  726. paramMap.put("vs_currency", "usd");
  727. AtomicInteger i = new AtomicInteger();
  728. Connection.Response response = null;
  729. try {
  730. response = JsoupUtil.requestBody(coingeckoCoinsMarketsUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap);
  731. JSONArray result = JSONArray.parseArray(response.body());
  732. Map<String, List<CoinCurrencyHolding>> currencyHoldingMap = currentHoldingList.stream().collect(Collectors.groupingBy(CoinCurrencyHolding::getCoingeckoId));
  733. for (int j = 0; j < result.size(); j++) {
  734. JSONObject marketData = result.getJSONObject(j);
  735. String id = marketData.getString("id");
  736. if (currencyHoldingMap.containsKey(id)) {
  737. List<CoinCurrencyHolding> currencyHoldingSubList = currencyHoldingMap.get(id);
  738. for (CoinCurrencyHolding currencyHolding : currencyHoldingSubList) {
  739. // 市场价格
  740. if (marketData.containsKey("current_price") && null != marketData.get("current_price")) {
  741. String markPrice = marketData.getBigDecimal("current_price").toPlainString();
  742. currencyHolding.setCurrentPrice(markPrice);
  743. }
  744. // 入场总额
  745. if (StringUtils.isNotEmpty(currencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(currencyHolding.getBuyQuantity())) {
  746. currencyHolding.setBuyAmount(new BigDecimal(currencyHolding.getBuyPrice()).multiply(new BigDecimal(currencyHolding.getBuyQuantity())).setScale(2, RoundingMode.HALF_UP));
  747. }
  748. // 当前总额
  749. if (StringUtils.isNotEmpty(currencyHolding.getCurrentPrice()) && StringUtils.isNotEmpty(currencyHolding.getCurrentQuantity())) {
  750. currencyHolding.setCurrentAmount(new BigDecimal(currencyHolding.getCurrentPrice()).multiply(new BigDecimal(currencyHolding.getCurrentQuantity())).setScale(2, RoundingMode.HALF_UP));
  751. }
  752. // 涨跌幅比例
  753. if (StringUtils.isNotEmpty(currencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(currencyHolding.getCurrentPrice())) {
  754. BigDecimal oriChangePercentage = currencyHolding.getChangePercentage();
  755. BigDecimal changePercentage = new BigDecimal(currencyHolding.getCurrentPrice()).divide(new BigDecimal(currencyHolding.getBuyPrice()), 2, RoundingMode.HALF_UP);
  756. currencyHolding.setChangePercentage(changePercentage);
  757. // 监控报警
  758. if (oriChangePercentage != null && !"USDT".equals(currencyHolding.getSymbol())) {
  759. BigDecimal roundedDown = changePercentage.setScale(0, RoundingMode.FLOOR);
  760. if (changePercentage.compareTo(roundedDown) > 0 && oriChangePercentage.compareTo(roundedDown) < 0) {
  761. String content = "<div class=\"highlight\">币种名称:" + currencyHolding.getSymbol() + "</div>" +
  762. "<div>入场价格:" + currencyHolding.getBuyPrice() + "</div>" +
  763. "<div>当前价格:" + currencyHolding.getCurrentPrice() + "</div>" +
  764. "<div>入场总额:" + currencyHolding.getBuyAmount() + "</div>" +
  765. "<div>当前总额:" + currencyHolding.getCurrentAmount() + "</div>" +
  766. "<div>所属平台:" + currencyHolding.getExchangeCategory() + "</div>" +
  767. "<div>买入时间:" + currencyHolding.getBuyTime() + "</div>" +
  768. "<div class=\"gray\">涨跌幅:" + currencyHolding.getChangePercentage() + "</div>";
  769. JSONObject params = new JSONObject();
  770. params.put("title", "持仓币种盈利报警");
  771. params.put("logUrl", "https://jav.lvzhiqiang.top/coin.html");
  772. params.put("btnTxt", "详情");
  773. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null);
  774. }
  775. }
  776. }
  777. coinMapper.updateCurrentHolding(currencyHolding);
  778. }
  779. }
  780. }
  781. } catch (Exception e) {
  782. log.error("parseCurrentHoldingMap4Coingecko error,response={},size={},i={}", response != null ? response.body() : "--", currentHoldingList.size(), i.get(), e);
  783. }
  784. }
  785. @Override
  786. public Object getCurrentHoldingTotalAmout(JSONObject params) {
  787. BigDecimal totalAmout = coinMapper.getCurrentHoldingTotalAmout(params.toJavaObject(Map.class));
  788. return totalAmout;
  789. }
  790. @Override
  791. public Object login(String username, String password) {
  792. Integer exist = coinMapper.existUserByUsernameAndPassword(username, password);
  793. if (exist != null) {
  794. return R.ok().data("success");
  795. } else {
  796. return R.error().message("用户名或者密码错误!");
  797. }
  798. }
  799. @Override
  800. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  801. public R insertOrUpdateCurrentHolding(JSONObject params) {
  802. String operationType = params.getString("operationType");
  803. if ("buy".equals(operationType)) {
  804. String symbol = params.getString("symbol");
  805. String buyPrice = params.getString("buyPrice");
  806. String buyQuantity = params.getString("buyQuantity");
  807. String currentQuantity = params.getString("currentQuantity");
  808. String exchangeCategoryId = params.getString("exchangeCategory");
  809. String coingeckoId = params.getString("coingeckoId");
  810. String buyTime = params.getString("buyTime");
  811. String remark = params.getString("remark");
  812. if (StringUtils.isEmpty(symbol) || StringUtils.isEmpty(buyPrice) || StringUtils.isEmpty(buyQuantity) || StringUtils.isEmpty(currentQuantity) || StringUtils.isEmpty(exchangeCategoryId)) {
  813. throw new ParameterException("必填参数不能为空!");
  814. }
  815. symbol = symbol.trim();
  816. Integer existCmcMapBySymbol = coinMapper.existCmcMapBySymbol(symbol);
  817. if (existCmcMapBySymbol == null) {
  818. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "symbol不存在!");
  819. }
  820. if (StringUtils.isEmpty(coingeckoId)) {
  821. coingeckoId = coinMapper.findCoingeckoIdBySymbol(symbol);
  822. if (StringUtils.isEmpty(coingeckoId)) {
  823. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "coingeckoId不存在!");
  824. }
  825. }
  826. CoinCurrencyHolding coinCurrencyHolding = new CoinCurrencyHolding();
  827. coinCurrencyHolding.setSymbol(symbol);
  828. coinCurrencyHolding.setBuyPrice(buyPrice);
  829. coinCurrencyHolding.setBuyQuantity(buyQuantity);
  830. coinCurrencyHolding.setCurrentQuantity(currentQuantity);
  831. coinCurrencyHolding.setExchangeCategoryId(Integer.valueOf(exchangeCategoryId));
  832. coinCurrencyHolding.setCoingeckoId(coingeckoId);
  833. coinCurrencyHolding.setBuyTime(StringUtils.isNotEmpty(buyTime) ? LocalDateTime.parse(buyTime, DateUtils.dateTimeFormatter) : LocalDateTime.now());
  834. coinCurrencyHolding.setStatus(1);
  835. coinCurrencyHolding.setRemark(remark);
  836. coinMapper.buyCurrentHolding(coinCurrencyHolding);
  837. List<CoinCurrencyHolding> currentHoldingList = new ArrayList<>();
  838. currentHoldingList.add(coinCurrencyHolding);
  839. parseCurrentHoldingMap4Coingecko(currentHoldingList);
  840. } else if ("sell".equals(operationType)) {
  841. String id = params.getString("id");
  842. String sellPrice = params.getString("sellPrice");
  843. String sellTime = params.getString("sellTime");
  844. String sellAmount = params.getString("sellAmount");
  845. String remark = params.getString("remark");
  846. if (StringUtils.isEmpty(id) || StringUtils.isEmpty(sellPrice)) {
  847. throw new ParameterException("必填参数不能为空!");
  848. }
  849. id = id.trim();
  850. CoinCurrencyHolding coinCurrencyHolding = coinMapper.findCurrencyHoldingById(id);
  851. if (coinCurrencyHolding == null) {
  852. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "id不存在!");
  853. }
  854. coinCurrencyHolding.setSellTime(StringUtils.isNotEmpty(sellTime) ? LocalDateTime.parse(sellTime, DateUtils.dateTimeFormatter) : LocalDateTime.now());
  855. coinCurrencyHolding.setSellPrice(sellPrice);
  856. coinCurrencyHolding.setStatus(2);
  857. if (StringUtils.isEmpty(sellAmount)) {
  858. coinCurrencyHolding.setSellAmount(new BigDecimal(sellPrice).multiply(new BigDecimal(coinCurrencyHolding.getCurrentQuantity())).setScale(2,RoundingMode.HALF_UP));
  859. }else{
  860. coinCurrencyHolding.setSellAmount(new BigDecimal(sellAmount).setScale(2,RoundingMode.HALF_UP));
  861. }
  862. if (StringUtils.isNotEmpty(remark)) {
  863. coinCurrencyHolding.setRemark(remark + (StringUtils.isNotEmpty(coinCurrencyHolding.getRemark()) ? System.lineSeparator() + coinCurrencyHolding.getRemark() : ""));
  864. }
  865. // 涨跌幅比例
  866. if (StringUtils.isNotEmpty(coinCurrencyHolding.getBuyPrice()) && StringUtils.isNotEmpty(sellPrice)) {
  867. BigDecimal changePercentage = new BigDecimal(sellPrice).divide(new BigDecimal(coinCurrencyHolding.getBuyPrice()), 2, RoundingMode.HALF_UP);
  868. coinCurrencyHolding.setChangePercentage(changePercentage);
  869. }
  870. coinMapper.updateCurrentHolding(coinCurrencyHolding);
  871. // 更新余额
  872. CoinCurrencyHolding usdtCurrencyHolding = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", coinCurrencyHolding.getExchangeCategoryId());
  873. if (usdtCurrencyHolding == null) {
  874. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "USDT不存在!");
  875. }
  876. usdtCurrencyHolding.setCurrentAmount(usdtCurrencyHolding.getCurrentAmount().add(coinCurrencyHolding.getSellAmount()));
  877. coinMapper.updateCurrentHolding(usdtCurrencyHolding);
  878. } else if ("transfer".equals(operationType)) {
  879. String remitter = params.getString("remitter");
  880. String payee = params.getString("payee");
  881. String amount = params.getString("amount");
  882. if (StringUtils.isEmpty(remitter) || StringUtils.isEmpty(payee) || StringUtils.isEmpty(amount)) {
  883. throw new ParameterException("必填参数不能为空!");
  884. }
  885. // 更新余额
  886. CoinCurrencyHolding usdtCurrencyHolding4Remitter = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", Integer.valueOf(remitter));
  887. if (usdtCurrencyHolding4Remitter == null) {
  888. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "汇款人USDT不存在!");
  889. }
  890. CoinCurrencyHolding usdtCurrencyHolding4Payee = coinMapper.findCurrentHoldingBySymbolAndExchangeCategoryId("USDT", Integer.valueOf(payee));
  891. if (usdtCurrencyHolding4Payee == null) {
  892. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "收款人USDT不存在!");
  893. }
  894. usdtCurrencyHolding4Remitter.setCurrentAmount(usdtCurrencyHolding4Remitter.getCurrentAmount().subtract(new BigDecimal(amount)));
  895. usdtCurrencyHolding4Payee.setCurrentAmount(usdtCurrencyHolding4Payee.getCurrentAmount().add(new BigDecimal(amount)));
  896. coinMapper.updateCurrentHolding(usdtCurrencyHolding4Remitter);
  897. coinMapper.updateCurrentHolding(usdtCurrencyHolding4Payee);
  898. } else if ("modifyCurrentAmount".equals(operationType)) {
  899. String id = params.getString("id");
  900. String currentAmount = params.getString("currentAmount");
  901. if (StringUtils.isEmpty(id) || StringUtils.isEmpty(currentAmount)) {
  902. throw new ParameterException("必填参数不能为空!");
  903. }
  904. id = id.trim();
  905. CoinCurrencyHolding coinCurrencyHolding = coinMapper.findCurrencyHoldingById(id);
  906. if (coinCurrencyHolding == null) {
  907. throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "id不存在!");
  908. }
  909. coinCurrencyHolding.setCurrentAmount(new BigDecimal(currentAmount).setScale(2, RoundingMode.HALF_UP));
  910. coinMapper.updateCurrentHolding(coinCurrencyHolding);
  911. } else {
  912. throw new ParameterException("暂不支持的操作!");
  913. }
  914. return R.ok().data("success");
  915. }
  916. @Override
  917. public void monitorBitgetMixOrder() {
  918. if (!"1".equals(getMonitorJobStatus("bitget-mix-order"))) {
  919. return;
  920. }
  921. LocalDateTime endTime = LocalDateTime.now();
  922. // 全部历史委托列表
  923. Map<String, String> paramMap = new LinkedHashMap<>();
  924. paramMap.put("productType", "umcbl");
  925. paramMap.put("startTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime.minusMinutes(1))));
  926. //paramMap.put("startTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime.minusDays(3))));
  927. paramMap.put("endTime", String.valueOf(DateUtils.localDateTimeToMilliseconds(endTime)));
  928. paramMap.put("pageSize", "100");
  929. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  930. try {
  931. JSONObject response = requestApi4Common("/api/mix/v1/order/historyProductType", signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  932. JSONArray orderList = response.getJSONObject("data").getJSONArray("orderList");
  933. if (null == orderList) {
  934. return;
  935. }
  936. log.warn("bitget-mix-order exec,orderList={}", orderList.size());
  937. for (int i = 0; i < orderList.size(); i++) {
  938. JSONObject order = orderList.getJSONObject(i);
  939. LocalDateTime cTime = DateUtils.longToLocalDateTime(order.getLong("cTime"));
  940. String orderId = order.getString("orderId");
  941. String symbol = order.getString("symbol");
  942. if (Duration.between(cTime, endTime).getSeconds() < 200 && !orderMap.containsKey(orderId)) {
  943. orderMap.put(orderId, "1");
  944. log.warn("bitget-mix-order exec,orderId={}", orderId);
  945. String content = "<div class=\"highlight\">交易对:" + order.getString("symbol") + "</div>" +
  946. "<div>交易方向:" + InitRunner.publicParamsMap.get("tradeSide").getString(order.getString("tradeSide")) + "</div>" +
  947. "<div>杠杆倍数:" + order.getString("leverage") + "</div>" +
  948. "<div>成交均价:" + order.getString("priceAvg") + "</div>" +
  949. "<div>委托价格:" + order.getString("price") + "</div>" +
  950. "<div>订单状态:" + InitRunner.publicParamsMap.get("state").getString(order.getString("state")) + "</div>" +
  951. "<div>订单类型:" + InitRunner.publicParamsMap.get("orderType").getString(order.getString("orderType")) + "</div>" +
  952. "<div class=\"gray\">订单时间:" + DateUtils.longToString(order.getLong("cTime")) + "</div>";
  953. JSONObject params = new JSONObject();
  954. params.put("title", (order.getString("side").contains("open") ? "BITGET合约开单" : "BITGET合约平单") + "报警");
  955. params.put("logUrl", "https://jav.lvzhiqiang.top/coin/orderDetail2/" + orderId + "/" + symbol);
  956. params.put("btnTxt", "订单详情");
  957. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4APP_TEXT_CARD(content, params, null);
  958. }
  959. }
  960. } catch (Exception e) {
  961. log.error("bitget-mix-order error", e);
  962. }
  963. }
  964. @Override
  965. public void monitorBitgetMixReturnrate() {
  966. if (!"1".equals(getMonitorJobStatus("bitget-mix-returnrate"))) {
  967. return;
  968. }
  969. // BITGET全部合约仓位信息V2
  970. Map<String, String> paramMap = new HashMap<>();
  971. paramMap.put("productType", "umcbl");
  972. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  973. try {
  974. JSONObject response = requestApi4Common("/api/mix/v1/position/allPosition-v2", signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  975. JSONArray mixList = response.getJSONArray("data");
  976. for (int i = 0; i < mixList.size(); i++) {
  977. JSONObject mixData = mixList.getJSONObject(i);
  978. String symbol = mixData.getString("symbol");
  979. String margin = mixData.getString("margin");
  980. String averageOpenPrice = mixData.getString("averageOpenPrice");
  981. String key = symbol + margin + averageOpenPrice;
  982. // 回报率=未实现盈亏/保证金
  983. // 持仓方向 long:多头 short:空头
  984. String holdSide = mixData.getString("holdSide");
  985. BigDecimal returnRate = new BigDecimal(mixData.getString("unrealizedPL")).divide(new BigDecimal(margin), 4, RoundingMode.HALF_UP);
  986. for (int j = 1; j <= 10; j++) {
  987. BigDecimal grid = BigDecimal.valueOf(0.5).multiply(BigDecimal.valueOf(j));
  988. BigDecimal minusGrid = BigDecimal.valueOf(-0.5).multiply(BigDecimal.valueOf(j));
  989. if (returnRate.compareTo(grid) < 0) {
  990. if (mixMap.containsKey(key)) {
  991. mixMap.get(key).put("returnRate", returnRate);
  992. } else {
  993. JSONObject jsonObject = new JSONObject();
  994. jsonObject.put("returnRate", returnRate);
  995. mixMap.put(key, jsonObject);
  996. }
  997. break;
  998. }
  999. if (returnRate.compareTo(grid) > 0) {
  1000. if (mixMap.containsKey(key)) {
  1001. mixMap.get(key).put("returnRate", returnRate);
  1002. if (mixMap.get(key).containsKey(grid.toPlainString())) {
  1003. continue;
  1004. } else {
  1005. mixMap.get(key).put(grid.toPlainString(), true);
  1006. String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol;
  1007. String last = "--";
  1008. try {
  1009. Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  1010. last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last");
  1011. } catch (Exception e) {
  1012. }
  1013. String content = "币对名称:" + symbol + "\n" +
  1014. "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" +
  1015. "杠杆倍数:" + mixData.getString("leverage") + "\n" +
  1016. "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" +
  1017. "当前价格:" + last + "\n" +
  1018. "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString();
  1019. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null);
  1020. }
  1021. } else {
  1022. JSONObject jsonObject = new JSONObject();
  1023. jsonObject.put("returnRate", returnRate);
  1024. jsonObject.put(grid.toPlainString(), true);
  1025. mixMap.put(key, jsonObject);
  1026. String requestUrl = mainUrl + "/api/mix/v1/market/ticker?symbol=" + symbol;
  1027. String last = "--";
  1028. try {
  1029. Connection.Response responseTicker = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  1030. last = JSONObject.parseObject(responseTicker.body()).getJSONObject("data").getString("last");
  1031. } catch (Exception e) {
  1032. }
  1033. String content = "币对名称:" + symbol + "\n" +
  1034. "持仓方向:" + InitRunner.publicParamsMap.get("holdSide").getString(mixData.getString("holdSide")) + "\n" +
  1035. "杠杆倍数:" + mixData.getString("leverage") + "\n" +
  1036. "开仓均价:" + mixData.getString("averageOpenPrice") + "\n" +
  1037. "当前价格:" + last + "\n" +
  1038. "回报率:" + returnRate.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString() + ",超过" + grid.multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toPlainString();
  1039. SpringUtils.getBean(CoinServiceImpl.class).monitorAlarm4CHAT_BOT(content, null);
  1040. //break;
  1041. }
  1042. }
  1043. }
  1044. }
  1045. } catch (Exception e) {
  1046. log.error("bitget-mix-returnrate error", e);
  1047. }
  1048. }
  1049. private void initCexSpotFlag(Map<String, CoinWatchlistOther> coinWatchlistOtherMap4Symbol) {
  1050. // spot
  1051. String coingeckoExchangeTickersUrl = InitRunner.dicCodeMap.get("coingecko_exchange_tickers_url").getCodeValue();
  1052. String[] coingeckoExchangeIdArr = InitRunner.dicCodeMap.get("coingecko_exchange_ids").getCodeValue().split(",");
  1053. Map<String, String> headerMap = new HashMap<>();
  1054. headerMap.put("Accept", "application/json");
  1055. headerMap.put("Accept-Encoding", "deflate,gzip");
  1056. Map<String, String> paramMap = new LinkedHashMap<>();
  1057. paramMap.put("coin_ids", StringUtils.join(coinWatchlistOtherMap4Symbol.keySet(), ","));
  1058. int iii = 0;
  1059. JSONArray result = null;
  1060. for (String coingeckoExchangeId : coingeckoExchangeIdArr) {
  1061. iii++;
  1062. try {
  1063. Thread.sleep(10000L);
  1064. Connection.Response response = JsoupUtil.requestBody(String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap);
  1065. result = JSONObject.parseObject(response.body()).getJSONArray("tickers");
  1066. if (result == null) {
  1067. Thread.sleep(60000L);
  1068. response = JsoupUtil.requestBody(String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap);
  1069. result = JSONObject.parseObject(response.body()).getJSONArray("tickers");
  1070. }
  1071. Map<String, JSONObject> resultMap = result.stream().collect(Collectors.toMap(i -> {
  1072. JSONObject a = (JSONObject) i;
  1073. String k = a.getString("base").concat(a.getString("target"));
  1074. return k;
  1075. }, i -> (JSONObject) i, (existing, replacement) -> existing));
  1076. for (CoinWatchlistOther coinWatchlistOther : coinWatchlistOtherMap4Symbol.values()) {
  1077. String baseTarget = coinWatchlistOther.getSymbol().concat("USDT");
  1078. if (iii == 1) {
  1079. coinWatchlistOther.setCexSpot("");
  1080. }
  1081. if (resultMap.containsKey(baseTarget)) {
  1082. coinWatchlistOther.setCexSpot(coinWatchlistOther.getCexSpot() + "1");
  1083. } else {
  1084. coinWatchlistOther.setCexSpot(coinWatchlistOther.getCexSpot() + "0");
  1085. }
  1086. }
  1087. } catch (Exception e) {
  1088. log.error("initCexSpotFlag error,url={},paramMap={},result={}", String.format(coingeckoExchangeTickersUrl, coingeckoExchangeId), paramMap, result, e);
  1089. throw new RuntimeException("initCexSpotFlag error");
  1090. }
  1091. }
  1092. }
  1093. /**
  1094. * 解析交易所现货+合约标志
  1095. */
  1096. @Override
  1097. @Async
  1098. public void syncCexFlag(String symbol) {
  1099. List<CoinWatchlistOther> watchlistOtherList;
  1100. if (StringUtils.isNotEmpty(symbol)) {
  1101. CoinWatchlistOther watchlistOther = coinMapper.findWatchlistOtherBySymbol2(symbol);
  1102. if (watchlistOther == null) {
  1103. return;
  1104. }
  1105. watchlistOtherList = new ArrayList<>();
  1106. watchlistOtherList.add(watchlistOther);
  1107. } else {
  1108. watchlistOtherList = coinMapper.findAllCoinWatchlistOther();
  1109. }
  1110. // Perpetual
  1111. String coingeckoExchangeFuturesTickersUrl = InitRunner.dicCodeMap.get("coingecko_exchange_futures_tickers_url").getCodeValue();
  1112. String[] coingeckoExchangeFutruesIdArr = InitRunner.dicCodeMap.get("coingecko_exchange_futures_ids").getCodeValue().split(",");
  1113. Map<String, String> headerMap = new HashMap<>();
  1114. headerMap.put("Accept", "application/json");
  1115. headerMap.put("Accept-Encoding", "deflate,gzip");
  1116. Map<String, String> paramMap = new LinkedHashMap<>();
  1117. paramMap.put("include_tickers", "unexpired");
  1118. int jjj = 0;
  1119. JSONArray result = null;
  1120. for (String coingeckoExchangeFutruesId : coingeckoExchangeFutruesIdArr) {
  1121. jjj++;
  1122. try {
  1123. Thread.sleep(10000L);
  1124. Connection.Response response = JsoupUtil.requestBody(String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap);
  1125. result = JSONObject.parseObject(response.body()).getJSONArray("tickers");
  1126. if (result == null) {
  1127. Thread.sleep(10000L);
  1128. response = JsoupUtil.requestBody(String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), JsoupUtil.HTTP_GET, Proxy.NO_PROXY, headerMap, paramMap);
  1129. result = JSONObject.parseObject(response.body()).getJSONArray("tickers");
  1130. }
  1131. Map<String, JSONObject> resultMap = result.stream().collect(Collectors.toMap(i -> {
  1132. JSONObject a = (JSONObject) i;
  1133. String k = a.getString("base").concat(a.getString("target"));
  1134. return k;
  1135. }, i -> (JSONObject) i, (existing, replacement) -> existing));
  1136. for (CoinWatchlistOther coinWatchlistOther : watchlistOtherList) {
  1137. String baseTarget = coinWatchlistOther.getSymbol().concat("USDT");
  1138. if (jjj == 1) {
  1139. coinWatchlistOther.setCexPerpetual("");
  1140. }
  1141. if (resultMap.containsKey(baseTarget) || resultMap.containsKey("1000".concat(baseTarget))) {
  1142. coinWatchlistOther.setCexPerpetual(coinWatchlistOther.getCexPerpetual() + "1");
  1143. } else {
  1144. coinWatchlistOther.setCexPerpetual(coinWatchlistOther.getCexPerpetual() + "0");
  1145. }
  1146. }
  1147. } catch (Exception e) {
  1148. log.error("initCexFlag error,url={},paramMap={},result={}", String.format(coingeckoExchangeFuturesTickersUrl, coingeckoExchangeFutruesId), paramMap, result, e);
  1149. throw new RuntimeException("initCexFlag error");
  1150. }
  1151. }
  1152. int MAX_NUMBER2 = 10;
  1153. Stream.iterate(0, n -> n + 1).limit((watchlistOtherList.size() + MAX_NUMBER2 - 1) / MAX_NUMBER2)
  1154. .forEach(i -> {
  1155. try {
  1156. Thread.sleep(5000L);
  1157. } catch (InterruptedException e) {
  1158. throw new RuntimeException(e);
  1159. }
  1160. Map<String, CoinWatchlistOther> coinWatchlistOtherMap4Symbol = watchlistOtherList.stream().skip((long) i * MAX_NUMBER2).limit(MAX_NUMBER2).collect(Collectors.toMap(CoinWatchlistOther::getCoingeckoId, coinWatchlistOther -> coinWatchlistOther));
  1161. initCexSpotFlag(coinWatchlistOtherMap4Symbol);
  1162. });
  1163. boolean fail = watchlistOtherList.stream().anyMatch(e -> e.getCexSpot() == null || e.getCexSpot().length() < 6);
  1164. if (!fail) {
  1165. coinMapper.insertOrUpdateCoinWatchlistOtherList(watchlistOtherList);
  1166. }
  1167. }
  1168. public void parseWatchlistMap4Coingecko(Map<String, CoinWatchlist> watchlistMap4Coingecko) {
  1169. String coingeckoCoinsMarketsUrl = InitRunner.dicCodeMap.get("coingecko_coins_markets_url").getCodeValue();
  1170. Map<String, String> headerMap = new HashMap<>();
  1171. headerMap.put("Accept", "application/json");
  1172. headerMap.put("Accept-Encoding", "deflate,gzip");
  1173. Map<String, String> paramMap = new LinkedHashMap<>();
  1174. paramMap.put("ids", StringUtils.join(watchlistMap4Coingecko.keySet(), ","));
  1175. paramMap.put("vs_currency", "usd");
  1176. AtomicInteger i = new AtomicInteger();
  1177. Connection.Response response = null;
  1178. try {
  1179. response = JsoupUtil.requestBody(coingeckoCoinsMarketsUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap);
  1180. JSONArray result = JSONArray.parseArray(response.body());
  1181. for (int j = 0; j < result.size(); j++) {
  1182. JSONObject marketData = result.getJSONObject(j);
  1183. String id = marketData.getString("id");
  1184. if (watchlistMap4Coingecko.containsKey(id)) {
  1185. CoinWatchlist coinWatchlist = watchlistMap4Coingecko.get(id);
  1186. // 总市值排名
  1187. if (marketData.containsKey("market_cap_rank") && null != marketData.get("market_cap_rank")) {
  1188. Integer totalMarketRanking = marketData.getInteger("market_cap_rank");
  1189. coinWatchlist.setTotalMarketRanking(totalMarketRanking);
  1190. }
  1191. // 总市值
  1192. if (marketData.containsKey("market_cap") && null != marketData.get("market_cap")) {
  1193. BigDecimal totalMarketValue = marketData.getBigDecimal("market_cap").setScale(2, RoundingMode.HALF_UP);
  1194. coinWatchlist.setTotalMarketValue(totalMarketValue);
  1195. }
  1196. // 市场价格
  1197. if (marketData.containsKey("current_price") && null != marketData.get("current_price")) {
  1198. String markPrice = marketData.getBigDecimal("current_price").toPlainString();
  1199. coinWatchlist.setMarkPrice(markPrice);
  1200. }
  1201. // 24小时价格变化
  1202. if (marketData.containsKey("price_change_percentage_24h") && null != marketData.get("price_change_percentage_24h")) {
  1203. BigDecimal priceChangePercentage24h = marketData.getBigDecimal("price_change_percentage_24h").setScale(2, RoundingMode.HALF_UP);
  1204. coinWatchlist.setPriceChangePercentage24h(priceChangePercentage24h);
  1205. }
  1206. // 历史最高价格
  1207. if (marketData.containsKey("ath") && null != marketData.get("ath")) {
  1208. String highestHistoricalPrice = marketData.getBigDecimal("ath").toPlainString();
  1209. coinWatchlist.setHighestHistoricalPrice(highestHistoricalPrice);
  1210. }
  1211. // 历史最高点涨幅比例
  1212. if (marketData.containsKey("ath_change_percentage") && null != marketData.get("ath_change_percentage")) {
  1213. BigDecimal athChangePercentage = marketData.getBigDecimal("ath_change_percentage").setScale(2, RoundingMode.HALF_UP);
  1214. coinWatchlist.setAthChangePercentage(athChangePercentage);
  1215. }
  1216. // 历史最高日期
  1217. if (marketData.containsKey("ath_date") && null != marketData.get("ath_date")) {
  1218. LocalDate highestHistoricalDate = LocalDate.parse(marketData.getString("ath_date"), DateUtils.utcTimeFormatter);
  1219. coinWatchlist.setHighestHistoricalDate(highestHistoricalDate);
  1220. }
  1221. // 历史最低价格
  1222. if (marketData.containsKey("atl") && null != marketData.get("atl")) {
  1223. String lowestHistoricalPrice = marketData.getString("atl");
  1224. coinWatchlist.setLowestHistoricalPrice(lowestHistoricalPrice);
  1225. }
  1226. // 历史最低点涨幅比例
  1227. if (marketData.containsKey("atl_change_percentage") && null != marketData.get("atl_change_percentage")) {
  1228. BigDecimal atlChangePercentage = marketData.getBigDecimal("atl_change_percentage").setScale(2, RoundingMode.HALF_UP);
  1229. coinWatchlist.setAtlChangePercentage(atlChangePercentage);
  1230. }
  1231. // 历史最低日期
  1232. if (marketData.containsKey("atl_date") && null != marketData.get("atl_date")) {
  1233. LocalDate lowestHistoricalDate = LocalDate.parse(marketData.getString("atl_date"), DateUtils.utcTimeFormatter);
  1234. coinWatchlist.setLowestHistoricalDate(lowestHistoricalDate);
  1235. }
  1236. // 涨幅倍数
  1237. if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice()) && StringUtils.isNotEmpty(coinWatchlist.getLowestHistoricalPrice())) {
  1238. BigDecimal increaseMultiple = new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(new BigDecimal(coinWatchlist.getLowestHistoricalPrice()), 0, RoundingMode.HALF_UP);
  1239. coinWatchlist.setIncreaseMultiple(increaseMultiple.intValue());
  1240. }
  1241. coinMapper.updateCoinWatchlist(coinWatchlist);
  1242. }
  1243. }
  1244. } catch (Exception e) {
  1245. log.error("parseWatchlistMap4Coingecko error,response={},size={},i={}", response != null ? response.body() : "--", watchlistMap4Coingecko.size(), i.get(), e);
  1246. }
  1247. }
  1248. public void parseWatchlistMap4CmC(Map<Long, CoinWatchlist> watchlistMap4CmC) {
  1249. String coinmarketcapApikey = InitRunner.dicCodeMap.get("coinmarketcap_apikey").getCodeValue();
  1250. String coinmarketcapQuotesLatestUrl = InitRunner.dicCodeMap.get("coinmarketcap_quotes_latest_url").getCodeValue();
  1251. Map<String, String> headerMap = new HashMap<>();
  1252. headerMap.put("Accept", "application/json");
  1253. headerMap.put("Accept-Encoding", "deflate,gzip");
  1254. headerMap.put("X-CMC_PRO_API_KEY", coinmarketcapApikey);
  1255. Map<String, String> paramMap = new LinkedHashMap<>();
  1256. paramMap.put("id", StringUtils.join(watchlistMap4CmC.keySet(), ","));
  1257. AtomicInteger i = new AtomicInteger();
  1258. try {
  1259. Connection.Response response = JsoupUtil.requestBody(coinmarketcapQuotesLatestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, headerMap, paramMap);
  1260. JSONObject result = JSONObject.parseObject(response.body());
  1261. JSONObject dataJO = result.getJSONObject("data");
  1262. watchlistMap4CmC.forEach((key, value) -> {
  1263. i.getAndIncrement();
  1264. if (dataJO.containsKey(key)) {
  1265. JSONObject jsonObject = dataJO.getJSONObject(String.valueOf(key));
  1266. value.setTotalMarketRanking(jsonObject.getInteger("cmc_rank"));
  1267. BigDecimal totalMarketValue = null;
  1268. try {
  1269. totalMarketValue = jsonObject.getJSONObject("quote").getJSONObject("USD").getBigDecimal("market_cap").setScale(2, RoundingMode.HALF_UP);
  1270. if (totalMarketValue.compareTo(BigDecimal.ZERO) == 0) {
  1271. totalMarketValue = jsonObject.getBigDecimal("self_reported_market_cap").setScale(2, RoundingMode.HALF_UP);
  1272. }
  1273. } catch (Exception e) {
  1274. if (jsonObject.containsKey("self_reported_market_cap") && null != jsonObject.getBigDecimal("self_reported_market_cap")) {
  1275. totalMarketValue = jsonObject.getBigDecimal("self_reported_market_cap").setScale(2, RoundingMode.HALF_UP);
  1276. }
  1277. }
  1278. value.setTotalMarketValue(totalMarketValue);
  1279. // cmc_url
  1280. value.setCmcUrl(jsonObject.getString("slug"));
  1281. // coingecko_url
  1282. if (StringUtils.isEmpty(value.getCoingeckoUrl())) {
  1283. value.setCoingeckoUrl(value.getCmcUrl());
  1284. }
  1285. coinMapper.updateCoinWatchlist(value);
  1286. // 解析CoinWatchlistOther
  1287. CoinWatchlistOther coinWatchlistOther = new CoinWatchlistOther();
  1288. coinWatchlistOther.setSymbol(value.getSymbol());
  1289. if (null != jsonObject.get("max_supply")) {
  1290. coinWatchlistOther.setMaxSupply(jsonObject.getBigDecimal("max_supply").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1291. }
  1292. if (null != jsonObject.get("circulating_supply")) {
  1293. coinWatchlistOther.setCirculatingSupply(jsonObject.getBigDecimal("circulating_supply").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1294. if ("0".equals(coinWatchlistOther.getCirculatingSupply()) && null != jsonObject.get("self_reported_circulating_supply")) {
  1295. coinWatchlistOther.setCirculatingSupply(jsonObject.getBigDecimal("self_reported_circulating_supply").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1296. }
  1297. }
  1298. if (null != coinWatchlistOther.getMaxSupply() && null != coinWatchlistOther.getCirculatingSupply()) {
  1299. coinWatchlistOther.setCirculatingRate(new BigDecimal(coinWatchlistOther.getCirculatingSupply()).divide(new BigDecimal(coinWatchlistOther.getMaxSupply()), 3, RoundingMode.HALF_UP).multiply(new BigDecimal("100")).toPlainString());
  1300. }
  1301. if (null != jsonObject.get("total_supply")) {
  1302. coinWatchlistOther.setTotalSupply(jsonObject.getBigDecimal("total_supply").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1303. }
  1304. JSONObject usdQuote = jsonObject.getJSONObject("quote").getJSONObject("USD");
  1305. if (usdQuote != null && null != usdQuote.get("market_cap")) {
  1306. coinWatchlistOther.setMarketCap(usdQuote.getBigDecimal("market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1307. if ("0".equals(coinWatchlistOther.getMarketCap()) && null != jsonObject.get("self_reported_market_cap")) {
  1308. coinWatchlistOther.setMarketCap(jsonObject.getBigDecimal("self_reported_market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1309. }
  1310. }
  1311. if (usdQuote != null && null != usdQuote.get("fully_diluted_market_cap")) {
  1312. coinWatchlistOther.setFullyDilutedMarketCap(usdQuote.getBigDecimal("fully_diluted_market_cap").setScale(0, RoundingMode.HALF_UP).toPlainString());
  1313. }
  1314. coinMapper.insertOrUpdateCoinWatchlistOther(coinWatchlistOther);
  1315. }
  1316. });
  1317. } catch (Exception e) {
  1318. log.error("parseWatchlistMap4CmC error,size={},i={}", watchlistMap4CmC.size(), i.get(), e);
  1319. }
  1320. }
  1321. @Override
  1322. @Async("coinTaskExecutor")
  1323. public void monitorAlarm4APP_TEXT_CARD(String content, JSONObject params, WxCpService wxCpServiceFinal) {
  1324. // 文本卡片模式发消息
  1325. String title = "监控告警明细";
  1326. if (params.containsKey("title")) {
  1327. title = params.getString("title");
  1328. }
  1329. String logUrl = "https://lvzhiqiang.top";
  1330. if (params.containsKey("logUrl")) {
  1331. logUrl = params.getString("logUrl");
  1332. }
  1333. String btnTxt = "日志详情";
  1334. if (params.containsKey("btnTxt")) {
  1335. btnTxt = params.getString("btnTxt");
  1336. }
  1337. String user = "LvZhiQiang";
  1338. if (params.containsKey("user")) {
  1339. user = params.getString("user");
  1340. }
  1341. String party = "";
  1342. if (params.containsKey("party")) {
  1343. party = params.getString("party");
  1344. }
  1345. String tag = "";
  1346. if (params.containsKey("tag")) {
  1347. tag = params.getString("tag");
  1348. }
  1349. Integer agentId = properties.getAgentId();
  1350. if (params.containsKey("agentId")) {
  1351. agentId = params.getInteger("agentId");
  1352. }
  1353. if (wxCpServiceFinal == null) {
  1354. wxCpServiceFinal = wxCpService;
  1355. }
  1356. WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(agentId)
  1357. .toUser(user)
  1358. .toParty(party)
  1359. .toTag(tag)
  1360. .title(title).description(content)
  1361. .url(logUrl).btnTxt(btnTxt)
  1362. .build();
  1363. try {
  1364. log.info("企业微信推送消息,send message: {}", wxCpMessage);
  1365. WxCpMessageSendResult sendResult = wxCpServiceFinal.getMessageService().send(wxCpMessage);
  1366. log.info("企业微信推送消息成功,send result: {}", sendResult);
  1367. } catch (WxErrorException e) {
  1368. log.error("企业微信推送消息失败!Detail: ", e);
  1369. }
  1370. }
  1371. @Override
  1372. @Async("coinTaskExecutor")
  1373. public void monitorAlarm4NEWS(JSONObject params, WxCpService wxCpServiceFinal, NewArticle... articles) {
  1374. // 图文消息
  1375. String title = "监控告警明细";
  1376. if (params.containsKey("title")) {
  1377. title = params.getString("title");
  1378. }
  1379. String logUrl = "https://lvzhiqiang.top";
  1380. if (params.containsKey("logUrl")) {
  1381. logUrl = params.getString("logUrl");
  1382. }
  1383. String btnTxt = "日志详情";
  1384. if (params.containsKey("btnTxt")) {
  1385. btnTxt = params.getString("btnTxt");
  1386. }
  1387. String user = "LvZhiQiang";
  1388. if (params.containsKey("user")) {
  1389. user = params.getString("user");
  1390. }
  1391. String party = "";
  1392. if (params.containsKey("party")) {
  1393. party = params.getString("party");
  1394. }
  1395. String tag = "";
  1396. if (params.containsKey("tag")) {
  1397. tag = params.getString("tag");
  1398. }
  1399. Integer agentId = properties.getAgentId();
  1400. if (params.containsKey("agentId")) {
  1401. agentId = params.getInteger("agentId");
  1402. }
  1403. if (wxCpServiceFinal == null) {
  1404. wxCpServiceFinal = wxCpService;
  1405. }
  1406. WxCpMessage wxCpMessage = WxCpMessage.NEWS().agentId(agentId)
  1407. .toUser(user)
  1408. .toParty(party)
  1409. .toTag(tag)
  1410. .addArticle(articles)
  1411. .build();
  1412. try {
  1413. log.info("企业微信推送消息,send message: {}", wxCpMessage);
  1414. WxCpMessageSendResult sendResult = wxCpServiceFinal.getMessageService().send(wxCpMessage);
  1415. log.info("企业微信推送消息成功,send result: {}", sendResult);
  1416. } catch (WxErrorException e) {
  1417. log.error("企业微信推送消息失败!Detail: ", e);
  1418. }
  1419. }
  1420. @Override
  1421. @Async("coinTaskExecutor")
  1422. public void monitorAlarm4CHAT_BOT(String content, JSONObject params) {
  1423. // 调用企业微信群聊机器人发消息
  1424. WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService();
  1425. String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940";
  1426. List<String> userList = Collections.singletonList("LvZhiQiang");
  1427. if (params != null && params.containsKey("user")) {
  1428. userList = Arrays.asList(params.getString("user").split("[|,]"));
  1429. }
  1430. try {
  1431. log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, userList);
  1432. groupRobotService.sendText(webhookUrl, content, userList, Collections.emptyList());
  1433. log.info("企业微信推送消息成功");
  1434. } catch (WxErrorException e) {
  1435. log.error("企业微信推送消息失败!Detail: ", e);
  1436. }
  1437. }
  1438. @Override
  1439. @Async("coinTaskExecutor")
  1440. public void monitorAlarm(String content, String jobAlarmMode) {
  1441. // 判断告警模式
  1442. if (StringUtils.isEmpty(JOB_ALARM_MODE)) {
  1443. jobAlarmMode = JOB_ALARM_MODE;
  1444. }
  1445. // 文本卡片模式发消息
  1446. if (JOB_ALARM_MODE_APP_TEXT_CARD.equals(jobAlarmMode)) {
  1447. String title = "监控告警明细";
  1448. String logUrl = "https://lvzhiqiang.top";
  1449. String btnTxt = "日志详情";
  1450. WxCpMessage wxCpMessage = WxCpMessage.TEXTCARD().agentId(properties.getAgentId())
  1451. .toUser("LvZhiQiang")
  1452. .toParty("")
  1453. .toTag("")
  1454. .title(title).description(content)
  1455. .url(logUrl).btnTxt(btnTxt)
  1456. .build();
  1457. try {
  1458. log.info("企业微信推送消息,send message: {}", wxCpMessage);
  1459. WxCpMessageSendResult sendResult = wxCpService.getMessageService().send(wxCpMessage);
  1460. log.info("企业微信推送消息成功,send result: {}", sendResult);
  1461. } catch (WxErrorException e) {
  1462. log.error("企业微信推送消息失败!Detail: ", e);
  1463. }
  1464. }
  1465. // 调用企业微信群聊机器人发消息
  1466. if (JOB_ALARM_MODE_CHAT_BOT.equals(jobAlarmMode)) {
  1467. WxCpGroupRobotService groupRobotService = wxCpService.getGroupRobotService();
  1468. String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=082970da-2a33-422a-81f6-15f9bde87940";
  1469. try {
  1470. log.info("企业微信推送消息,send content: {}, userIdSet: {}", content, "LvZhiQiang");
  1471. groupRobotService.sendText(webhookUrl, content, Collections.singletonList("LvZhiQiang"), Collections.emptyList());
  1472. log.info("企业微信推送消息成功");
  1473. } catch (WxErrorException e) {
  1474. log.error("企业微信推送消息失败!Detail: ", e);
  1475. }
  1476. }
  1477. }
  1478. /**
  1479. * 请求通用API方法
  1480. */
  1481. private JSONObject requestApi4Common(String requestPath, String signQueryString, String signBody, String httpMethod, Map<String, String> paramMap) {
  1482. String timestamp = String.valueOf(System.currentTimeMillis());
  1483. Map<String, String> headerMap = new HashMap<>();
  1484. headerMap.putAll(basicHeaderMap);
  1485. try {
  1486. String accessSign = CheckSign4Bitget.generate(timestamp, httpMethod, requestPath, signQueryString, signBody, secretKey);
  1487. headerMap.put("ACCESS-TIMESTAMP", timestamp);
  1488. headerMap.put("ACCESS-SIGN", accessSign);
  1489. } catch (CloneNotSupportedException e) {
  1490. throw new RuntimeException(e);
  1491. } catch (InvalidKeyException e) {
  1492. throw new RuntimeException(e);
  1493. } catch (UnsupportedEncodingException e) {
  1494. throw new RuntimeException(e);
  1495. }
  1496. try {
  1497. String requestUrl = mainUrl + requestPath;
  1498. if (httpMethod.equals(JsoupUtil.HTTP_GET)) {
  1499. Connection.Response response = JsoupUtil.requestBody(requestUrl, httpMethod, InitRunner.proxy, headerMap, paramMap);
  1500. return JSONObject.parseObject(response.body());
  1501. } else {
  1502. Connection.Response response = JsoupUtil.requestBodyJSON(requestUrl, httpMethod, InitRunner.proxy, null, headerMap, paramMap);
  1503. return JSONObject.parseObject(response.body());
  1504. }
  1505. } catch (Exception e) {
  1506. throw new RuntimeException(e);
  1507. }
  1508. }
  1509. private JSONObject requestApi4Common4OKX(String requestPath, String signQueryString, String signBody, String httpMethod, Map<String, String> paramMap) {
  1510. String timestamp = DateUtils.getUTCTimeStr();
  1511. Map<String, String> headerMap = new HashMap<>();
  1512. headerMap.putAll(basicHeaderMap4OKX);
  1513. try {
  1514. String accessSign = CheckSign4OKX.generate(timestamp, httpMethod, requestPath, signQueryString, signBody, secretKey4OKX);
  1515. headerMap.put("OK-ACCESS-TIMESTAMP", timestamp);
  1516. headerMap.put("OK-ACCESS-SIGN", accessSign);
  1517. } catch (CloneNotSupportedException e) {
  1518. throw new RuntimeException(e);
  1519. } catch (InvalidKeyException e) {
  1520. throw new RuntimeException(e);
  1521. } catch (UnsupportedEncodingException e) {
  1522. throw new RuntimeException(e);
  1523. }
  1524. try {
  1525. String requestUrl = "https://www.okx.com" + requestPath;
  1526. if (httpMethod.equals(JsoupUtil.HTTP_GET)) {
  1527. Connection.Response response = JsoupUtil.requestBody(requestUrl, httpMethod, InitRunner.proxy, headerMap, paramMap);
  1528. return JSONObject.parseObject(response.body());
  1529. } else {
  1530. Connection.Response response = JsoupUtil.requestBodyJSON(requestUrl, httpMethod, InitRunner.proxy, null, headerMap, paramMap);
  1531. return JSONObject.parseObject(response.body());
  1532. }
  1533. } catch (Exception e) {
  1534. throw new RuntimeException(e);
  1535. }
  1536. }
  1537. @Override
  1538. public Object mainSearch(JSONObject params) throws Exception {
  1539. JSONArray result = new JSONArray();
  1540. if (params.getString("nameEn").equals("allPositionv2")) {
  1541. Map<String, String> paramMap = new HashMap<>();
  1542. paramMap.put("productType", "umcbl");
  1543. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  1544. JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  1545. result = response.getJSONArray("data");
  1546. renderMainSearch4AllPositionv2(result, params.getInteger("unrealizedPLSort"));
  1547. } else if (params.getString("nameEn").equals("orderMarginCoinCurrent")) {
  1548. Map<String, String> paramMap = new LinkedHashMap<>();
  1549. paramMap.put("productType", "umcbl");
  1550. paramMap.put("marginCoin", "USDT");
  1551. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  1552. JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  1553. result = response.getJSONArray("data");
  1554. renderMainSearch4OrderMarginCoinCurrent(result, params.getInteger("chaRateSort"));
  1555. } else if (params.getString("nameEn").equals("orderHistoryProductType")) {
  1556. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1557. List<CoinHistoryOrder> historyOrderList = coinMapper.findHistoryOrderList(params.toJavaObject(Map.class));
  1558. PageInfo<CoinHistoryOrder> historyOrderPageInfo = new PageInfo<>(historyOrderList);
  1559. renderMainSearch4OrderHistoryProductType(historyOrderList);
  1560. //result = (JSONArray) JSON.toJSON(historyOrderList);
  1561. return historyOrderPageInfo;
  1562. } else if (params.getString("nameEn").equals("orderHistorySpotProductType")) {
  1563. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1564. List<CoinSpotOrders> spotOrdersList = new ArrayList<>();
  1565. String exchangeCategory = params.getString("exchangeCategory");
  1566. if ("6".equals(exchangeCategory)){
  1567. List<CoinGateSpotOrders> gateSpotOrdersList = coinGateMapper.findHistorySpotOrderList(params.toJavaObject(Map.class));
  1568. renderMainSearch4OrderHistorySpotProductType(gateSpotOrdersList, spotOrdersList);
  1569. }
  1570. PageInfo<CoinSpotOrders> historyOrderPageInfo = new PageInfo<>(spotOrdersList);
  1571. return historyOrderPageInfo;
  1572. } else if (params.getString("nameEn").equals("traderList")) {
  1573. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1574. List<CoinTrader> mixTraderList = coinMapper.findMixTraderList(params.toJavaObject(Map.class));
  1575. PageInfo<CoinTrader> coinTraderPageInfo = new PageInfo<>(mixTraderList);
  1576. renderMainSearch4TraderList(mixTraderList);
  1577. //result = (JSONArray) JSON.toJSON(mixTraderList);
  1578. return coinTraderPageInfo;
  1579. } else if (params.getString("nameEn").equals("watchlist")) {
  1580. JSONObject coinUser = coinMapper.findUserByUsername(params.getString("userName"));
  1581. if (coinUser == null) {
  1582. throw new ParameterException("用户不存在!");
  1583. }
  1584. Integer userId = coinUser.getInteger("id");
  1585. params.put("userId",userId);
  1586. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1587. if (params.containsKey("sortField")) {
  1588. params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
  1589. }
  1590. if (params.containsKey("cexFilterField") && StringUtils.isNotEmpty(params.getString("cexFilterField"))) {
  1591. String[] cexFilterFieldArr = params.getString("cexFilterField").split(",");
  1592. params.put("cexFilterName", cexFilterFieldArr[0]);
  1593. params.put("cexFilterIndex", cexFilterFieldArr[1]);
  1594. }
  1595. List<CoinWatchlist> watchlistList = coinMapper.findWatchlistList2ByUserId(params.toJavaObject(Map.class));
  1596. PageInfo<CoinWatchlist> watchlistPageInfo = new PageInfo<>(watchlistList);
  1597. renderMainSearch4Watchlist(watchlistList, userId);
  1598. return watchlistPageInfo;
  1599. } else if (params.getString("nameEn").equals("image")) {
  1600. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1601. List<FileImage> fileImageList = pictureInfoMapper.findImageList(params.toJavaObject(Map.class));
  1602. PageInfo<FileImage> imagePageInfo = new PageInfo<>(fileImageList);
  1603. renderMainSearch4Image(fileImageList);
  1604. return imagePageInfo;
  1605. } else if (params.getString("nameEn").equals("cmcmap")) {
  1606. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1607. List<CoinCmcMap> cmcMapList = coinMapper.findCmcMapList(params.toJavaObject(Map.class));
  1608. PageInfo<CoinCmcMap> cmcMapPageInfo = new PageInfo<>(cmcMapList);
  1609. renderMainSearch4CmcMap(cmcMapList);
  1610. return cmcMapPageInfo;
  1611. } else if (params.getString("nameEn").equals("monitorCurrency")) {
  1612. List<CoinMonitorCurrency> monitorCurrencyList = coinMapper.findMonitorCurrencyList();
  1613. Map<String, JSONArray> resultMulti = new ConcurrentHashMap<>();
  1614. Arrays.stream(params.getString("url").split(",")).parallel().forEach(e -> {
  1615. String requestUrl = mainUrl + e;
  1616. try {
  1617. Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  1618. resultMulti.put(e, JSONObject.parseObject(response.body()).getJSONArray("data"));
  1619. } catch (Exception ex) {
  1620. throw new RuntimeException(ex);
  1621. }
  1622. });
  1623. result = renderMainSearch4MonitorCurrency(resultMulti, monitorCurrencyList, params.getInteger("changeUtcSort"));
  1624. } else if (params.getString("nameEn").equals("currentPlan")) {
  1625. Map<String, String> paramMap = new LinkedHashMap<>();
  1626. paramMap.put("productType", "umcbl");
  1627. paramMap.put("isPlan", "plan");
  1628. String signQueryString = paramMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
  1629. JSONObject response = requestApi4Common(params.getString("url"), signQueryString, null, JsoupUtil.HTTP_GET, paramMap);
  1630. result = response.getJSONArray("data");
  1631. renderMainSearch4CurrentPlan(result, params.getInteger("chaRateSort"));
  1632. } else if (params.getString("nameEn").equals("music")) {
  1633. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1634. List<FileMusicCollection> fileMusicCollectionList = musicInfoMapper.findMusicCollectionList(params.toJavaObject(Map.class));
  1635. PageInfo<FileMusicCollection> musicCollectionPageInfo = new PageInfo<>(fileMusicCollectionList);
  1636. renderMainSearch4Music(fileMusicCollectionList);
  1637. return musicCollectionPageInfo;
  1638. } else if (params.getString("nameEn").equals("currentHolding")) {
  1639. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1640. if (params.containsKey("sortField")) {
  1641. params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
  1642. }
  1643. List<CoinCurrencyHolding> currentHoldingList = coinMapper.findCurrentHoldingList(params.toJavaObject(Map.class));
  1644. PageInfo<CoinCurrencyHolding> currentHoldingPageInfo = new PageInfo<>(currentHoldingList);
  1645. renderMainSearch4CurrencyHolding(currentHoldingList);
  1646. return currentHoldingPageInfo;
  1647. } else if (params.getString("nameEn").equals("bookmark")) {
  1648. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1649. if (params.containsKey("sortField")) {
  1650. params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
  1651. }
  1652. String level1CategoryField = params.getString("level1CategoryField");
  1653. String level2CategoryField = params.getString("level2CategoryField");
  1654. String level3CategoryField = params.getString("level3CategoryField");
  1655. String categoryId = "999999999999999";
  1656. if (StringUtils.isNotEmpty(level3CategoryField)) {
  1657. categoryId = level3CategoryField;
  1658. } else if (StringUtils.isNotEmpty(level2CategoryField)) {
  1659. categoryId = level2CategoryField;
  1660. } else if (StringUtils.isNotEmpty(level1CategoryField)) {
  1661. categoryId = level1CategoryField;
  1662. }
  1663. String path = bookmarkMapper.selectPathByCategoryId(Long.valueOf(categoryId));
  1664. List<Long> categoryIds = bookmarkMapper.selectCategoryAndChildrenIds(Long.valueOf(categoryId), path);
  1665. params.put("categoryIds", categoryIds);
  1666. List<BookmarkInfo> bookmarkInfoList = bookmarkMapper.findBookmarkList(params.toJavaObject(Map.class));
  1667. PageInfo<BookmarkInfo> bookmarkInfoPageInfo = new PageInfo<>(bookmarkInfoList);
  1668. renderMainSearch4Bookmark(bookmarkInfoList);
  1669. return bookmarkInfoPageInfo;
  1670. } else if (params.getString("nameEn").equals("youtubeLive")) {
  1671. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1672. if (params.containsKey("sortField")) {
  1673. params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
  1674. }
  1675. List<CoinYoutubeYt2140LiveChapterDTO> yt2140LiveChapterList = coinYoutubeMapper.findYoutubeLiveList(params.toJavaObject(Map.class));
  1676. PageInfo<CoinYoutubeYt2140LiveChapterDTO> yt2140LiveChapterPageInfo = new PageInfo<>(yt2140LiveChapterList);
  1677. renderMainSearch4YoutubeYt2140LiveChapter(yt2140LiveChapterList);
  1678. return yt2140LiveChapterPageInfo;
  1679. } else if (params.getString("nameEn").equals("goldenQuotes")) {
  1680. PageHelper.startPage(params.getInteger("pageNo"), params.getInteger("pageSize"), true);
  1681. if (params.containsKey("sortField")) {
  1682. params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
  1683. }
  1684. List<GoldenQuotes> goldenQuotesList = goldenQuotesMapper.findGoldenQuotesList(params.toJavaObject(Map.class));
  1685. PageInfo<GoldenQuotes> goldenQuotesPageInfo = new PageInfo<>(goldenQuotesList);
  1686. renderMainSearch4GoldenQuotes(goldenQuotesList);
  1687. return goldenQuotesPageInfo;
  1688. }
  1689. return result;
  1690. }
  1691. private void renderMainSearch4GoldenQuotes(List<GoldenQuotes> goldenQuotesList) {
  1692. for (GoldenQuotes goldenQuotes : goldenQuotesList) {
  1693. // 是否收藏
  1694. goldenQuotes.setIsPinnedStr(1 == goldenQuotes.getIsPinned() ? "是" : "否");
  1695. goldenQuotes.setTags(StringUtils.isNotEmpty(goldenQuotes.getTags()) ? goldenQuotes.getTags() : "");
  1696. String content = goldenQuotes.getContent().length() > 50 ? (goldenQuotes.getContent().substring(0, 50) + "...") : goldenQuotes.getContent();
  1697. goldenQuotes.setContent("<span class=\"primary\" title=\"" + goldenQuotes.getContent() + " \" >" + content + " </span>");
  1698. String remark;
  1699. if (StringUtils.isNotEmpty(goldenQuotes.getRemark())) {
  1700. if (goldenQuotes.getRemark().length() > 15) {
  1701. remark = goldenQuotes.getRemark().substring(0, 15) + "...";
  1702. } else {
  1703. remark = goldenQuotes.getRemark();
  1704. }
  1705. } else {
  1706. remark = "";
  1707. }
  1708. goldenQuotes.setRemark("<span class=\"primary\" title=\"" + goldenQuotes.getRemark() + " \" >" + remark + " </span>");
  1709. if (StringUtils.isNotEmpty(goldenQuotes.getOriginalUrl())) {
  1710. goldenQuotes.setSourcePlatform("<a target=\"_blank\" href=\" " + goldenQuotes.getOriginalUrl() + "\">" + goldenQuotes.getSourcePlatform() + "</a>");
  1711. }
  1712. }
  1713. }
  1714. private void renderMainSearch4YoutubeYt2140LiveChapter(List<CoinYoutubeYt2140LiveChapterDTO> yt2140LiveChapterList) {
  1715. for (CoinYoutubeYt2140LiveChapterDTO yt2140LiveChapter : yt2140LiveChapterList) {
  1716. String chapterUrl = yt2140LiveChapter.getOriginalUrl().concat("&t=").concat(String.valueOf(yt2140LiveChapter.getStartTime())).concat("s");
  1717. // 顺序
  1718. yt2140LiveChapter.setChapterSort(yt2140LiveChapter.getSort() + "/" + yt2140LiveChapter.getChapterCount());
  1719. // 开始时间
  1720. yt2140LiveChapter.setReadableStartTime("<a target=\"_blank\" href=\" " + chapterUrl + "\">" + formatTime(yt2140LiveChapter.getStartTime()) + "</a>");
  1721. // 结束时间
  1722. yt2140LiveChapter.setReadableEndTime(formatTime(yt2140LiveChapter.getEndTime() - 1));
  1723. }
  1724. }
  1725. public String formatTime(long totalSeconds) {
  1726. if (totalSeconds >= 3600) {
  1727. return DurationFormatUtils.formatDuration(totalSeconds * 1000, "H:mm:ss"); // 2:20:20
  1728. } else {
  1729. return DurationFormatUtils.formatDuration(totalSeconds * 1000, "mm:ss"); // 20:20
  1730. }
  1731. }
  1732. private void renderMainSearch4Bookmark(List<BookmarkInfo> bookmarkInfoList) {
  1733. for (BookmarkInfo bookmarkInfo : bookmarkInfoList) {
  1734. // 名称
  1735. bookmarkInfo.setTitle("<a target=\"_blank\" href=\" " + bookmarkInfo.getUrl() + "\">" + bookmarkInfo.getTitle() + "</a>");
  1736. // 是否收藏
  1737. bookmarkInfo.setIsFavoriteStr(bookmarkInfo.getIsFavorite() == 1 ? "是" : "否");
  1738. // 状态
  1739. bookmarkInfo.setStatusStr(bookmarkInfo.getStatus() == 1 ? "正常" : "失效");
  1740. // tags
  1741. bookmarkInfo.setTags(StringUtils.isEmpty(bookmarkInfo.getTags()) ? "--" : bookmarkInfo.getTags());
  1742. // sourceUrl
  1743. if (StringUtils.isNotEmpty(bookmarkInfo.getSourceUrl())) {
  1744. bookmarkInfo.setSourceUrl("<a target=\"_blank\" href=\" " + bookmarkInfo.getSourceUrl() + "\">" + "goGO" + "</a>");
  1745. } else {
  1746. bookmarkInfo.setSourceUrl("--");
  1747. }
  1748. // 备注
  1749. String remark;
  1750. if (StringUtils.isNotEmpty(bookmarkInfo.getRemark())) {
  1751. if (bookmarkInfo.getRemark().length() > 20) {
  1752. remark = bookmarkInfo.getRemark().substring(0, 20) + "...";
  1753. } else {
  1754. remark = bookmarkInfo.getRemark();
  1755. }
  1756. } else {
  1757. remark = "--";
  1758. }
  1759. bookmarkInfo.setRemark("<span class=\"primary\" title=\"" + bookmarkInfo.getRemark() + "\" avid=\"" + bookmarkInfo.getId() + " \" >" + remark + " </span>");
  1760. }
  1761. }
  1762. private void renderMainSearch4CurrencyHolding(List<CoinCurrencyHolding> currentHoldingList) {
  1763. for (CoinCurrencyHolding coinCurrencyHolding : currentHoldingList) {
  1764. // 名称
  1765. coinCurrencyHolding.setSymbolStyle(" style=\"background-color:rgba(70,169,244,.72);font-weight: bold;\"");
  1766. // 当前价格
  1767. coinCurrencyHolding.setCurrentPriceStyle(" style=\"color:#252B31;background-color:#C4ADE9;\"");
  1768. if (StringUtils.isNotEmpty(coinCurrencyHolding.getCurrentPrice())) {
  1769. coinCurrencyHolding.setCurrentPrice(new BigDecimal(coinCurrencyHolding.getCurrentPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString());
  1770. }
  1771. // 入场价格
  1772. if (StringUtils.isNotEmpty(coinCurrencyHolding.getBuyPrice())) {
  1773. coinCurrencyHolding.setBuyPrice(new BigDecimal(coinCurrencyHolding.getBuyPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString());
  1774. }
  1775. coinCurrencyHolding.setStatusStr(coinCurrencyHolding.getStatus() == 1 ? "正常" : "失效");
  1776. // 涨跌幅比例
  1777. if (coinCurrencyHolding.getChangePercentage() == null) {
  1778. } else if (coinCurrencyHolding.getChangePercentage().compareTo(BigDecimal.ONE) < 0) {
  1779. coinCurrencyHolding.setChangePercentageStyle(" style=\"color:#000000;background-color:#f1a8a4;\"");
  1780. } else {
  1781. coinCurrencyHolding.setChangePercentageStyle(" style=\"color:#000000;background-color:#aad6f5;\"");
  1782. }
  1783. }
  1784. }
  1785. private void renderMainSearch4CmcMap(List<CoinCmcMap> cmcMapList) {
  1786. for (CoinCmcMap coinCmcMap : cmcMapList) {
  1787. String platform = coinCmcMap.getPlatform();
  1788. if (StringUtils.isNotEmpty(platform)) {
  1789. JSONObject jsonObject = JSONObject.parseObject(platform);
  1790. jsonObject.remove("token_address");
  1791. coinCmcMap.setPlatform(jsonObject.toJSONString());
  1792. }
  1793. }
  1794. }
  1795. private void renderMainSearch4Image(List<FileImage> fileImageList) {
  1796. // String ftpBaseurl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue();
  1797. for (FileImage fileImage : fileImageList) {
  1798. // String newPath = "<a target=\"_blank\" href=\" " + ftpBaseurl + fileImage.getPath() + "\">" + fileImage.getNewName() + "</a>";
  1799. // fileImage.setNewName(newPath);
  1800. // fileImage.setOldName("<span class=\"primary\" avid=\"" + fileImage.getId() + " \" >" + fileImage.getOldName() + " </span>");
  1801. fileImage.setRemark("<span class=\"primary\" avid=\"" + fileImage.getId() + " \" >" + fileImage.getRemark() + " </span>");
  1802. }
  1803. }
  1804. private void renderMainSearch4Music(List<FileMusicCollection> fileMusicCollectionList) {
  1805. List<JSONObject> fileMusicCategoryList = coinApiConfigMapper.findFileMusicCategoryList();
  1806. Map<String, String> fileMusicCategoryMap = fileMusicCategoryList.stream().collect(Collectors.toMap(e -> e.getString("id"), e -> e.getString("categoryName")));
  1807. String ftpBasePath = InitRunner.dicCodeMap.get("ftp_music_basepath").getCodeValue();
  1808. String ftpBaseUrl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue();
  1809. String[] scoreTitleArr = {"很差", "较差", "还行", "推荐", "力荐"};
  1810. for (FileMusicCollection fileMusicCollection : fileMusicCollectionList) {
  1811. String categoryName = Arrays.stream(fileMusicCollection.getCategoryId().split(",")).map(original -> fileMusicCategoryMap.getOrDefault(original, original)).collect(Collectors.joining(","));
  1812. fileMusicCollection.setCategoryName(categoryName);
  1813. String remark = StringUtils.isNotEmpty(fileMusicCollection.getRemark()) && fileMusicCollection.getRemark().length() > 20 ? (fileMusicCollection.getRemark().substring(0, 20) + "...") : fileMusicCollection.getRemark();
  1814. fileMusicCollection.setRemark("<span class=\"primary\" avid=\"" + fileMusicCollection.getId() + " \" >" + remark + " </span>");
  1815. if (StringUtils.isNotEmpty(fileMusicCollection.getLowQualityUrl())) {
  1816. fileMusicCollection.setLowQualityUrl(ftpBaseUrl + ftpBasePath + fileMusicCollection.getLowQualityUrl());
  1817. }
  1818. if (StringUtils.isNotEmpty(fileMusicCollection.getHighQualityUrl())) {
  1819. fileMusicCollection.setHighQualityUrl(ftpBaseUrl + ftpBasePath + fileMusicCollection.getHighQualityUrl());
  1820. }
  1821. // 优先级
  1822. Integer score = Integer.valueOf(fileMusicCollection.getScore());
  1823. score = score > 5 ? 5 : score;
  1824. StringBuffer scoreSB = new StringBuffer("<ul class=\"rating\">");
  1825. for (int i = 0; i < scoreTitleArr.length; i++) {
  1826. String style = (i + 1) <= score ? "fa-star" : "fa-star-o";
  1827. scoreSB.append("<li id=\"" +fileMusicCollection.getId() + "\" title=\"" + scoreTitleArr[i] + "\" val=\"" + (i + 1) + "\"><i class=\"fa " + style + "\"></i></li>");
  1828. }
  1829. scoreSB.append("</ul>");
  1830. fileMusicCollection.setScore(scoreSB.toString());
  1831. }
  1832. }
  1833. private void renderMainSearch4Watchlist(List<CoinWatchlist> watchlistList, Integer userId) {
  1834. BigDecimal bigDecimal10000 = new BigDecimal("10000");
  1835. List<String> popularTrackCategoryList = Arrays.asList("DePIN", "AI", "RWA", "大饼生态", "以太Layer-2", "Restaking再质押", "NFT|链游|元宇宙", "WEB3社交");
  1836. // 赛道分类预处理
  1837. List<String> trackCategoryList = coinApiConfigMapper.findTrackCategoryListByUserId(userId).stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
  1838. List<String> trackCategory2List = coinApiConfigMapper.findTrackCategory2ListByUserId(userId).stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
  1839. Map<String, String> trackCategoryMap = new HashMap<>();
  1840. Map<String, String> trackCategory2Map = new HashMap<>();
  1841. List<String> colorList = coinApiConfigMapper.findColorStyleList();
  1842. int j = 0;
  1843. for (int i = 0; i < trackCategoryList.size(); i++) {
  1844. if (j > colorList.size() - 1) {
  1845. j = 0;
  1846. }
  1847. String popularTrackCategoryStyle = popularTrackCategoryList.contains(trackCategoryList.get(i)) ? "font-weight:bold;" : "";
  1848. trackCategoryMap.put(trackCategoryList.get(i), colorList.get(j) + popularTrackCategoryStyle);
  1849. j++;
  1850. }
  1851. j = 0;
  1852. for (int i = 0; i < trackCategory2List.size(); i++) {
  1853. if (j > colorList.size() - 1) {
  1854. j = 0;
  1855. }
  1856. String popularTrackCategoryStyle = popularTrackCategoryList.contains(trackCategory2List.get(i)) ? "font-weight:bold;" : "";
  1857. trackCategory2Map.put(trackCategory2List.get(i), colorList.get(j) + popularTrackCategoryStyle);
  1858. j++;
  1859. }
  1860. String[] scoreTitleArr = {"很差", "较差", "还行", "推荐", "力荐"};
  1861. String regex = "[+-]?\\d*\\.?\\d*[eE][+-]?\\d+"; // 科学计数法正则表达式
  1862. for (CoinWatchlist coinWatchlist : watchlistList) {
  1863. // 优先级
  1864. Integer score = Integer.valueOf(coinWatchlist.getScore());
  1865. score = score > 5 ? 5 : score;
  1866. StringBuffer scoreSB = new StringBuffer("<ul class=\"rating\">");
  1867. for (int i = 0; i < scoreTitleArr.length; i++) {
  1868. String style = (i + 1) <= score ? "fa-star" : "fa-star-o";
  1869. scoreSB.append("<li id=\"" + coinWatchlist.getSymbol() + "\" title=\"" + scoreTitleArr[i] + "\" val=\"" + (i + 1) + "\"><i class=\"fa " + style + "\"></i></li>");
  1870. }
  1871. scoreSB.append("</ul>");
  1872. coinWatchlist.setScore(scoreSB.toString());
  1873. // 流通市值
  1874. if (null != coinWatchlist.getTotalMarketValue()) {
  1875. BigDecimal divide = coinWatchlist.getTotalMarketValue().divide(bigDecimal10000, 8, RoundingMode.HALF_UP);
  1876. if (divide.compareTo(bigDecimal10000) <= 0) {
  1877. coinWatchlist.setTotalMarketValueStr(divide.setScale(2, RoundingMode.HALF_UP) + "万");
  1878. } else {
  1879. divide = divide.divide(bigDecimal10000, 2, RoundingMode.HALF_UP);
  1880. coinWatchlist.setTotalMarketValueStr(divide + "亿");
  1881. }
  1882. coinWatchlist.setTotalMarketValueStr("<span class=\"primary\" avid=\"" + coinWatchlist.getSymbol() + "\" >" + coinWatchlist.getTotalMarketValueStr() + " </span>");
  1883. }
  1884. // 赛道分类
  1885. String[] trackCategoryArr = coinWatchlist.getTrackCategory().split(",");
  1886. StringBuffer sb = new StringBuffer();
  1887. String trackCategoryLength = "";
  1888. for (int i = 0; i < trackCategoryArr.length; i++) {
  1889. if (i == trackCategoryArr.length - 1) {
  1890. if (trackCategoryArr.length > 1 && trackCategoryLength.length() > 10) {
  1891. sb.append("<br>");
  1892. }
  1893. sb.append("<span class=\"selected-value\" style=\"" + trackCategoryMap.get(trackCategoryArr[i]) + " \" >" + trackCategoryArr[i] + " </span>");
  1894. } else {
  1895. trackCategoryLength += trackCategoryArr[i];
  1896. sb.append("<span class=\"selected-value\" style=\"margin-right:0.3em;" + trackCategoryMap.get(trackCategoryArr[i]) + " \" >" + trackCategoryArr[i] + " </span>");
  1897. }
  1898. }
  1899. coinWatchlist.setTrackCategoryStyle(" style=\"padding:0em 0.3em;\"");
  1900. coinWatchlist.setTrackCategory(sb.toString());
  1901. String[] trackCategory2Arr = coinWatchlist.getTrackCategory2().split(",");
  1902. StringBuffer sb2 = new StringBuffer();
  1903. for (int i = 0; i < trackCategory2Arr.length; i++) {
  1904. sb2.append("<span class=\"selected-value\" style=\"margin-right:0.3em;" + trackCategory2Map.get(trackCategory2Arr[i]) + " \" >" + trackCategory2Arr[i] + " </span>");
  1905. if (i % 2 == 1 && i != trackCategory2Arr.length - 1) {
  1906. sb2.append("<br>");
  1907. }
  1908. }
  1909. coinWatchlist.setTrackCategory2Style(" style=\"padding:0em 0.3em;\"");
  1910. coinWatchlist.setTrackCategory2(sb2.toString());
  1911. // 名称
  1912. coinWatchlist.setSymbolStyle(" style=\"background-color:rgba(70,169,244,.72);font-weight: bold;\"");
  1913. // 市场价格
  1914. coinWatchlist.setMarkPriceStyle(" style=\"color:#252B31;background-color:#C4ADE9;\"");
  1915. if (StringUtils.isNotEmpty(coinWatchlist.getMarkPrice())) {
  1916. if (!coinWatchlist.getMarkPrice().matches(regex)) {
  1917. //coinWatchlist.setMarkPrice(new BigDecimal(coinWatchlist.getMarkPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString());
  1918. coinWatchlist.setMarkPrice(formatNumber(coinWatchlist.getMarkPrice()));
  1919. }
  1920. }
  1921. if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice())) {
  1922. if (!coinWatchlist.getHighestHistoricalPrice().matches(regex)) {
  1923. //coinWatchlist.setHighestHistoricalPrice(new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString());
  1924. coinWatchlist.setHighestHistoricalPrice(formatNumber(coinWatchlist.getHighestHistoricalPrice()));
  1925. }
  1926. }
  1927. if (StringUtils.isNotEmpty(coinWatchlist.getLowestHistoricalPrice())) {
  1928. if (!coinWatchlist.getLowestHistoricalPrice().matches(regex)) {
  1929. //coinWatchlist.setLowestHistoricalPrice(new BigDecimal(coinWatchlist.getLowestHistoricalPrice()).divide(BigDecimal.ONE, new MathContext(3)).toPlainString());
  1930. coinWatchlist.setLowestHistoricalPrice(formatNumber(coinWatchlist.getLowestHistoricalPrice()));
  1931. }
  1932. }
  1933. // 24小时价格变化
  1934. if (coinWatchlist.getPriceChangePercentage24h() == null) {
  1935. } else if (coinWatchlist.getPriceChangePercentage24h().compareTo(BigDecimal.ZERO) < 0) {
  1936. coinWatchlist.setPriceChangePercentage24hStyle(" style=\"color:#000000;background-color:#f1a8a4;\"");
  1937. } else {
  1938. coinWatchlist.setPriceChangePercentage24hStyle(" style=\"color:#000000;background-color:#aad6f5;\"");
  1939. }
  1940. /*// 历史最高点涨幅比例
  1941. if (coinWatchlist.getAthChangePercentage() == null) {
  1942. } else if (coinWatchlist.getAthChangePercentage().compareTo(BigDecimal.ZERO) < 0) {
  1943. coinWatchlist.setAthChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#F1493F;\"");
  1944. } else {
  1945. coinWatchlist.setAthChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  1946. }
  1947. // 历史最低点涨幅比例
  1948. if (coinWatchlist.getAtlChangePercentage() == null) {
  1949. } else if (coinWatchlist.getAtlChangePercentage().compareTo(BigDecimal.ZERO) < 0) {
  1950. coinWatchlist.setAtlChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#F1493F;\"");
  1951. } else {
  1952. coinWatchlist.setAtlChangePercentageStyle(" style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  1953. }*/
  1954. // 涨幅倍数2
  1955. if (StringUtils.isNotEmpty(coinWatchlist.getHighestHistoricalPrice()) && StringUtils.isNotEmpty(coinWatchlist.getMarkPrice())) {
  1956. BigDecimal increaseMultiple = new BigDecimal(coinWatchlist.getHighestHistoricalPrice()).divide(new BigDecimal(coinWatchlist.getMarkPrice()), 1, RoundingMode.HALF_UP);
  1957. coinWatchlist.setIncreaseMultiple2(increaseMultiple.toPlainString());
  1958. if (increaseMultiple.compareTo(new BigDecimal("50")) >= 0) {
  1959. coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#5a964e;\"");
  1960. } else if (increaseMultiple.compareTo(new BigDecimal("10")) >= 0) {
  1961. coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#9cc494;\"");
  1962. } else {
  1963. coinWatchlist.setIncreaseMultiple2Style(" style=\"color:#000000;background-color:#dae8d7;\"");
  1964. }
  1965. }
  1966. }
  1967. }
  1968. private static String formatNumber(String str) {
  1969. double number = Double.parseDouble(str);
  1970. int dotIndex = str.indexOf('.');
  1971. if (dotIndex == -1) {
  1972. return str; // 无小数点,直接返回
  1973. }
  1974. // 检查小数点后连续0的数量
  1975. int zeroCount = 0;
  1976. for (int i = dotIndex + 1; i < str.length(); i++) {
  1977. if (str.charAt(i) == '0') {
  1978. zeroCount++;
  1979. } else {
  1980. break; // 遇到非0字符停止计数
  1981. }
  1982. }
  1983. // 如果0的数量 >=5,用科学计数法;否则用普通小数
  1984. if (zeroCount >= 5) {
  1985. DecimalFormat df = new DecimalFormat("0.###E0"); // 科学计数法
  1986. return df.format(number).replace("E-0", "E-"); // 修正E-09为E-9
  1987. } else {
  1988. return new BigDecimal(str).divide(BigDecimal.ONE, new MathContext(3)).stripTrailingZeros().toPlainString();
  1989. }
  1990. }
  1991. /**
  1992. * 动态格式化数字:小数点后,从第一个非0数字开始,保留指定的有效位数
  1993. * * @param value 原始数值
  1994. * @param keepFraction 需要保留的非0有效小数位数 (如你的需求是 3)
  1995. * @return 格式化后的清爽字符串
  1996. */
  1997. public static String formatDynamicScale(BigDecimal value, int keepFraction) {
  1998. // 1. 判空防雷
  1999. if (value == null || value.compareTo(BigDecimal.ZERO) == 0) {
  2000. return "0";
  2001. }
  2002. // 2. 取绝对值去零并转纯文本,方便字符串分析 (负数不影响我们找小数点)
  2003. String plainStr = value.abs().stripTrailingZeros().toPlainString();
  2004. int dotIndex = plainStr.indexOf('.');
  2005. // 3. 如果没有小数点(纯整数),直接返回去零后的原始字符串
  2006. if (dotIndex == -1) {
  2007. return value.stripTrailingZeros().toPlainString();
  2008. }
  2009. // 4. 计算小数点后连续的 0 的个数
  2010. int leadingZeros = 0;
  2011. String fractionPart = plainStr.substring(dotIndex + 1);
  2012. for (int i = 0; i < fractionPart.length(); i++) {
  2013. if (fractionPart.charAt(i) == '0') {
  2014. leadingZeros++;
  2015. } else {
  2016. break; // 遇到第一个非 0 数字,停止计数
  2017. }
  2018. }
  2019. // 5. 核心逻辑:计算目标标度 (Target Scale)
  2020. // 例如:0.00000098333 -> 连续6个0,保留3位 -> 目标标度为 6 + 3 = 9
  2021. int targetScale = leadingZeros + keepFraction;
  2022. // 6. 截断保留指定精度
  2023. // 注意:这里强烈建议使用 RoundingMode.DOWN (向下取整/直接截断)
  2024. // 在金融/币圈,数量和金额通常宁愿截断,也不能四舍五入(HALF_UP),防止出现"超卖/超买"的资金盘差异
  2025. BigDecimal scaledValue = value.setScale(targetScale, RoundingMode.DOWN);
  2026. // 7. 最后做一次兜底去零。
  2027. // 防止出现例如截断后变成 139.990,去零后变成更清爽的 139.99
  2028. return scaledValue.stripTrailingZeros().toPlainString();
  2029. }
  2030. private void renderMainSearch4TraderList(List<CoinTrader> mixTraderList) {
  2031. for (CoinTrader mixTrader : mixTraderList) {
  2032. mixTrader.setLastTradeTime(DateUtils.longToString(Long.valueOf(mixTrader.getLastTradeTime())));
  2033. }
  2034. }
  2035. /**
  2036. * 渲染获取当前计划委托(止盈止损)列表
  2037. *
  2038. * @param result
  2039. */
  2040. private void renderMainSearch4CurrentPlan(JSONArray result, Integer chaRateSort) {
  2041. forkJoinPool3.submit(() -> result.parallelStream().forEach(e -> {
  2042. JSONObject jsonObject = (JSONObject) e;
  2043. // 币对名称
  2044. String symbol = jsonObject.getString("symbol");
  2045. jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol.replace("USDT_UMCBL", "") + "</font></strong>USDT_UMCBL");
  2046. // 订单状态
  2047. jsonObject.put("status", InitRunner.publicParamsMap.get("status").getString(jsonObject.getString("status")));
  2048. // 交易类型
  2049. jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType")));
  2050. // 订单类型
  2051. jsonObject.put("planType", InitRunner.publicParamsMap.get("planType").getString(jsonObject.getString("planType")));
  2052. // 开单方向
  2053. String side = jsonObject.getString("side");
  2054. jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side));
  2055. if (side.equals("open_long")) {
  2056. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2057. } else if (side.equals("open_short")) {
  2058. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2059. } else {
  2060. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\"");
  2061. }
  2062. // 触发类型
  2063. jsonObject.put("triggerType", InitRunner.publicParamsMap.get("triggerType").getString(jsonObject.getString("triggerType")));
  2064. jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime")));
  2065. jsonObject.put("uTime", StringUtils.isEmpty(jsonObject.getString("uTime")) ? "--" : DateUtils.longToString(jsonObject.getLong("uTime")));
  2066. // 获取合约标记价格
  2067. String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol;
  2068. try {
  2069. Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  2070. String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice");
  2071. BigDecimal chaRate = BigDecimal.ZERO;
  2072. BigDecimal triggerPriceDecimal = new BigDecimal(jsonObject.getString("triggerPrice"));
  2073. BigDecimal markPriceDecimal = new BigDecimal(markPrice);
  2074. if (markPriceDecimal.compareTo(triggerPriceDecimal) < 0) {
  2075. chaRate = markPriceDecimal.divide(triggerPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
  2076. } else if (markPriceDecimal.compareTo(triggerPriceDecimal) > 0) {
  2077. chaRate = triggerPriceDecimal.divide(markPriceDecimal, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
  2078. }
  2079. jsonObject.put("markPrice", markPrice);
  2080. jsonObject.put("markPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
  2081. jsonObject.put("chaRate", chaRate);
  2082. jsonObject.put("chaRateStyle", " style=\"color:#FFFFFF;background-color:#5EA294;\"");
  2083. } catch (Exception ex) {
  2084. throw new RuntimeException(ex);
  2085. }
  2086. })).join();
  2087. if (chaRateSort != 0) {
  2088. Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate"))));
  2089. }
  2090. }
  2091. /**
  2092. * 渲染监控币种列表
  2093. *
  2094. * @param monitorCurrencyList
  2095. */
  2096. private JSONArray renderMainSearch4MonitorCurrency(Map<String, JSONArray> resultMulti, List<CoinMonitorCurrency> monitorCurrencyList, Integer changeUtcSort) {
  2097. Map<String, CoinMonitorCurrency> monitorCurrencyMap4Mix = monitorCurrencyList.stream().filter(e -> e.getType().equals("2")).collect(Collectors.toMap(CoinMonitorCurrency::getSymbol, Function.identity(), (key1, key2) -> key1));
  2098. Set<String> symbolSet4Mix = monitorCurrencyMap4Mix.keySet();
  2099. Map<String, CoinMonitorCurrency> monitorCurrencyMap4Spot = monitorCurrencyList.stream().filter(e -> e.getType().contains("1")).collect(Collectors.toMap(CoinMonitorCurrency::getSymbol, Function.identity(), (key1, key2) -> key1));
  2100. Set<String> symbolSet4Spot = monitorCurrencyMap4Spot.keySet();
  2101. JSONArray array4Spot = resultMulti.get("/api/spot/v1/market/tickers").stream()
  2102. .filter(iter -> symbolSet4Spot.contains(((JSONObject) iter).getString("symbol")))
  2103. .collect(Collectors.toCollection(JSONArray::new));
  2104. JSONArray array4Mix = resultMulti.get("/api/mix/v1/market/tickers?productType=umcbl").stream()
  2105. .filter(iter -> symbolSet4Mix.contains(((JSONObject) iter).getString("symbol")))
  2106. .collect(Collectors.toCollection(JSONArray::new));
  2107. forkJoinPool.submit(() -> array4Spot.parallelStream().forEach(e -> {
  2108. JSONObject jsonObject = (JSONObject) e;
  2109. jsonObject.put("changeUtc", jsonObject.getBigDecimal("changeUtc").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
  2110. jsonObject.put("change", jsonObject.getBigDecimal("change").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
  2111. jsonObject.put("ts", DateUtils.longToString(jsonObject.getLong("ts")));
  2112. jsonObject.put("category", monitorCurrencyMap4Spot.get(jsonObject.getString("symbol")).getCategory());
  2113. // UTC0时涨跌幅
  2114. if (jsonObject.getBigDecimal("changeUtc").compareTo(BigDecimal.ZERO) < 0) {
  2115. jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2116. } else {
  2117. jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2118. }
  2119. // 24小时涨跌幅
  2120. if (jsonObject.getBigDecimal("change").compareTo(BigDecimal.ZERO) < 0) {
  2121. jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2122. } else {
  2123. jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2124. }
  2125. // 币对名称
  2126. String symbol = jsonObject.getString("symbol").replace("USDT", "");
  2127. if ("BTC".equals(symbol) || "ETH".equals(symbol)) {
  2128. jsonObject.put("symbol", "<strong style=\"background-color:#FF6EB4;\"><font color=\"#242A30\">" + symbol + "</font></strong>USDT");
  2129. } else {
  2130. jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol + "</font></strong>USDT");
  2131. }
  2132. // 标记价格
  2133. jsonObject.put("closeStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
  2134. // 基础币量 计价币量 usdt币量
  2135. jsonObject.put("baseVol", readableFileSize(jsonObject.getDouble("baseVol")));
  2136. jsonObject.put("quoteVol", readableFileSize(jsonObject.getDouble("quoteVol")));
  2137. jsonObject.put("usdtVol", readableFileSize(jsonObject.getDouble("usdtVol")));
  2138. })).join();
  2139. forkJoinPool.submit(() -> array4Mix.parallelStream().forEach(e -> {
  2140. JSONObject jsonObject = (JSONObject) e;
  2141. jsonObject.put("changeUtc", jsonObject.getBigDecimal("chgUtc").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
  2142. jsonObject.put("change", jsonObject.getBigDecimal("priceChangePercent").multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
  2143. jsonObject.put("ts", DateUtils.longToString(jsonObject.getLong("timestamp")));
  2144. jsonObject.put("category", monitorCurrencyMap4Mix.get(jsonObject.getString("symbol")).getCategory());
  2145. // UTC0时涨跌幅
  2146. if (jsonObject.getBigDecimal("changeUtc").compareTo(BigDecimal.ZERO) < 0) {
  2147. jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2148. } else {
  2149. jsonObject.put("changeUtcStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2150. }
  2151. // 24小时涨跌幅
  2152. if (jsonObject.getBigDecimal("change").compareTo(BigDecimal.ZERO) < 0) {
  2153. jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2154. } else {
  2155. jsonObject.put("changeStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2156. }
  2157. // 币对名称
  2158. String symbol = jsonObject.getString("symbol").replace("USDT_UMCBL", "");
  2159. jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol + "</font></strong>USDT_UMCBL");
  2160. // 标记价格
  2161. jsonObject.put("close", jsonObject.getString("last"));
  2162. jsonObject.put("closeStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
  2163. // 基础币量 计价币量 usdt币量
  2164. jsonObject.put("baseVol", readableFileSize(jsonObject.getDouble("baseVolume")));
  2165. jsonObject.put("quoteVol", readableFileSize(jsonObject.getDouble("quoteVolume")));
  2166. jsonObject.put("usdtVol", readableFileSize(jsonObject.getDouble("quoteVolume")));
  2167. // 其他字段兼容
  2168. jsonObject.put("openUtc0", jsonObject.getString("openUtc"));
  2169. jsonObject.put("buyOne", jsonObject.getString("bestBid"));
  2170. jsonObject.put("sellOne", jsonObject.getString("bestAsk"));
  2171. })).join();
  2172. array4Spot.addAll(array4Mix);
  2173. if (changeUtcSort != 0) {
  2174. Collections.sort(array4Spot, (o1, o2) -> changeUtcSort * (((JSONObject) o1).getBigDecimal("changeUtc").compareTo(((JSONObject) o2).getBigDecimal("changeUtc"))));
  2175. }
  2176. return array4Spot;
  2177. }
  2178. /**
  2179. * 渲染获取全部历史委托
  2180. *
  2181. * @param historyOrderList
  2182. */
  2183. private void renderMainSearch4OrderHistoryProductType(List<CoinHistoryOrder> historyOrderList) {
  2184. for (CoinHistoryOrder coinHistoryOrder : historyOrderList) {
  2185. // 币种名称
  2186. coinHistoryOrder.setSymbol(coinHistoryOrder.getSymbol().replace("USDT_UMCBL", ""));
  2187. // 订单状态
  2188. coinHistoryOrder.setState(InitRunner.publicParamsMap.get("state").getString(coinHistoryOrder.getState()));
  2189. // 开单方向
  2190. coinHistoryOrder.setSide(InitRunner.publicParamsMap.get("side").getString(coinHistoryOrder.getSide()));
  2191. // 总盈亏
  2192. String TotalProfits = "0E-8";
  2193. if (!coinHistoryOrder.getTotalProfits().contains("0E-8")) {
  2194. TotalProfits = new BigDecimal(coinHistoryOrder.getTotalProfits()).setScale(2, RoundingMode.HALF_UP).toPlainString();
  2195. }
  2196. coinHistoryOrder.setTotalProfits(TotalProfits);
  2197. // 手续费
  2198. String fee = "0E-8";
  2199. if (!coinHistoryOrder.getFee().contains("0E-8")) {
  2200. fee = new BigDecimal(coinHistoryOrder.getFee()).setScale(2, RoundingMode.HALF_UP).toPlainString();
  2201. }
  2202. coinHistoryOrder.setFee(fee);
  2203. // 持仓方向
  2204. coinHistoryOrder.setPosSide(InitRunner.publicParamsMap.get("posSide").getString(coinHistoryOrder.getPosSide()));
  2205. // 仓位模式
  2206. coinHistoryOrder.setMarginMode(InitRunner.publicParamsMap.get("marginMode").getString(coinHistoryOrder.getMarginMode()));
  2207. // 交易类型
  2208. coinHistoryOrder.setOrderType(InitRunner.publicParamsMap.get("orderType").getString(coinHistoryOrder.getOrderType()));
  2209. // 交易方向
  2210. coinHistoryOrder.setTradeSide(InitRunner.publicParamsMap.get("tradeSide").getString(coinHistoryOrder.getTradeSide()));
  2211. // 持仓模式
  2212. coinHistoryOrder.setHoldMode(InitRunner.publicParamsMap.get("holdMode").getString(coinHistoryOrder.getHoldMode()));
  2213. // orderSource
  2214. coinHistoryOrder.setOrderSource(InitRunner.publicParamsMap.get("orderSource").getString(coinHistoryOrder.getOrderSource()));
  2215. coinHistoryOrder.setCTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getCTime())));
  2216. coinHistoryOrder.setUTime(DateUtils.longToString(Long.valueOf(coinHistoryOrder.getUTime())));
  2217. }
  2218. }
  2219. /**
  2220. * 渲染获取全部历史委托-现货
  2221. *
  2222. * @param spotOrdersList
  2223. */
  2224. private void renderMainSearch4OrderHistorySpotProductType(List<CoinGateSpotOrders> gateSpotOrdersList, List<CoinSpotOrders> spotOrdersList) {
  2225. for (CoinGateSpotOrders coinGateSpotOrders : gateSpotOrdersList) {
  2226. CoinSpotOrders coinSpotOrders = new CoinSpotOrders();
  2227. BeanUtils.copyProperties(coinGateSpotOrders, coinSpotOrders);
  2228. // 币种名称
  2229. coinSpotOrders.setCurrencyPair(coinGateSpotOrders.getCurrencyPair().replace("_USDT", ""));
  2230. // 订单状态
  2231. coinSpotOrders.setStatus(InitRunner.publicParamsMap.get("gateStatus").getString(coinGateSpotOrders.getStatus()));
  2232. if ("cancelled".equals(coinGateSpotOrders.getStatus()) && coinGateSpotOrders.getFilledTotal().compareTo(BigDecimal.ZERO) > 0) {
  2233. coinSpotOrders.setStatus("已结束的订单");
  2234. }
  2235. // 结束状态
  2236. coinSpotOrders.setFinishAs(InitRunner.publicParamsMap.get("gatefinishAs").getString(coinGateSpotOrders.getFinishAs()));
  2237. if ("cancelled".equals(coinGateSpotOrders.getFinishAs()) && coinGateSpotOrders.getFilledTotal().compareTo(BigDecimal.ZERO) > 0) {
  2238. coinSpotOrders.setFinishAs("部分成交");
  2239. }
  2240. // 交易方向
  2241. coinSpotOrders.setSide(InitRunner.publicParamsMap.get("spotSide").getString(coinGateSpotOrders.getSide()));
  2242. // 订单类型
  2243. coinSpotOrders.setType(InitRunner.publicParamsMap.get("orderType").getString(coinGateSpotOrders.getType()));
  2244. // 委托数量,委托价格,已成交数量,成交均价,成交总金额,手续费
  2245. coinSpotOrders.setAmount(formatDynamicScale(coinGateSpotOrders.getAmount(), 3));
  2246. coinSpotOrders.setPrice(formatDynamicScale(coinGateSpotOrders.getPrice(), 3));
  2247. coinSpotOrders.setFilledAmount(formatDynamicScale(coinGateSpotOrders.getFilledAmount(), 3));
  2248. coinSpotOrders.setAvgDealPrice(formatDynamicScale(coinGateSpotOrders.getAvgDealPrice(), 3));
  2249. coinSpotOrders.setFilledTotal(formatDynamicScale(coinGateSpotOrders.getFilledTotal(), 3));
  2250. coinSpotOrders.setFee(formatDynamicScale(coinGateSpotOrders.getFee(), 3));
  2251. coinSpotOrders.setCreateTimeMs(DateUtils.longToString(coinGateSpotOrders.getCreateTimeMs()));
  2252. coinSpotOrders.setUpdateTimeMs(DateUtils.longToString(coinGateSpotOrders.getUpdateTimeMs()));
  2253. spotOrdersList.add(coinSpotOrders);
  2254. }
  2255. }
  2256. /**
  2257. * 渲染获取全部当前委托
  2258. *
  2259. * @param result
  2260. */
  2261. private void renderMainSearch4OrderMarginCoinCurrent(JSONArray result, Integer chaRateSort) {
  2262. forkJoinPool2.submit(() -> result.parallelStream().forEach(e -> {
  2263. JSONObject jsonObject = (JSONObject) e;
  2264. // 币对名称
  2265. String symbol = jsonObject.getString("symbol");
  2266. jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol.replace("USDT_UMCBL", "") + "</font></strong>USDT_UMCBL");
  2267. // 订单状态
  2268. jsonObject.put("state", InitRunner.publicParamsMap.get("state").getString(jsonObject.getString("state")));
  2269. // 开单方向
  2270. String side = jsonObject.getString("side");
  2271. jsonObject.put("side", InitRunner.publicParamsMap.get("side").getString(side));
  2272. if (side.equals("open_long")) {
  2273. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2274. } else if (side.equals("open_short")) {
  2275. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2276. } else {
  2277. jsonObject.put("sideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\"");
  2278. }
  2279. // 交易类型
  2280. jsonObject.put("orderType", InitRunner.publicParamsMap.get("orderType").getString(jsonObject.getString("orderType")));
  2281. // 止盈止损
  2282. jsonObject.put("presetTakeProfitPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice"));
  2283. jsonObject.put("presetStopLossPrice", StringUtils.isEmpty(jsonObject.getString("presetTakeProfitPrice")) ? "--" : jsonObject.getString("presetTakeProfitPrice"));
  2284. // 持仓模式
  2285. jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode")));
  2286. // orderSource
  2287. jsonObject.put("orderSource", InitRunner.publicParamsMap.get("orderSource").getString(jsonObject.getString("orderSource")));
  2288. // 仓位模式
  2289. jsonObject.put("marginMode", InitRunner.publicParamsMap.get("marginMode").getString(jsonObject.getString("marginMode")));
  2290. jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime")));
  2291. jsonObject.put("uTime", DateUtils.longToString(jsonObject.getLong("uTime")));
  2292. // 获取合约标记价格
  2293. String requestUrl = mainUrl + "/api/mix/v1/market/mark-price?symbol=" + symbol;
  2294. try {
  2295. Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  2296. String markPrice = JSONObject.parseObject(response.body()).getJSONObject("data").getString("markPrice");
  2297. BigDecimal chaRate = BigDecimal.ZERO;
  2298. if ("open_short".equals(side)) {
  2299. chaRate = new BigDecimal(markPrice).divide(new BigDecimal(jsonObject.getString("price")), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
  2300. } else if ("open_long".equals(side)) {
  2301. chaRate = new BigDecimal(jsonObject.getString("price")).divide(new BigDecimal(markPrice), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
  2302. }
  2303. jsonObject.put("markPrice", markPrice);
  2304. jsonObject.put("markPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
  2305. jsonObject.put("chaRate", chaRate);
  2306. jsonObject.put("chaRateStyle", " style=\"color:#FFFFFF;background-color:#5EA294;\"");
  2307. } catch (Exception ex) {
  2308. throw new RuntimeException(ex);
  2309. }
  2310. })).join();
  2311. if (chaRateSort != 0) {
  2312. Collections.sort(result, (o1, o2) -> chaRateSort * (((JSONObject) o1).getBigDecimal("chaRate").compareTo(((JSONObject) o2).getBigDecimal("chaRate"))));
  2313. }
  2314. }
  2315. /**
  2316. * 渲染获取全部合约仓位信息V2
  2317. *
  2318. * @param result
  2319. */
  2320. private void renderMainSearch4AllPositionv2(JSONArray result, Integer unrealizedPLSort) {
  2321. forkJoinPool4.submit(() -> result.parallelStream().forEach(e -> {
  2322. JSONObject jsonObject = (JSONObject) e;
  2323. // 币对名称
  2324. String symbol = jsonObject.getString("symbol");
  2325. jsonObject.put("symbol", "<strong style=\"background-color:#F1B90d;\"><font color=\"#242A30\">" + symbol.replace("USDT_UMCBL", "") + "</font></strong>USDT_UMCBL");
  2326. // 持仓方向
  2327. String holdSide = jsonObject.getString("holdSide");
  2328. jsonObject.put("holdSide", InitRunner.publicParamsMap.get("holdSide").getString(holdSide));
  2329. if (holdSide.equals("long")) {
  2330. jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2331. } else if (holdSide.equals("short")) {
  2332. jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2333. } else {
  2334. jsonObject.put("holdSideStyle", " style=\"color:#FFFFFF;background-color:#F0F0F0;\"");
  2335. }
  2336. // 保证金模式
  2337. jsonObject.put("marginMode", InitRunner.publicParamsMap.get("marginMode").getString(jsonObject.getString("marginMode")));
  2338. // 持仓模式
  2339. jsonObject.put("holdMode", InitRunner.publicParamsMap.get("holdMode").getString(jsonObject.getString("holdMode")));
  2340. // 最近更新时间 保证金数量 (保证金币种) 平均开仓价 未实现盈亏 预估强平价
  2341. jsonObject.put("cTime", DateUtils.longToString(jsonObject.getLong("cTime")));
  2342. jsonObject.put("margin", new BigDecimal(jsonObject.getString("margin")).setScale(4, RoundingMode.HALF_UP));
  2343. jsonObject.put("averageOpenPrice", new BigDecimal(jsonObject.getString("averageOpenPrice")).divide(BigDecimal.ONE, new MathContext(4)));
  2344. jsonObject.put("unrealizedPL", new BigDecimal(jsonObject.getString("unrealizedPL")).setScale(4, RoundingMode.HALF_UP));
  2345. jsonObject.put("liquidationPrice", new BigDecimal(jsonObject.getString("liquidationPrice")).divide(BigDecimal.ONE, new MathContext(4)));
  2346. // 未实现盈亏
  2347. if (jsonObject.getBigDecimal("unrealizedPL").compareTo(BigDecimal.ZERO) < 0) {
  2348. jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2349. } else {
  2350. jsonObject.put("unrealizedPLStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2351. }
  2352. // 回报率=未实现盈亏/保证金
  2353. BigDecimal returnRate = jsonObject.getBigDecimal("unrealizedPL").divide(jsonObject.getBigDecimal("margin"), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
  2354. jsonObject.put("returnRate", returnRate);
  2355. if (returnRate.compareTo(BigDecimal.ZERO) < 0) {
  2356. jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2357. } else {
  2358. jsonObject.put("returnRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2359. }
  2360. // 获取当前资金费率
  2361. String requestUrl = mainUrl + "/api/mix/v1/market/current-fundRate?symbol=" + symbol;
  2362. try {
  2363. Connection.Response response = JsoupUtil.requestBody(requestUrl, JsoupUtil.HTTP_GET, InitRunner.proxy, null, null);
  2364. String fundingRate = JSONObject.parseObject(response.body()).getJSONObject("data").getString("fundingRate");
  2365. if (new BigDecimal(fundingRate).compareTo(BigDecimal.ZERO) < 0) {
  2366. jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#F1493F;\"");
  2367. } else {
  2368. jsonObject.put("fundingRateStyle", " style=\"color:#FFFFFF;background-color:#1DA2B4;\"");
  2369. }
  2370. jsonObject.put("fundingRate", new BigDecimal(fundingRate).multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP).toPlainString() + "%");
  2371. } catch (Exception ex) {
  2372. throw new RuntimeException(ex);
  2373. }
  2374. // 标记价格
  2375. jsonObject.put("marketPriceStyle", " style=\"color:#252B31;background-color:#C4ADE9;font-weight:bold;\"");
  2376. })).join();
  2377. if (unrealizedPLSort != 0) {
  2378. Collections.sort(result, (o1, o2) -> unrealizedPLSort * (((JSONObject) o1).getBigDecimal("unrealizedPL").compareTo(((JSONObject) o2).getBigDecimal("unrealizedPL"))));
  2379. }
  2380. }
  2381. /**
  2382. * Java实现字节转换,可以自动转换为B、KB、MB、GB、TB
  2383. *
  2384. * @param size
  2385. * @return
  2386. */
  2387. private String readableFileSize(double size) {
  2388. if (size <= 0) {
  2389. return "0";
  2390. }
  2391. final String[] units = new String[]{"B", "K", "M", "G", "T"};
  2392. int digitGroups = (int) (Math.log10(size) / Math.log10(1000));
  2393. return df1.format(size / Math.pow(1000, digitGroups)) + units[digitGroups];
  2394. }
  2395. public String getMonitorJobStatus(String jobName) {
  2396. Map<String, JSONObject> monitorJobConfig = getMonitorJobConfig();
  2397. if (null != monitorJobConfig && monitorJobConfig.containsKey(jobName)) {
  2398. return monitorJobConfig.get(jobName).getString("job_status");
  2399. }
  2400. return null;
  2401. }
  2402. @Override
  2403. public Map<String, JSONObject> getMonitorJobConfig() {
  2404. String cacheKey = "coin:monitor:job:list";
  2405. // 1. 缓存有,直接返回
  2406. if (redisUtils.hasKey(cacheKey) && redisUtils.get(cacheKey) != null) {
  2407. return (Map<String, JSONObject>) redisUtils.get(cacheKey);
  2408. }
  2409. // 加锁防止同时对一个数据发送多次请求
  2410. RLock lock = redissonClient.getLock("lock:" + cacheKey);
  2411. try {
  2412. // 2. 尝试加分布式锁,最多等待30秒,上锁以后60秒自动解锁
  2413. boolean lockFlag = lock.tryLock(30, 60, TimeUnit.SECONDS);
  2414. if (lockFlag) {
  2415. // 3. 加锁成功,二次检查,缓存有,直接返回
  2416. if (redisUtils.hasKey(cacheKey) && redisUtils.get(cacheKey) != null) {
  2417. return (Map<String, JSONObject>) redisUtils.get(cacheKey);
  2418. }
  2419. // 4. 查数据库,并且按给定的时长加到缓存中
  2420. Map<String, JSONObject> monitorJobConfigMap = coinMapper.findMonitorJobConfig();
  2421. // 缓存监控任务配置信息
  2422. if (monitorJobConfigMap != null) {
  2423. redisUtils.set(cacheKey, monitorJobConfigMap, 60, TimeUnit.MINUTES);
  2424. }
  2425. return monitorJobConfigMap;
  2426. } else {
  2427. log.error("getMonitorJobConfig 加锁失败 error,lockFlag: false");
  2428. return null;
  2429. }
  2430. } catch (Exception e) {
  2431. log.error("getMonitorJobConfig Exception", e);
  2432. return InitRunner.monitorJobConfigMap;
  2433. } finally {
  2434. if (lock.isLocked() && lock.isHeldByCurrentThread()) {
  2435. lock.unlock();
  2436. }
  2437. }
  2438. }
  2439. @Override
  2440. public Object mainSearchDetail(String userName, String nameEn, String id) {
  2441. if ("image".equals(nameEn)) {
  2442. FileImage fileImage = pictureInfoMapper.findFileImageById(Long.valueOf(id));
  2443. String ftpBaseurl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue();
  2444. String ftpBasePath = InitRunner.dicCodeMap.get("ftp_basepath").getCodeValue();
  2445. String ftpThumbnailBasePath = InitRunner.dicCodeMap.get("ftp_thumbnail_basepath").getCodeValue();
  2446. String path = fileImage.getPath();
  2447. fileImage.setPath(ftpBaseurl + ftpBasePath + path);
  2448. fileImage.setThumbnailPath(ftpBaseurl + ftpThumbnailBasePath + path);
  2449. return fileImage;
  2450. } else if ("watchlist".equals(nameEn)) {
  2451. Integer userId;
  2452. if (StringUtils.isEmpty(userName)) {
  2453. throw new ParameterException("userName为空!");
  2454. } else {
  2455. JSONObject coinUser = coinMapper.findUserByUsername(userName);
  2456. if (coinUser == null) {
  2457. throw new ParameterException("用户不存在!");
  2458. }
  2459. userId = coinUser.getInteger("id");
  2460. }
  2461. CoinWatchlist coinWatchlist = coinMapper.findWatchlistUserBySymbolAndUserId(id, userId);
  2462. String watchlistOtherStr = parseWatchlistOther(id);
  2463. coinWatchlist.setRemark(watchlistOtherStr + MarkdownToHtmlUtils.markdownToHtmlExtensions(coinWatchlist.getRemark()));
  2464. coinWatchlist.setFeixiaohaoUrl("https://www.feixiaohao.com/currencies/" + coinWatchlist.getFeixiaohaoUrl());
  2465. coinWatchlist.setCmcUrl("https://coinmarketcap.com/zh/currencies/" + coinWatchlist.getCmcUrl());
  2466. coinWatchlist.setCoingeckoUrl("https://www.coingecko.com/zh/%E6%95%B0%E5%AD%97%E8%B4%A7%E5%B8%81/" + coinWatchlist.getCoingeckoUrl());
  2467. return coinWatchlist;
  2468. }
  2469. return null;
  2470. }
  2471. private String parseWatchlistOther(String id) {
  2472. CoinWatchlistOther coinWatchlistOther = coinMapper.findWatchlistOtherBySymbol(id);
  2473. StringBuffer sb = new StringBuffer();
  2474. sb.append("<table border=\"1\" cellspacing=\"0\">");
  2475. sb.append("<tr><td>").append("流通市值").append("</td>").append("<td>").append(convertHumanReadable(coinWatchlistOther.getMarketCap())).append("</td></tr>");
  2476. sb.append("<tr><td>").append("流通供应量").append("</td>").append("<td>").append(convertHumanReadable(coinWatchlistOther.getCirculatingSupply())).append("</td></tr>");
  2477. sb.append("<tr><td>").append("流通率").append("</td>").append("<td>").append(StringUtils.isNotEmpty(coinWatchlistOther.getCirculatingRate()) ? new BigDecimal(coinWatchlistOther.getCirculatingRate()).setScale(1, RoundingMode.HALF_UP) + "%" : "--").append("</td></tr>");
  2478. sb.append("<tr><td>").append("总供应量").append("</td>").append("<td>").append(convertHumanReadable(coinWatchlistOther.getTotalSupply())).append("</td></tr>");
  2479. sb.append("<tr><td>").append("最大供应量").append("</td>").append("<td>").append(convertHumanReadable(coinWatchlistOther.getMaxSupply())).append("</td></tr>");
  2480. sb.append("<tr><td>").append("完全稀释的市值").append("</td>").append("<td>").append(convertHumanReadable(coinWatchlistOther.getFullyDilutedMarketCap())).append("</td></tr>");
  2481. String[] coingeckoExchangeNameArr = InitRunner.dicCodeMap.get("coingecko_exchange_names").getCodeValue().split(",");
  2482. List<String> coingeckoExchangeNameList = new ArrayList<>();
  2483. if (StringUtils.isNotEmpty(coinWatchlistOther.getCexSpot()) && coingeckoExchangeNameArr.length == coinWatchlistOther.getCexSpot().length()) {
  2484. for (int i = 0; i < coingeckoExchangeNameArr.length; i++) {
  2485. if ("1".equals(String.valueOf(coinWatchlistOther.getCexSpot().charAt(i)))) {
  2486. coingeckoExchangeNameList.add(coingeckoExchangeNameArr[i]);
  2487. }
  2488. }
  2489. }
  2490. sb.append("<tr><td>").append("现货").append("</td>").append("<td>").append(coingeckoExchangeNameList.size() > 0 ? String.join("、", coingeckoExchangeNameList) : "--").append("</td></tr>");
  2491. String[] coingeckoExchangeFuturesNameArr = InitRunner.dicCodeMap.get("coingecko_exchange_futures_names").getCodeValue().split(",");
  2492. List<String> coingeckoExchangeFuturesNameList = new ArrayList<>();
  2493. if (StringUtils.isNotEmpty(coinWatchlistOther.getCexPerpetual()) && coingeckoExchangeFuturesNameArr.length == coinWatchlistOther.getCexPerpetual().length()) {
  2494. for (int i = 0; i < coingeckoExchangeFuturesNameArr.length; i++) {
  2495. if ("1".equals(String.valueOf(coinWatchlistOther.getCexPerpetual().charAt(i)))) {
  2496. coingeckoExchangeFuturesNameList.add(coingeckoExchangeFuturesNameArr[i]);
  2497. }
  2498. }
  2499. }
  2500. sb.append("<tr><td>").append("合约").append("</td>").append("<td>").append(coingeckoExchangeFuturesNameList.size() > 0 ? String.join("、", coingeckoExchangeFuturesNameList) : "--").append("</td></tr>");
  2501. sb.append("</table>");
  2502. return sb.toString();
  2503. }
  2504. @Override
  2505. public void debugTest() {
  2506. Map<String, Object> params = new HashMap<>();
  2507. params.put("sortField", Collections.singletonList("create_time"));
  2508. params.put("sort", "desc");
  2509. ArrayList symbolList = new ArrayList();
  2510. symbolList.add("PYTH");
  2511. symbolList.add("SNX");
  2512. symbolList.add("REEF");
  2513. //List<CoinWatchlist> watchlistList = coinMapper.findWatchlistList(params);
  2514. // watchlistList = watchlistList.stream().filter(x -> symbolList.contains(x.getSymbol())).collect(Collectors.toList());
  2515. //Map<String, CoinWatchlist> coinWatchlistMap4CoingeckoId = watchlistList.stream().collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist));
  2516. //parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId);
  2517. //Map<Long, CoinWatchlist> coinWatchlistMap4CmcId = watchlistList.stream().collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist));
  2518. // parseWatchlistMap4CmC(coinWatchlistMap4CmcId);
  2519. syncCexFlag("PONKE");
  2520. }
  2521. @Override
  2522. @Async("coinTaskExecutor")
  2523. public void initWatchlist(CoinWatchlist coinWatchlist2) {
  2524. Map<String, Object> params = new HashMap<>();
  2525. params.put("symbol", coinWatchlist2.getSymbol());
  2526. params.put("sortField", Collections.singletonList("create_time"));
  2527. params.put("sort", "desc");
  2528. List<CoinWatchlist> watchlistListCKO = coinMapper.findWatchlistList(params);
  2529. Map<String, CoinWatchlist> coinWatchlistMap4CoingeckoId = watchlistListCKO.stream().collect(Collectors.toMap(CoinWatchlist::getCoingeckoId, coinWatchlist -> coinWatchlist));
  2530. parseWatchlistMap4Coingecko(coinWatchlistMap4CoingeckoId);
  2531. List<CoinWatchlist> watchlistListCMC = coinMapper.findWatchlistList(params);
  2532. Map<Long, CoinWatchlist> coinWatchlistMap4CmcId = watchlistListCMC.stream().collect(Collectors.toMap(CoinWatchlist::getCmcId, coinWatchlist -> coinWatchlist));
  2533. parseWatchlistMap4CmC(coinWatchlistMap4CmcId);
  2534. // 初始化cexflag信息
  2535. syncCexFlag(coinWatchlist2.getSymbol());
  2536. }
  2537. public String convertHumanReadable(String number) {
  2538. if (StringUtils.isEmpty(number)) {
  2539. return "--";
  2540. }
  2541. BigDecimal bigDecimal10000 = new BigDecimal("10000");
  2542. BigDecimal divide = new BigDecimal(number).divide(bigDecimal10000, 8, RoundingMode.HALF_UP);
  2543. if (divide.compareTo(bigDecimal10000) <= 0) {
  2544. return divide.setScale(2, RoundingMode.HALF_UP) + "万";
  2545. } else {
  2546. return divide.divide(bigDecimal10000, 2, RoundingMode.HALF_UP) + "亿";
  2547. }
  2548. }
  2549. }