Netty实现A服务信息到B服务信息转发

1、本代码需要一定java基础

Netty原理

我们都知道 Netty 是一个高性能、异步事件驱动的 NIO 框架,基于 JAVA NIO 提供的 API 实现

2、代码文件目录

直接上代码

1、往BBWConfig.properties写入服务信息

package com.com.test.bbw;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.Properties;

@Component
@ConfigurationProperties
@PropertySource("classpath:BBWConfig.properties")
public class BBWConfig {

    private static final Log logger = LogFactory.getLog(BBWConfig.class);


    private String config_path = "src/main/resources/";   //本地路径
    private String config_filename = "BBWConfig.properties";
    private static Properties properties;

    private static String bbwServerIp;   //BBW服务器地址
    private static String bbwServerPort; //BBW服务器端口
    private static String paySendPort;   //支付发出端口
    private static String payRcvServerIp; //支付接收地址
    private static String payTcpRcvPort; //tcp支付接收端口
    private static String payHttpRcvPort; //http支付接收端口
    private static String warningNum; //AGENt重连次数警告consumer
    private static String encoding;      //报文编码格式
    private static String reStartTime;   //AGNET异常重连间隔(单位:毫秒)


    // Netty服务连接状态:连接中
    public static final int CONNECT_STATE_CONNECTING = 0;
    //  Netty服务连接状态:连接成功
    public static final int CONNECT_STATE_SUCC = 1;
    //  Netty服务连接状态:连接失败
    public static final int CONNECT_STATE_FAIL = -1;

    public BBWConfig() {
        String home =System.getProperty("user.dir");
        // set config file path
        if (null != home && !"".equals(home.trim())){
            if (!home.endsWith("/")){
                home = home + "/";
            }
            config_filename = home + config_path + config_filename;
        }
        else{
            config_filename = config_path + config_filename;
        }
        // check config file
        creatConfigFile();
        this.properties = getConfigFile();
        init();
    }

    private Properties getConfigFile() {
        properties = new Properties();
        try {
            FileReader fileReader = new FileReader(config_filename);
            properties.load(fileReader);
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties;
    }

    public static Properties getProperties() {
        return properties;
    }

    public static void setProperties(Properties properties) {
        BBWConfig.properties = properties;
    }

    public static String getBbwServerIp() {
        return bbwServerIp;
    }

    public static void setBbwServerIp(String bbwServerIp) {
        BBWConfig.bbwServerIp = bbwServerIp;
    }

    public static String getBbwServerPort() {
        return bbwServerPort;
    }

    public static void setBbwServerPort(String bbwServerPort) {
        BBWConfig.bbwServerPort = bbwServerPort;
    }

    public static String getPaySendPort() {
        return paySendPort;
    }

    public static void setPaySendPort(String paySendPort) {
        BBWConfig.paySendPort = paySendPort;
    }

    public static String getPayRcvServerIp() {
        return payRcvServerIp;
    }

    public static void setPayRcvServerIp(String payRcvServerIp) {
        BBWConfig.payRcvServerIp = payRcvServerIp;
    }

    public static String getPayTcpRcvPort() {
        return payTcpRcvPort;
    }

    public static void setPayTcpRcvPort(String payTcpRcvPort) {
        BBWConfig.payTcpRcvPort = payTcpRcvPort;
    }

    public static String getPayHttpRcvPort() {
        return payHttpRcvPort;
    }

    public static void setPayHttpRcvPort(String payHttpRcvPort) {
        BBWConfig.payHttpRcvPort = payHttpRcvPort;
    }

    public static String getWarningNum() {
        return warningNum;
    }

    public static void setWarningNum(String warningNum) {
        BBWConfig.warningNum = warningNum;
    }

    public static String getEncoding() {
        return encoding;
    }

    public static void setEncoding(String encoding) {
        BBWConfig.encoding = encoding;
    }

    public static String getReStartTime() {
        return reStartTime;
    }

    public static void setReStartTime(String reStartTime) {
        BBWConfig.reStartTime = reStartTime;
    }

    /**
     *
     * @说明 创建默认的配置文件
     */
    private File creatConfigFile() {
        File file = new File(config_filename);

        if (logger.isInfoEnabled()) {
            logger.info("BBWConfig config_filename = " + config_filename);
        }

        if (!file.exists()) {
            if (logger.isInfoEnabled()) {
                logger.info("BBWConfig start creatConfigFile  ..............");
            }
            try {
                file.createNewFile();
                OutputStreamWriter osw = new OutputStreamWriter(
                        new FileOutputStream(file));
                osw.write("#BBW服务器地址" + "\n");
                osw.write("bbwServerIp=127.0.0.1" + "\n");
                osw.write("#BBW服务器端口" + "\n");
                osw.write("bbwServerPort=4444" + "\n");
                osw.write("#支付接出端口Tcp" + "\n");
                osw.write("paySendPort=50039" + "\n");
                osw.write("#支付接入平台地址" + "\n");
                osw.write("payRcvServerIp=127.0.0.1" + "\n");
                osw.write("#支付平台接入端口tcp" + "\n");
                osw.write("payTcpRcvPort=50040" + "\n");
                osw.write("#支付平台接入http端口" + "\n");
                osw.write("payHttpRcvPort=9004" + "\n");
                osw.write("#AGENt重连次数警告consumer" + "\n");
                osw.write("warningNum=10" + "\n");
                osw.write("#报文编码格式" + "\n");
                osw.write("encoding=UTF8" + "\n");
                osw.write("#AGNET异常重连间隔(单位:毫秒)" + "\n");
                osw.write("reStartTime=10000" + "\n");
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return file;
    }
    private void init(){
        BBWConfig.bbwServerIp=BBWConfig.properties.getProperty("bbwServerIp");
        BBWConfig.bbwServerPort=BBWConfig.properties.getProperty("bbwServerPort");
        BBWConfig.paySendPort=BBWConfig.properties.getProperty("paySendPort");
        BBWConfig.payRcvServerIp=BBWConfig.properties.getProperty("payRcvServerIp");
        BBWConfig.payTcpRcvPort=BBWConfig.properties.getProperty("payTcpRcvPort");
        BBWConfig.payHttpRcvPort=BBWConfig.properties.getProperty("payHttpRcvPort");
        BBWConfig.encoding=BBWConfig.properties.getProperty("encoding");
        BBWConfig.warningNum=BBWConfig.properties.getProperty("warningNum");
    }
    public static void main(String[] args) {
        BBWConfig nc = new BBWConfig();
        if (logger.isDebugEnabled()) {
            logger.debug("BBWConfig toString \n" + nc.getConfigFile().toString());
        }
    }

}

 2、读取数据工具类

package com.com.test.bbw;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ConverUtils {

    private static final Logger log = LoggerFactory.getLogger(ConverUtils.class);

    private static final Integer msgLeng = 5;

    /**
     * 循环去写数据
     *
     * @param bos
     * @param buff
     * @param bufferCapacity
     * @throws IOException
     */
    public static void cycleWriteMessage(BufferedOutputStream bos, byte[] buff, int bufferCapacity) throws IOException {
        int len = buff.length;

        int count = 0;
        int tmpLen = len;
        while (true) {
            if (tmpLen <= bufferCapacity) {
                bos.write(buff, count, tmpLen);
                bos.flush();
                count += tmpLen;
                tmpLen = 0;
            } else {
                bos.write(buff, count, bufferCapacity);
                bos.flush();
                count += bufferCapacity;
                tmpLen -= bufferCapacity;
            }
            if (tmpLen == 0)
                break;
        }
    }

    /**
     * 循环去读数据
     *
     * @param bis
     * @param baos
     * @param bufferCapacity
     * @param len
     * @param socketReaderCircleTimes
     * @throws IOException
     */
    public static String cycleReadMessage(BufferedInputStream bis, StringBuffer msgInfo, int bufferCapacity, int len,
                                          int socketReaderCircleTimes) throws IOException {
        int index = 0;
        // 读取的明文长度
        int numberRead = 0;
        // 读取的字节长度
        int numberReadByte = 0;
        // 明文的长度变量
        int tmpLen = len;
        byte[] buff = null;
        // 定义可变字符串用于存储取到的明文数据
        StringBuffer msgTemp = new StringBuffer();
        for (int i = 0; i < socketReaderCircleTimes; i++) {
            buff = new byte[tmpLen];
            numberReadByte = bis.read(buff, 0, tmpLen);
            if (numberReadByte <= 0) {
                break;
            }
            // 拿到的字节数据转为16进制数据
            String asciiBytesToMsg = byteArr2HexStr(buff);
            String msg = convertHexToString(asciiBytesToMsg);
            msgTemp.append(msg);
            // 判断长度
            numberRead = asciiBytesToMsg.length();
            // 如果明文长度小于剩余报文头长度
            if (numberRead < tmpLen) {
                index += numberRead;
                tmpLen -= numberRead;
            } else {
                if (numberRead != tmpLen)
                    break;
                index += numberRead;
                tmpLen -= numberRead;
            }

            if (tmpLen <= 0)
                break;
        }
        // 判断长度
        if (msgTemp.length() == len) {
            msgInfo.append(msgTemp);
            return msgInfo.toString();
        }
        return null;
    }



    /**
     * 将两个字节数组合并
     *
     * @param data1
     * @param data2
     * @return
     */
    public static byte[] addBytes(byte[] headData, byte[] bodyData) {
        byte[] data3 = null;
        if(null != bodyData && 0 < bodyData.length){
            data3 = new byte[headData.length + bodyData.length];
            System.arraycopy(headData, 0, data3, 0, headData.length);
            System.arraycopy(bodyData, 0, data3, headData.length, bodyData.length);
        }else {
            data3 = "00000".getBytes();
        }
        return data3;
    }

    /**
     * 循环去读报文的长度
     *
     * @param bis
     * @param baos
     * @param bufferCapacity
     * @param len
     * @param socketReaderCircleTimes
     * @throws IOException
     */
    public static void cycleReadMessageHeadLeng(BufferedInputStream bis, ByteArrayOutputStream baos, int bufferCapacity,
                                                int len, int socketReaderCircleTimes) throws IOException {

        int numberRead = 0;
        int tmpLen = len;
        int index = 0;
        byte[] buff = null;
        for (int i = 0; i < socketReaderCircleTimes; i++) {
            if (tmpLen < bufferCapacity) {
                buff = new byte[tmpLen];
                int available = bis.available();
                if(available >0){
                    numberRead = bis.read(buff, 0, tmpLen);
                }else{
                    continue;
                }
            } else {
                buff = new byte[bufferCapacity];
                numberRead = bis.read(buff, 0, bufferCapacity);
            }

            if (numberRead < 0) {
                return;
            }
            if (numberRead < tmpLen) {
                index += numberRead;
                tmpLen -= numberRead;

                byte[] tmpByte = new byte[numberRead];
                System.arraycopy(buff, 0, tmpByte, 0, numberRead);
                baos.write(tmpByte);
            } else {
                if (numberRead != tmpLen)
                    break;
                index += numberRead;
                tmpLen -= numberRead;
                baos.write(buff);
            }

            if (tmpLen <= 0)
                break;
        }
    }

    /**
     * 将报文的2位ASCII码字符码转为10进制
     *
     * @param bytes
     * @return
     */
    public static int decodeMsgLength(byte[] bytes) {
        if (bytes != null && bytes.length == 2) {

            return bytes2Short(bytes, 0);
        } else {
            log.error("bytes of length cannot decode to int value.");
        }
        return 0;
    }

    public static short bytes2Short(byte[] b, int offset) {
        short n = (short) (((b[offset] < 0 ? b[offset] + 256 : b[offset]) << 8)
                + (b[offset + 1] < 0 ? b[offset + 1] + 256 : b[offset + 1]));
        return n;
    }

    public static byte[] encode(int lengthValue, int length) {
        if (lengthValue > 0 && length > 0) {
            byte[] bytes;
            try {
                String hexStr = Integer.toHexString(lengthValue);
                if (hexStr.length() % 2 != 0) {
                    hexStr = "0" + hexStr;
                }
                bytes = hexstr2ByteArr(hexStr);
                byte[] ret = new byte[length];
                for (int i = 0; i < length; i++) {
                    ret[i] = 0x00;
                }
                System.arraycopy(bytes, 0, ret, length - bytes.length, bytes.length);
                return ret;
            } catch (Exception e) {
                log.error("报文长度转化出错");
            }
        } else {
            if(lengthValue == 0){
                return "00000".getBytes();
            }
            log.error("无效的报文长度:"+lengthValue);
        }
        return new byte[0];
    }

    public static byte[] hexstr2ByteArr(String strIn) {
        byte[] arrB = strIn.getBytes();
        int iLen = arrB.length;
        byte[] arrOut = new byte[iLen / 2];

        for (int i = 0; i < iLen; i += 2) {
            String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }

        return arrOut;
    }

    /**
     * hex字符串转byte数组
     *
     * @param inHex
     *            待转换的Hex字符串
     * @return 转换后的byte数组结果
     */
    public static byte[] hexToByteArray(String inHex) {
        if (inHex != null && !inHex.isEmpty()) {
            int hexlen = inHex.trim().length();
            byte[] result;
            if (hexlen % 2 == 1) {
                // 奇数

            } else {
                // 偶数
                result = new byte[(hexlen / 2)];
                int j = 0;
                for (int i = 0; i < hexlen; i += 2) {
                    result[j] = hexToByte(inHex.substring(i, i + 2));
                    j++;
                }
                return result;
            }
        }
        return new byte[0];

    }

    /**
     * Hex字符串转byte
     *
     * @param inHex
     *            待转换的Hex字符串
     * @return 转换后的byte
     */
    public static byte hexToByte(String inHex) {
        return (byte) Integer.parseInt(inHex, 16);
    }

    /**
     * ASCII码转换为16进制
     *
     * @param str
     * @return
     */
    public static String convertStringToHex(String str) {

        char[] chars = str.toCharArray();

        StringBuffer hex = new StringBuffer();
        for (int i = 0; i < chars.length; i++) {
            hex.append(Integer.toHexString((int) chars[i]));
        }

        return hex.toString();
    }

    /**
     * 将字节数组转为16进制数据
     *
     * @param arrB
     * @return
     * @throws IOException
     */
    public static String byteArr2HexStr(byte[] arrB) throws IOException {
        int iLen = arrB.length;
        StringBuffer sb = new StringBuffer(iLen * 2);

        for (int i = 0; i < iLen; ++i) {
            int intTmp;
            for (intTmp = arrB[i]; intTmp < 0; intTmp += 256) {
            }

            if (intTmp < 16) {
                sb.append("0");
            }

            sb.append(Integer.toString(intTmp, 16));
        }

        return sb.toString();
    }

    /**
     *
     * 16进制数据转为明文
     * @param arrB
     * @return
     * @throws IOException
     */
    public static String convertHexToString(String hex) {

        StringBuilder sb = new StringBuilder();
        StringBuilder temp = new StringBuilder();
        for (int i = 0; i < hex.length() - 1; i += 2) {
            String output = hex.substring(i, (i + 2));
            int decimal = Integer.parseInt(output, 16);
            sb.append((char) decimal);
            temp.append(decimal);
        }

        return sb.toString();
    }
    /**
     * 左补齐
     *
     * @param 原字符串
     * @param 左补字符
     * @param 补齐长度
     * @return 如果原字符串超长,返回原字符串
     */
    public static String lpad(String str, char ch, int len) {
        if (str.length() >= len) {
            return str;
        }

        char[] origChs = str.toCharArray();
        char[] chs = new char[len];
        int pos = len - origChs.length;

        for (int i = 0; i < len; i++) {
            if (i < pos) {
                chs[i] = ch;
            } else {
                chs[i] = origChs[i - pos];
            }
        }
        return new String(chs);
    }

}

 3、解码

package com.com.test.bbw;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.util.List;

public class MsgPckDecode extends MessageToMessageDecoder<ByteBuf> {
	//明文长度
	private static final Integer msgLeng = 5;
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {

		final byte[] headMessage;
		final byte[] contentMessage;
		headMessage = new byte[2];
		// 定义可变字符串用于存储取到的明文数据
		StringBuffer msgTemp = new StringBuffer();
		//获取报文长度
		msg.getBytes(msg.readerIndex(), headMessage, 0, 2);
		// 将两位报文ASII码数据转为10进制长度
		int msgLength = ConverUtils.decodeMsgLength(headMessage);
		String msgLengthStr = msgLength+"";
		msgLengthStr = ConverUtils.lpad(msgLengthStr, '0', msgLeng);
		msgTemp.append(msgLengthStr);
        //根据报文长度读取内容
		contentMessage = new byte[msgLength];
		msg.getBytes(2, contentMessage, 0, msgLength);
		// 拿到的字节数据转为16进制数据
		String asciiBytesToMsg = ConverUtils.byteArr2HexStr(contentMessage);
		String message = ConverUtils.convertHexToString(asciiBytesToMsg);
		msgTemp.append(message);
		out.add(msgTemp.toString());

	}

}
package com.com.test.bbw;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;

public class MsgPckEncode extends MessageToByteEncoder<Object> {

	private static final Integer msgLeng = 5;
	@Override
	protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf buf) throws Exception {
		MessagePack pack = new MessagePack();
		String dataStr = (String) msg;
		// 第一步:获取报文长度
		int msgLength = Integer.parseInt(dataStr.substring(0, msgLeng));
		String msgInfo = dataStr.substring(msgLeng);
		//将报文 长度转为16进制 ,内容转为ASCII码
		String convertStringToHex  = ConverUtils.convertStringToHex(msgInfo);
		byte[] message = ConverUtils.addBytes(ConverUtils.encode(msgLength, 2), ConverUtils.hexToByteArray(convertStringToHex));
		buf.writeBytes(message);

	}
}

4、前置与服务B之间长链接,支持断线重连机制 

package com.com.test.bbw;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class TCPBBWAdaptor {
    private static final Logger log = LoggerFactory.getLogger(TCPBBWAdaptor.class);
    private String bbwServerIp;
    private int bbwServerPort;/* 端口 */
    private String encoding;/* 编码方式 */
    private int timeout=10000;/* 超时时间:秒 */
    private EventLoopGroup group;
    private Bootstrap client;
    private Channel channel;

    public static Logger getLog() {
        return log;
    }

    private ChannelFuture future;

    public TCPBBWAdaptor() {
        init();
        group = new NioEventLoopGroup();
        client = new Bootstrap();
        client.group(group);
        client.channel(NioSocketChannel.class);
        client.option(ChannelOption.SO_KEEPALIVE, true);
        client.handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 按照\r\n进行解码
                //ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
                ch.pipeline().addLast(new IdleStateHandler(0, 0, 5))
                        .addLast(new MsgPckDecode())
                        .addLast(new MsgPckEncode())
                        .addLast(new TcpBBWClientHandler(TCPBBWAdaptor.this));
            }

        });
    }

    public Object send(Object msg) {
        try {
            if (channel != null && channel.isActive()) {
                doSend(msg);
            } else {
                restConnOrConn();
                send(msg);
            }
        } catch (Exception e) {
            log.info("连接异常!");
            group.shutdownGracefully();
        }

        return msg;
    }

    private void doSend(Object msg) {
        group.execute(new Runnable() {
            @Override
            public void run() {
                channel.writeAndFlush(msg);
            }
        });
    }

    public void restConnOrConn() throws InterruptedException {
        future = client.connect(bbwServerIp, bbwServerPort).sync();
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    channel = future.channel();
                } else {
                    future.channel().eventLoop().schedule(new Runnable() {

                        @Override
                        public void run() {
                            try {
                                restConnOrConn();
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                        }
                    }, 2, TimeUnit.SECONDS);
                }
            }
        });

    }
    public void init(){
        this.bbwServerIp=BBWConfig.getBbwServerIp();
        this.bbwServerPort= Integer.parseInt(BBWConfig.getBbwServerPort());
        this.encoding=BBWConfig.getEncoding();
    }
}
package com.com.test.bbw;

import com.com.test.client.TCPPayAdaptor;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

public class TcpBBWClientHandler extends ChannelInboundHandlerAdapter {
	private static final Logger log = LoggerFactory.getLogger(TcpBBWClientHandler.class);
	// 明文报文长度
	private static final Integer msgLeng = 5;
	private TCPBBWAdaptor clinet;
	public TcpBBWClientHandler(TCPBBWAdaptor clinet) {
        this.clinet = clinet;
	}
	/**
	 * 客户端与服务端创建连接的时候调用
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		   ctx.fireChannelActive();
	}

	/**
	 * 客户端与服务端断开连接时调用
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		clinet.restConnOrConn();
	}

	/**
	 * 服务端接收客户端发送过来的数据结束之后调用
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	/**
	 * 工程出现异常的时候调用
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
		//重新连接
		clinet.restConnOrConn();
	}
	/**
	 * 心跳处理方法
	 */
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		// 如果属于心跳处理
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent event = (IdleStateEvent) evt;
			switch (event.state()) {
			case READER_IDLE:
				break;
			case WRITER_IDLE:
				break;
			case ALL_IDLE:
				//发送心跳
				sendMsg(ctx);
				break;
			}
		}
		super.userEventTriggered(ctx, evt);
	}
	private void sendMsg(ChannelHandlerContext ctx) {
		ctx.channel().writeAndFlush("");
	}
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ctx.channel().eventLoop().execute(new Runnable() {
			@Override
			public void run() {
				final String msgL=(String)msg;
				if (!StringUtils.isEmpty(msgL)) {
					log.debug("接收到报文为:" +msg);
					TCPPayAdaptor payAdaptor=new TCPPayAdaptor();
					payAdaptor.send(msg);
				}
			}
		});
	}

}

 5、前置与服务A之间短连接,异步调用

package com.com.test.client;

import com.com.test.bbw.BBWConfig;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;

public class TCPPayAdaptor {
	private static final Logger log = LoggerFactory.getLogger(TCPPayAdaptor.class);
	private String payRcvServerIp;
	private int payTcpRcvPort;/* 端口 */
	private String encoding;/* 编码方式 */
	private int timeout=20000;/* 超时时间:秒 */
	private EventLoopGroup group;
	private Bootstrap client;
	private Channel channel;

	public static Logger getLog() {
		return log;
	}

	private ChannelFuture future;

	public TCPPayAdaptor() {
		init();
		group = new NioEventLoopGroup();
		client = new Bootstrap();
		client.group(group);
		client.channel(NioSocketChannel.class);
		client.option(ChannelOption.SO_KEEPALIVE, true);
		client.handler(new ChannelInitializer<SocketChannel>() {

			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				// 按照\r\n进行解码
				//ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
				ch.pipeline().addLast(new IdleStateHandler(0, 0, 5))
						.addLast(new StringDecoder())
						.addLast(new StringEncoder())
						.addLast(new TcpPayClientHandler());
			}

		});
		try {
			future = client.connect(payRcvServerIp, payTcpRcvPort).sync();
			if(future!=null&&future.isSuccess()){
				channel=future.channel();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public Object send(Object msg) {
		try {
			if (channel != null && channel.isActive()) {
				doSend(msg);
			}
		} catch (Exception e) {
			log.info("连接异常!");
			group.shutdownGracefully();
		}

		return msg;
	}

	private void doSend(Object msg) {
		try {
			final String msgL=(String)msg;
			byte[] bytes = msgL.getBytes(encoding);
			ByteBuf buf = Unpooled.wrappedBuffer(bytes);
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String smsg = new String(req, encoding);
			if (!StringUtils.isEmpty(smsg)) {
				log.debug("接收到报文为:" + smsg);
				channel.writeAndFlush(smsg);
			}
			ByteBuf buffer = Unpooled.buffer();
			buffer.writeBytes(req);
		} catch (UnsupportedEncodingException e) {
			log.info("bbw返回发送异常");
		}
	}

	public void init(){
		this.payTcpRcvPort= Integer.parseInt(BBWConfig.getPayTcpRcvPort());
		this.payRcvServerIp= BBWConfig.getPayRcvServerIp();
		this.encoding= BBWConfig.getEncoding();
	}
}
package com.com.test.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

public class TcpPayClientHandler extends ChannelInboundHandlerAdapter {
	private static final Logger log = LoggerFactory.getLogger(TcpPayClientHandler.class);
	// 明文报文长度
	private static final Integer msgLeng = 5;
	/**
	 * 客户端与服务端创建连接的时候调用
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.fireChannelActive();
	}

	/**
	 * 客户端与服务端断开连接时调用
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		ctx.channel().writeAndFlush("");
	}

	/**
	 * 服务端接收客户端发送过来的数据结束之后调用
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	/**
	 * 工程出现异常的时候调用
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

6、 服务B监听器,用来监听B系统发送给前置消息,用来转发到服务A

package com.com.test.listener;

import com.com.test.server.Server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;



@Component
public class BBWListener implements ApplicationRunner{
	private static final Log logger = LogFactory.getLog(BBWListener.class);

	@Override
	public void run(ApplicationArguments args) throws Exception {

		if (logger.isInfoEnabled()) {
			logger.info("Starting booting core banking services platform...");
		}
		// Start
		try {
			Server server = new Server();
			server.start();
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
		}
		if (logger.isInfoEnabled()) {
			logger.info("Started booted bbw banking services platform...");
		}
	}

}

7、接收到系统B消息服务端

package com.com.test.server;

import com.com.test.bbw.BBWConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class Server {
    private static final Log logger = LogFactory.getLog(Server.class);
    private int paySendPort;
    private String encoding;/* 编码方式 */
    private int timeout = 10000;/* 超时时间:秒 */
    private int conNum = 1;/* 保持连接数 */
    private ServerSocketChannel serverSocketChannel;

    private void bind() {
        //服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
        //连接处理group
        EventLoopGroup boss = new NioEventLoopGroup();
        //事件处理group
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 绑定处理group
        bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
                //保持连接数
               // .option(ChannelOption.SO_BACKLOG, conNum)
                //有数据立即发送
                .option(ChannelOption.TCP_NODELAY, true)
                //保持连接
                .childOption(ChannelOption.SO_KEEPALIVE, false)
                //处理新连接
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 增加任务处理
                        ChannelPipeline p = sc.pipeline();
                        p.addLast(
                                //使用了netty自带的编码器和解码器
                                new StringDecoder(),
                                new StringEncoder(),
                                //心跳检测,读超时,写超时,读写超时
                                //new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS),
                                //自定义的处理器
                                new ServerHandler());
                    }
                });

        //绑定端口,同步等待成功
        ChannelFuture future;
        try {
            future = bootstrap.bind(paySendPort).sync();
            if (future.isSuccess()) {
                serverSocketChannel = (ServerSocketChannel) future.channel();
                logger.info("服务端启动成功,端口:" + paySendPort);
            } else {
                logger.info("服务端启动失败!");
            }
            //等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //优雅地退出,释放线程池资源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    public void init() {
        this.paySendPort = Integer.parseInt(BBWConfig.getPaySendPort());
        this.encoding = BBWConfig.getEncoding();
    }

    public void start() {
        init();
        bind();
    }
}

 7、接收到系统B消息客户端,调用

TCPPayAdaptor转发到系统A
package com.com.test.server;

import com.com.test.bbw.TCPBBWAdaptor;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;

public class ServerHandler extends ChannelInboundHandlerAdapter {
	private static final Log logger = LogFactory.getLog(ServerHandler.class);
	/**
	 * 客户端与服务端创建连接的时候调用
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.fireChannelActive();
	}

	/**
	 * 一旦建立连接第一个被执行
	 */
	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
	}

	/**
	 * 客户端与服务端断开连接时调用
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		ctx.close();
	}
	/**
	 * 服务端接收客户端发送过来的数据结束之后调用
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	/**
	 * 工程出现异常的时候调用
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

	/**
	 * 服务端处理客户端websocket请求的核心方法,这里接收了客户端发来的信息
	 */
	@Override
	public void channelRead(ChannelHandlerContext channelHandlerContext, Object info) throws Exception {
		logger.info("接收到了:" + info);
		try {
			final String msgL=(String)info;
			byte[] bytes = msgL.getBytes(CharsetUtil.UTF_8);
			ByteBuf buf = Unpooled.wrappedBuffer(bytes);
			byte[] req = new byte[buf.readableBytes()];
			buf.readBytes(req);
			String smsg = new String(req, CharsetUtil.UTF_8);
			if (!StringUtils.isEmpty(smsg)) {
				logger.debug("接收到报文为:" + smsg);
				new TCPBBWAdaptor().send(info);
			}
			ByteBuf buffer = Unpooled.buffer();
			buffer.writeBytes(req);
		} catch (Exception e) {
			logger.info("bbw返回发送异常");
		}
	}

}

8、启动类 

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = {"com"})
@SuppressWarnings("all")
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, MongoAutoConfiguration.class})
public class TestApplication {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}

}
日志
logging.config=classpath:logback-boot.xml  

BBWConfig.properties

#BBW服务器地址
bbwServerIp=127.0.0.1
#BBW服务器端口
bbwServerPort=4444
#支付接出端口Tcp
paySendPort=50039
#支付接入平台地址
payRcvServerIp=127.0.0.1
#支付平台接入端口tcp
payTcpRcvPort=50040
#支付平台接入http端口
payHttpRcvPort=9004
#AGENt重连次数警告consumer
warningNum=10
#报文编码格式
encoding=UTF8
#AGNET异常重连间隔(单位:毫秒)
reStartTime=10000
test=003090800C220000080000000040000000000000003535122313335102421203535301

logback-boot.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">

    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="D:/nmyslog/nmys" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--<logger name="org.springframework.web" level="info"/>-->
    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->


    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.com.test.bbw" level="debug"/>
    </springProfile>

    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>

    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
        <!--<root level="info">-->
            <!--<appender-ref ref="CONSOLE" />-->
            <!--<appender-ref ref="DEBUG_FILE" />-->
            <!--<appender-ref ref="INFO_FILE" />-->
            <!--<appender-ref ref="ERROR_FILE" />-->
            <!--<appender-ref ref="WARN_FILE" />-->
        <!--</root>-->
    <!--</springProfile>-->

</configuration>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.test</groupId>
	<artifactId>test</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>test</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.kafka</groupId>
			<artifactId>spring-kafka</artifactId>
		</dependency>
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
		</dependency>
		<dependency>
			<groupId>org.msgpack</groupId>
			<artifactId>msgpack</artifactId>
			<version>0.6.12</version>
		</dependency>
		 <dependency>
              <groupId>ch.qos.logback</groupId>
              <artifactId>logback-classic</artifactId>
              <version>1.2.3</version>
              <scope>compile</scope>
            </dependency>
            <dependency>
              <groupId>org.apache.logging.log4j</groupId>
              <artifactId>log4j-to-slf4j</artifactId>
              <version>2.10.0</version>
              <scope>compile</scope>
            </dependency>
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>jul-to-slf4j</artifactId>
              <version>1.7.25</version>
              <scope>compile</scope>
            </dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.kafka</groupId>
			<artifactId>spring-kafka-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值