springboot对接OK交易所实现发送钉钉通知

思路:

实现的效果,在后台配置虚拟货币,然后当它的涨幅度超过设定的值。然后发送钉钉通知

实现的思路:

技术选型:mysql+redis+rabbitmq+定时任务+代理服务器
mysql:负责存储虚拟货币配置类

redis:数据不落库,然后放到redis中,比如存当前时间戳,涨幅度,上一次价格

rabbitmq:异步发送钉钉通知

代理服务器:因为ok交易所需要科学上网

第一步:看配置先利用mysql配置虚拟货币配置,可以直接拿若依的

第二步:首先拿到所有配置类,然后判定redis中是否有数据,我们放的是一个redis的hashMap

第一次没有数据,然后调用ok的api然后进行请求,然后对应的价格,然后把时间戳,价 格,涨幅度放到redis中,第二次在执行的时候如果redis中有数据,然后这时候拿到应 的数据,用原来的价格-现价除以原价能知道他是涨还是跌,然后并且判定一下是否大于 涨幅达,如果大于涨幅度,则发送钉钉通知。

java 复制代码
package com.ruoyi.system.corn;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.IsoConstants;
import com.ruoyi.common.mq.IsoMqConstant;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.system.domain.SysIsoIncreaseConfig;
import com.ruoyi.system.mq.vo.IsoMqDTO;
import com.ruoyi.system.service.ISysIsoIncreaseConfigService;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author hu
 * @desc 虚拟货币定时任务
 */

@RestController
@AllArgsConstructor
@RequestMapping("/aaa")
public class SysIsoConfigCorn {
    private final ISysIsoIncreaseConfigService sysIsoIncreaseConfigService;
    /**
     * 代理服务器ip
     */
    private static final String PROXY_HOST = "127.0.0.1";
    /**
     * 代理服务端口
     */
    private static final Integer PROXY_PORT = 7890;


    /**
     * redis
     */
    private final RedisTemplate redisTemplate;
    /**
     * rabbitmq
     */
    private final RabbitTemplate rabbitTemplate;

    /**
     *
     */
    @SneakyThrows
    @GetMapping("/aaaa")
    public String corn() {
        // 第一步直接加锁,我们会使用redis的分布式锁。然后符合条件发送给mq,有时候mq处理慢。所以不希望重复发送

        // 第一步拿到传递过来的数据,虚拟货币配置表里面的数据
        List<SysIsoIncreaseConfig> list = sysIsoIncreaseConfigService.list();
        // 然后循环遍历
        for (int i = 0; i < list.size(); i++) {
            SysIsoIncreaseConfig sysIsoIncreaseConfig = list.get(i);
            // 第一步先拿到它的涨幅时间限制
            // 第一步从redis中拿到它的数据,判定是否存在是一个map数据里面
            Boolean hasKey = redisTemplate.opsForHash().hasKey(IsoConstants.ISO_REDIS, IsoConstants.ISO_MAP + sysIsoIncreaseConfig.getIsoCode());
            // 判定key是否存在,如果不存在说明是第一次,然后这时候先进行拼接数据,然后再rabbitmq发送消息,然后不发送。在此执行一次就可以发送了
            // 然后创建一个map
            // 说明在范围内,然后这时候在调用ok交易所接口,拿到价格
            // 然后用上一次价格减去当前价格/上一次价格,然后判定是否大于0,如果大于0说明跌了,如果小于0说明涨了,然后进行对比
            // 如果涨幅度在范围内则发送消息
            Long timeDate = null;
            BigDecimal price = null;
            String rise = null;
            // ture 代表涨了,false 代表跌了
            boolean status = true;
            if (hasKey) {
                JSONObject jsonMap = (JSONObject) redisTemplate.opsForHash().get(IsoConstants.ISO_REDIS, IsoConstants.ISO_MAP + sysIsoIncreaseConfig.getIsoCode());
                // 拿到它的时间戳
                timeDate = jsonMap.getLong(IsoConstants.ISO_LAST_TIME);
                // 价格
                price = jsonMap.getBigDecimal(IsoConstants.ISO_PRICE);
                // 幅度
                rise = jsonMap.getString(IsoConstants.ISO_RISE);
            }
            Map<String, String> map = new HashMap<>(list.size());
            map.put(IsoConstants.INST_ID, sysIsoIncreaseConfig.getIsoCode() + IsoConstants.ISO_CODE);
            String json = HttpUtils.doGet(IsoConstants.OK_URL, map, PROXY_HOST, PROXY_PORT);
            // 然后拿到ok交易所的数据。然后转换为json,
            JSONObject jsonObject = JSONObject.parseObject(json);
            // 然后这时候拿到code,data。如果code为0,并且data不是空的,然后这时候可以拿到数据
            if (jsonObject.getString(IsoConstants.OK_CODE).equals("0")) {
                // 然后拿到里面的数据
                JSONArray jsonArray = jsonObject.getJSONArray(IsoConstants.OK_DATA);
                JSONObject jsonObjectInfo = jsonArray.getJSONObject(0);
                // 24小时成交量
                BigDecimal vol24h = jsonObjectInfo.getBigDecimal(IsoConstants.OK_VOLUME_24H);
                // 24小时成交金额
                BigDecimal vol24hCny = jsonObjectInfo.getBigDecimal(IsoConstants.OK_VOLUME_24H_CNY);
                // 格式有很多,然后拿到价格
                BigDecimal last = jsonObjectInfo.getBigDecimal(IsoConstants.OK_LAST);
                // 然后用上一次价格减去当前价格,如果价格是空的,这时候直接往redis里面赋值
                if (StringUtils.isNotNull(price)) {
                    // 用bigDecimal类型来计算 ,四舍五入保留2为小数
                    BigDecimal divide = price.subtract(last).divide(price, 4, BigDecimal.ROUND_DOWN);
                    // 然后拿到价格后,判定是否大于0说明跌,如果小于0,说明涨,
                    // 然后拿到绝对值,判定一下是否大于等于涨幅度限制 然后再用绝对值*100算出是否大于涨幅度
                     BigDecimal absDivide = divide.abs().multiply(new BigDecimal(100));
                    if (absDivide.compareTo(new BigDecimal(rise)) < 0) {
                        // 然后直接跳出循环
                       continue;
                    }
                    // 然后给一个标识用来确认是涨的还是跌的
                    if (divide.compareTo(BigDecimal.ZERO) > 0) {
                        status = false;
                    }
                    if (divide.compareTo(BigDecimal.ZERO) < 0) {
                        status = true;
                    }
                    // 获取当前时间错
                    long time = System.currentTimeMillis();
                    // 然后拿到上一次时间错,相减,然后转换为分钟
                    long l = time - timeDate;
                    // 转换为分钟
                    l = l / 1000 / 60;
                    IsoMqDTO build = IsoMqDTO.builder()
                            .isoName(sysIsoIncreaseConfig.getIsoName())
                            .isoCode(sysIsoIncreaseConfig.getIsoCode())
                            .riseStatus(status)
                            .rise(String.valueOf(absDivide))
                            .risePercent(rise)
                            .riseTime(String.valueOf(l))
                            .lastPrice(price)
                            .price(last)
                            .volCcy24h(vol24hCny)
                            .vol24h(vol24h).build();
                    // 如果价格是空的,然后这时候需要把里面的数据赋值给redis,然后进行判断,然后进行发送消息
                    rabbitTemplate.convertAndSend(IsoMqConstant.ISO_DISCOUNT_EXCHANGE,
                            IsoMqConstant.ISO_DISCOUNT_ROUTING_KEY, build);
                } else {
                    Map<String, Object> redisMap = new HashMap<>(3);
                    // 时间戳
                    redisMap.put(IsoConstants.ISO_LAST_TIME, System.currentTimeMillis());
                    // 当前价格
                    redisMap.put(IsoConstants.ISO_PRICE, last);
                    // 涨幅度
                    redisMap.put(IsoConstants.ISO_RISE, sysIsoIncreaseConfig.getIsoIncreaseRate());
                    redisTemplate.opsForHash().put(IsoConstants.ISO_REDIS, IsoConstants.ISO_MAP + sysIsoIncreaseConfig.getIsoCode(), redisMap);
                }
            }
        }
        return null;
    }
}

第三步 一些ok交易所返回的值以及静态的变量。实体

java 复制代码
package com.ruoyi.system.mq.vo;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * @author hu
 */
@Data
@Builder
public class IsoMqDTO implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 币种
     */
    private String isoName;
    /**
     * 币种code
     */
    private String isoCode;
    /**
     * 涨幅状态
     */
    private Boolean riseStatus;
    /**
     * 涨幅百分比
     */
    private String rise;
    /**
     * 上一次幅度
     */
    private String risePercent;
    /**
     * 涨幅/跌幅时间
     */
    private String riseTime;
    /**
     * 上一次价格
     */
    private BigDecimal lastPrice;
    /**
     * 现价
     */
    private BigDecimal price;
    /**
     * 24计价货币成交量币价
     */
    private BigDecimal volCcy24h;
    /**
     * 24小时成交量
     */
    private BigDecimal vol24h;
}
java 复制代码
package com.ruoyi.common.constant;

/**
 * @author hu
 * @date 2025/05
 * @desc 币种常量
 */
public class IsoConstants {
    /**
     * 产品标识
     */
    public static final String INST_ID = "instId";
    /**
     * 欧易交易所获取产品信息URL
     */
    public static final String OK_URL = "https://www.okx.com/api/v5/market/ticker";
    /**
     * 币种现货标识
     */
    public static final String ISO_CODE = "-USDT";
    /**
     * iso的redis前缀
     */
    public static final String ISO_LOCK_REDIS = "ISO_LOCK";
    /**
     * redis数据
     */
    public static final String ISO_REDIS = "ISO:";
    /**
     * redisMap里面的数据币价
     */
    public static final String ISO_PRICE = "PRICE";
    /**
     * 上一次执行时间戳
     */
    public static final String ISO_LAST_TIME = "LAST_TIME";
    /**
     * 涨幅
     */
    public static final String ISO_RISE = "RISE";
    /**
     * ok交易所code
     */
    public static final String OK_CODE = "code";
    /**
     * ok交易所data
     */
    public static final String OK_DATA = "data";
    /**
     * ok最新价格
     */
    public static final String OK_LAST = "last";
    /**
     * ok最新成交量
     */
    public static final String OK_LAST_SZ = "lastSz";
    /**
     * ok24小时成交量
     */
    public static final String OK_VOLUME_24H = "vol24h";
    /**
     * ok24小时成交金额
     */
    public static final String OK_VOLUME_24H_CNY = "volCcy24h";
    /**
     * redisMap
     */
    public static final String ISO_MAP = "ISO_MAP:";

}

第四步:http请求

java 复制代码
package com.ruoyi.common.utils.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.http.MediaType;

/**
 * 通用http发送方法
 * 
 * @author ruoyi
 */
public class HttpUtils
{
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url)
    {
        return sendGet(url, StringUtils.EMPTY);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param)
    {
        return sendGet(url, param, Constants.UTF8);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType 编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType)
    {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try
        {
            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (in != null)
                {
                    in.close();
                }
            }
            catch (Exception ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param)
    {
        return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
    }

    /**
     * 向指定 URL 发送POST方法的请求
     * 
     * @param url 发送请求的 URL
     * @param param 请求参数
     * @param contentType 内容类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param, String contentType)
    {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try
        {
            log.info("sendPost - {}", url);
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("Content-Type", contentType);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
                if (in != null)
                {
                    in.close();
                }
            }
            catch (IOException ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    public static String sendSSLPost(String url, String param)
    {
        return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
    }

    public static String sendSSLPost(String url, String param, String contentType)
    {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try
        {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("Content-Type", contentType);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null)
            {
                if (ret != null && !"".equals(ret.trim()))
                {
                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }

    public static String doGet(String url, Map<String, String> params, String proxyHost, int proxyPort) throws IOException {
        // 构建URL参数
        StringBuilder fullUrl = new StringBuilder(url);
        if (params != null && !params.isEmpty()) {
            boolean isFirstParam = true;
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (isFirstParam) {
                    fullUrl.append("?");
                    isFirstParam = false;
                } else {
                    fullUrl.append("&");
                }
                fullUrl.append(entry.getKey()).append("=").append(entry.getValue());
            }
        }

        // 创建代理对象
        HttpHost proxy = new HttpHost(proxyHost, proxyPort, "http");

        // 使用代理配置HttpClient
        DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
        CloseableHttpClient httpClient = HttpClients.custom()
                .setRoutePlanner(routePlanner)
                .build();

        // 创建HttpGet请求
        HttpGet httpGet = new HttpGet(fullUrl.toString());

        // 设置请求头(可选)
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");

        // 执行请求
        String result = null;
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);

            HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream instream = entity.getContent();
                result = IOUtils.toString(instream, StandardCharsets.UTF_8);
            }
        } finally {
            // 关闭资源
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    private static class TrustAnyTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[] {};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }
}
java 复制代码
package com.ruoyi.system.config;

import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * @author hu
 * @desc 钉钉通知配置类
 */
@Configuration
public class DingDingConfig {
    @Value("${dingDing.url}")
    private String URL;
    @Value("${dingDing.secret}")
    private String SECRET;

    /**
     * 组装签名url
     *
     * @return url
     */
    @Bean
    public String getUrl() throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        Long timestamp = System.currentTimeMillis();
        String stringToSign = timestamp + "\n" + SECRET;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
        String signResult = "&timestamp=" + timestamp + "&sign=" + sign;
        // 得到拼接后的 URL
        return URL + signResult;
    }

    /**
     * 获取客户端
     *
     * @return
     */
    @Bean
    public DingTalkClient getClient() throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        return new DefaultDingTalkClient(getUrl());
    }
}

第6步 rabbitmq消费者消费消息及钉钉通知以及一些变量

java 复制代码
package com.ruoyi.system.mq;

import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.rabbitmq.client.Channel;
import com.ruoyi.common.constant.IsoConstants;
import com.ruoyi.system.config.DingDingConfig;
import com.ruoyi.system.mq.vo.IsoMqDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

import static com.ruoyi.common.mq.IsoMqConstant.*;

/**
 * @author hu
 */
@Component
@Slf4j
public class IsoConsumer {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DingDingConfig dingDingConfig;

    /**
     * 收到nack
     * 第二个参数是是否批量处理,true代表批量确认小于等于第二个参数的消息,false代表只确认当前一个消息收到
     * 第三个参数是是否重新入队,true代表重新入队,false代表丢弃
     */
    @RabbitListener(bindings = @QueueBinding(value = @Queue(value = ISO_DISCOUNT_QUEUE, durable = "true"),
            exchange = @Exchange(name = ISO_DISCOUNT_EXCHANGE), key = {ISO_DISCOUNT_ROUTING_KEY}))
    public void isoConsumer(Channel channel, Message message, IsoMqDTO isoMqDTO) throws IOException {
        log.info("虚拟货币收到rabbitmq消息了:{}", isoMqDTO);
        try {
            // 直接进行ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            // 删除redis里面的数据
            redisTemplate.opsForHash().delete(IsoConstants.ISO_REDIS, IsoConstants.ISO_MAP + isoMqDTO.getIsoCode());
            // 然后拿到里面的数据,开始放到redis中
            Map<String, Object> redisMap = new HashMap<>(3);
            // 上一次涨幅时间
            redisMap.put(IsoConstants.ISO_LAST_TIME, System.currentTimeMillis());
            // 当前价格
            redisMap.put(IsoConstants.ISO_PRICE, isoMqDTO.getPrice());
            // 涨幅度
            redisMap.put(IsoConstants.ISO_RISE, isoMqDTO.getRisePercent());
            redisTemplate.opsForHash().put(IsoConstants.ISO_REDIS, IsoConstants.ISO_MAP + isoMqDTO.getIsoCode(), redisMap);
            // string 类型转换为json
            // 然后这时候发送钉钉通知
            DingTalkClient client = dingDingConfig.getClient();
            OapiRobotSendRequest request = new OapiRobotSendRequest();
            // 消息类型 - 文本
            request.setMsgtype("text");
            OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
            // 确定是涨幅还是跌幅的文字描述
            String riseOrFall = isoMqDTO.getRiseStatus() ? "涨" : "跌";
            // 构建文本内容
            // 构建文本内容,添加上涨和下跌的图标以及基本的Markdown样式
            String content = "🚨 OK交易所「" + isoMqDTO.getIsoName() + "」价格异动通知\n\n" +
                    "---\n" +  // 分割线
                    "🔹 **币种代码**:`" + isoMqDTO.getIsoCode() + IsoConstants.ISO_CODE + "`\n" +
                    "🔹 **波动方向**:" +
                    (isoMqDTO.getRiseStatus() ?
                            "🟢 **<< 多头行情 >>**" :  // 绿色表示上涨
                            "🔴 **<< 空头预警 >>**") + "\n" +  // 红色表示下跌
                    "🔹 **当前" + riseOrFall + "幅**:`" + isoMqDTO.getRise() + "%` " +
                    getTrendIcon(Double.parseDouble(isoMqDTO.getRise()), Integer.valueOf(isoMqDTO.getRisePercent()), isoMqDTO.getRiseStatus()) + "\n" +  // 根据涨幅动态显示箭头
                    "🔹 **达标倒计时**:⏳ `" + isoMqDTO.getRiseTime() + "分钟`\n\n" +

                    "**📊 价格快照**\n" +
                    "┌──────────────┬─────────────┐\n" +
                    "│  上次价格    │  " + formatNumber(isoMqDTO.getLastPrice()) + "  │\n" +
                    "├──────────────┼─────────────┤\n" +
                    "│  最新报价    │  " + formatNumber(isoMqDTO.getPrice()) + "  │\n" +
                    "└──────────────┴─────────────┘\n" +

                    "**📈 市场数据**\n" +
                    "▪️ 24h计价量:`" + formatNumber(isoMqDTO.getVolCcy24h()) + "`\n" +
                    "▪️ 24h成交量:`" + formatNumber(isoMqDTO.getVol24h()) + "`\n\n" +

                    "**📌 风险提示**\n" +
                    "※ 数据来源:OKEx交易所实时行情\n" +
                    "**⚠️ 投资有风险,操作需谨慎**";

            text.setContent(content);
            request.setText(text);
            OapiRobotSendResponse response = client.execute(request);
        } catch (Exception e) {
            e.printStackTrace();
            // 不管包不包过就进行ack,防止重复发送
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    // 数值格式化方法
    private String formatNumber(Number value) {
        return ((BigDecimal) value).toPlainString();
    }

    // 动态趋势图标
    private String getTrendIcon(double rise, Integer risePercent, boolean riseStatus) {
        if (riseStatus && rise > risePercent){
            return "🚀";
        }else if (!riseStatus && -rise < risePercent){
            return "💥";
        }
        return "➡️";


    }
}
java 复制代码
package com.ruoyi.common.mq;

/**
 * @data
 * @desc 描述
 */
public class IsoMqConstant {
    /**
     * iso交换机名字
     */
    public static final String ISO_DISCOUNT_EXCHANGE = "iso.exchange";
    /**
     * iso队列
     */
    public static final String ISO_DISCOUNT_QUEUE = "iso.queue";
    /**
     * iso路由key
     */
    public static final String ISO_DISCOUNT_ROUTING_KEY = "iso.routing.key";

}

第7步:钉钉配置首先

邀请3个人

点击设置

就算可以了。

第8步:代理服务器

首先是在windows上利用clash上网,前提是自己有

最后就可以记性发送了,

结果

下一章节。在linux按照clash。并部署到服务器

相关推荐
欧达克44 分钟前
AI 嘴替,社交平台反杠机器人:第 1 篇-大模型集成
后端
失乐园1 小时前
Redis性能之王:从数据结构到集群架构的深度解密
java·后端·面试
全栈开发七分钟1 小时前
vercel开源平替,dokploy七分钟国内安装指南
前端·后端·ci/cd
fliter1 小时前
性能比拼: TCP vs UDP(第三轮)
后端
kfepiza1 小时前
Spring的 @Conditional @ConditionalOnProperty 注解 笔记250330
java·spring boot·spring
IT古董1 小时前
出现“ERR_CONNECTION_REFUSED”的原因及解决方法
后端
Justin3go2 小时前
如何制作Excalidraw流程图动画
前端·后端·github
Asthenia04122 小时前
面试复盘:Java String 源码分析与不可变类设计原理
后端
Asthenia04122 小时前
面试复盘:synchronized 锁与 ReentrantLock 锁的区别及 AQS 认知完善
后端