b站直播弹幕

结构:

WebSocketConfig:
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
SocketController:
@RestController
@RequestMapping(value = "/socket")
public class SocketController {

    private static final Logger logger = LoggerFactory.getLogger(SocketController.class);

    @Autowired
    private ClientSocket clientSocket;

    @ApiOperation(value = "获取bilibili弹幕")
    @PostMapping(value = "/open")
    public Result open(@ApiParam(value = "房间号") @RequestParam(value = "roomId", required = false) Integer roomId) {
        try {
            clientSocket.start(roomId);
        } catch (Exception e) {
            logger.error(" ===> 获取bilibili弹幕错误:{}", e.getMessage());
        }
        return ResultUtil.success();
    }

}
ResultCodeEnum:
public enum ResultCodeEnum {

    ERROR_CODE(1),
    SUCCESS_CODE(0),
    ;

    private Integer code;

    ResultCodeEnum(Integer code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

ResultMsgEnum:
public enum ResultMsgEnum {

    ERROR_MSG_DEFAULT("error"),
    SUCCESS_MSG_DEFAULT("success"),
    ;

    private String msg;

    ResultMsgEnum(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

UintEnum:
public enum UintEnum {

    UINT32(8),
    UINT16(4),
    UINT8(2),

    ;
    private Integer byteNum;

    public Integer getByteNum() {
        return byteNum;
    }

    public void setByteNum(Integer byteNum) {
        this.byteNum = byteNum;
    }

    UintEnum(Integer byteNum) {
        this.byteNum = byteNum;
    }
}
LiveRespDanMuVO:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel
public class LiveRespDanMuVO {

    @ApiParam(value = "用户ID")
    private Integer uid;

    @ApiParam(value = "用户名")
    private String uName;

    @ApiParam(value = "弹幕内容")
    private String content;
}
BinaryHandleInstance:
@Component
@Data
public class BinaryHandleInstance {

    private static final char ZERO = '0';

    private StringBuffer reqParam;

    public StringBuffer handleByteData(Integer offset, UintEnum unit, String valueStr) {

        valueStr = fillValueStr(unit, valueStr);

        try {
            if (Objects.isNull(this.reqParam)) {
                this.reqParam = new StringBuffer();
            }
            this.reqParam.insert(finalOffset(offset), valueStr);
        } catch (Exception e) {
            throw new RuntimeException(" ===> valueStr or offset is error");
        }
        return reqParam;
    }

    private String fillValueStr(UintEnum unit, String valueStr) {
        while (valueStr.length() < lengthFromUnit(unit)) {
            valueStr = ZERO + valueStr;
        }
        return valueStr;
    }

    private Integer lengthFromUnit(UintEnum unit) {
        return unit.getByteNum();
    }

    private Integer finalOffset(Integer offset) {
        return 2 * offset;
    }

}
LiveResponseMsgServiceImpl:
@Service
public class LiveResponseMsgServiceImpl implements LiveResponseMsgService {

    private static final String DANMU_INFO = "info";

    @Override
    public LiveRespDanMuVO ToVOFromJson(JSONObject jsonObject) {

        if (Objects.isNull(jsonObject)) {
            return null;
        }

        JSONArray info = jsonObject.getJSONArray(DANMU_INFO);
        if (CollectionUtils.isEmpty(info)) {
            return null;
        }

        JSONArray userInfo = info.getJSONArray(2);
        if (CollectionUtils.isEmpty(userInfo)){
            return null;
        }

        Integer uid = userInfo.getInteger(0);
        String uName = userInfo.getString(1);
        String content = info.getString(1);
        return LiveRespDanMuVO.builder()
                .uid(uid)
                .uName(uName)
                .content(content)
                .build();
    }
}
LiveResponseMsgService:
public interface LiveResponseMsgService {

    LiveRespDanMuVO ToVOFromJson(JSONObject jsonObject);
}
Regex:
public class Regex {

    /**
     * 匹配b站返回的弹幕前缀
     */
    public static final String LIVE_RESPONSE_DANMU_MSG = "\\{\"cmd\":\"DANMU_MSG\",[^\\}].*";

    /**
     * 匹配b站响应通用前缀
     */
    public static final String LIVE_RESPONSE_MSG = "\\{\"cmd\"[^\\}].*";

    /**
     * 匹配json字符串
     */
    @Deprecated
    public static final String KEY_VALUE_MSG = "\\{\"([a-zA-Z_]+)\\\":(.+)}";

    /**
     * 匹配双字节的字符
     */
    @Deprecated
    public static final String BYTE2_MSG = "[\\x00-\\xff].?";

}
Result:
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    public Result() {

    }

    public Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
BinaryHandleUtil:
@Component
public class BinaryHandleUtil {

    private static final String COMMA = ",";

    @Autowired
    private BinaryHandleInstance instance;

    public BinaryHandleUtil setUnit(Integer offset, UintEnum unit, Integer value) {

        if (Objects.isNull(value)) {
            throw new RuntimeException(" ===> value is null");
        }

        String valueStr = Integer.toHexString(value);
        instance.handleByteData(offset, unit, valueStr);
        return this;
    }

    public String getHexBytesStr() {
        return this.instance.getReqParam().toString();
    }

    public byte[] HexStrToByteArray() {

        String hexStr = this.getHexBytesStr();

        if (Objects.isNull(hexStr)) {
            return null;
        }

        if (Objects.equals(hexStr, 0)) {
            return new byte[0];
        }

        byte[] byteArray = new byte[hexStr.length() / 2];

        for (int i = 0; i < byteArray.length; i++) {
            String subStr = hexStr.substring(2 * i, 2 * i + 2);
            byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
        }
        return byteArray;
    }

    public byte[] HexByteArray() {
        return this.HexStrToByteArray();
    }

    public void clearInstanceBuffer() {
        instance.setReqParam(new StringBuffer());
    }

    public int[] toUintArrayFromByteArray(byte[] bytes) {
        int[] uintArray = new int[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            uintArray[i] = Byte.toUnsignedInt(bytes[i]);
        }
        return uintArray;
    }

    public StringBuffer toBinaryStrFromUintArray(int value, StringBuffer builder) {

        if (Objects.isNull(value)){
            throw new RuntimeException(" value is null");
        }

        String param1 = new String(String.valueOf(value));
        String param2 = new String(COMMA);
        builder.append(param1);
        builder.append(param2);
        return builder;
    }

    public String getStrByDecompress(StringBuffer buffer){
        buffer.deleteCharAt(buffer.length() - 1);
        String binaryStr = buffer.toString();//binaryStr: 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 8

        if (Objects.isNull(binaryStr)){
            throw new RuntimeException("binaryStr is null");
        }

        byte[] clientBytes = PakoUtil.receive(binaryStr);
        byte[] bytes = ZlibUtil.decompress(clientBytes);
        return new String(bytes, StandardCharsets.UTF_8);
    }

    public byte[] getByteArrayFromInt(int value){
        byte[] bytes = new byte[1];
        bytes[0] =  (byte) (value & 0xFF);
//        bytes[1] =  (byte) ((value>>8) & 0xFF);
//        bytes[2] =  (byte) ((value>>16) & 0xFF);
//        bytes[3] =  (byte) ((value>>24) & 0xFF);
        return bytes;
    }

}
PakoUtil:
public class PakoUtil {


        public static byte[] receive(String arrInt){

            /**
             * 将数字字符串 ->  byte[]
             */
            String[] a = arrInt.split(",");//arrInt: 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 8,
            byte[] clientBytes = new byte[a.length];
            int i = 0;
            for (String e : a) {
                clientBytes[i] = Integer.valueOf(e).byteValue();
                i++;
            }
            return clientBytes;
        }

        /**
         * 发送给 Pako 的数据格式
         * @param bytes 服务端生成的字节数组
         * @return String 发送给 pako.js 的数据格式
         */
        public static String send(byte[] bytes) {
            String[] ints = new String[bytes.length];
            int j=0;
            for(byte e:bytes) {
                int t = e;
                if(t<0) {
                    t = 256+t;
                }
                ints[j++] = String.valueOf(t);

            }
            return String.join(",", ints);
        }

}
ResultUtil:
public class ResultUtil {

    /**
     * 成功对象
     * 返回携带data、msg的
     *
     * @param msg
     * @param data
     * @return
     */
    public static Result success(String msg, Object data) {
        return successResult(msg, data);
    }

    /**
     * 成功对象
     * 返回携带data的
     *
     * @param data
     * @return
     */
    public static Result success(Object data) {
        return successResult(null, data);
    }

    /**
     * 成功对象
     * 返回携带msg的
     *
     * @param msg
     * @return
     */
    public static Result success(String msg) {
        return successResult(msg, null);
    }

    /**
     * 成功对象
     * 返回默认的
     *
     * @return
     */
    public static Result success() {
        return successResult(null, null);
    }

    /**
     * 失败对象
     * 返回携带data、msg的
     *
     * @param msg
     * @param data
     * @return
     */
    public static Result error(String msg, Object data) {
        return errorResult(msg, data);
    }


    /**
     * 失败对象
     * 返回携带data的
     *
     * @param data
     * @return
     */
    public static Result error(Object data) {
        return errorResult(null, data);
    }

    /**
     * 失败对象
     * 返回携带msg的
     *
     * @param msg
     * @return
     */
    public static Result error(String msg) {
        return errorResult(msg, null);
    }

    /**
     * 失败对象
     * 返回默认的
     *
     * @return
     */
    public static Result error() {
        return errorResult(null, null);
    }


    private static Result errorResult(String msg, Object data) {
        return StringUtils.isNotEmpty(msg) ? new Result(ResultCodeEnum.ERROR_CODE.getCode(), msg, data) : new Result(ResultCodeEnum.ERROR_CODE.getCode(), ResultMsgEnum.ERROR_MSG_DEFAULT.getMsg(), data);
    }

    private static Result successResult(String msg, Object data) {
        return StringUtils.isNotEmpty(msg) ? new Result(ResultCodeEnum.SUCCESS_CODE.getCode(), msg, data) : new Result(ResultCodeEnum.SUCCESS_CODE.getCode(), ResultMsgEnum.SUCCESS_MSG_DEFAULT.getMsg(), data);
    }
}
ZlibUtil:
/**
 * zlib 压缩算法
 */
public class ZlibUtil {


    /**
     * 压缩  
     *
     * @param data 待压缩数据
     * @return byte[] 压缩后的数据  
     */
    public static byte[] compress(byte[] data) {
        byte[] output = new byte[0];

        Deflater compresser = new Deflater();

        compresser.reset();
        compresser.setInput(data);
        compresser.finish();
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!compresser.finished()) {
                int i = compresser.deflate(buf);
                bos.write(buf, 0, i);
            }
            output = bos.toByteArray();
        } catch (Exception e) {
            output = data;
            e.printStackTrace();
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        compresser.end();
        return output;
    }

    /**
     * 压缩  
     * @param data 待压缩数据
     * @param os   输出流
     */
    public static void compress(byte[] data, OutputStream os) {
        DeflaterOutputStream dos = new DeflaterOutputStream(os);

        try {
            dos.write(data, 0, data.length);

            dos.finish();

            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解压缩  
     * @param data 待解压的数据
     * @return byte[] 解压缩后的数据
     */
    public static byte[] decompress(byte[] data) {
        byte[] output = new byte[0];

        Inflater decompresser = new Inflater();
        decompresser.reset();
        decompresser.setInput(data);

        ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!decompresser.finished()) {
                int i = decompresser.inflate(buf);
                o.write(buf, 0, i);
            }
            output = o.toByteArray();
        } catch (Exception e) {
            output = data;
            e.printStackTrace();
        } finally {
            try {
                o.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        decompresser.end();
        return output;
    }

    /**
     * 解压缩  
     * @param is 输入流
     * @return byte[] 解压缩后的数据  
     */
    public static byte[] decompress(InputStream is) {
        InflaterInputStream iis = new InflaterInputStream(is);
        ByteArrayOutputStream o = new ByteArrayOutputStream(1024);
        try {
            int i = 1024;
            byte[] buf = new byte[i];

            while ((i = iis.read(buf, 0, i)) > 0) {
                o.write(buf, 0, i);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return o.toByteArray();
    }
}
ClientSocket:
@ClientEndpoint
@Component
public class ClientSocket {

    private static final Logger logger = LoggerFactory.getLogger(ClientSocket.class);

    private static final Pattern PATTERN = Pattern.compile(Regex.LIVE_RESPONSE_DANMU_MSG);

    private static final String BUILD_JSON_PARAM_WEB = "web";
    private static final String BUILD_JSON_PARAM_CLIENTVER = "1.4.0";
    private static final Integer BUILD_JSON_PARAM_UID = 0;
    private static final Integer BUILD_JSON_PARAM_PROTOVER = 1;

    private JSONObject json;

    private Session session;

    private Integer roomId;

    String url = "wss://dsa-cn-live-comet-01.chat.bilibili.com:2245/sub";

    @Autowired
    private BinaryHandleUtil binaryHandleUtil;
    @Autowired
    private LiveResponseMsgService liveResponseMsgService;

    private JSONObject init(Integer roomId) {
        this.json = new JSONObject();
        json.put("roomid", roomId);
        json.put("uid", BUILD_JSON_PARAM_UID);
        json.put("protover", BUILD_JSON_PARAM_PROTOVER);
        json.put("platform", BUILD_JSON_PARAM_WEB);
        json.put("clientver", BUILD_JSON_PARAM_CLIENTVER);
//        json.put("type", 2);
//        json.put("key", "u91Sk476h2FV7KCv59U35sEAuVNmQUq0yBfeqJVHIKZqkxVPe_hJYD1GKS-cS43jNMVpD_TEih7K-ybwqpGvotO7luheMjhEi0w7wjILrm8WJ-dL4xid3D0rnJiP7QruH3xTVYNw41xffdF5-UM=");
        return json;
    }

    public void start(Integer roomId) {
        try {
            this.roomId = roomId;
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            URI uri = URI.create(url);
            container.connectToServer(ClientSocket.this, uri);
        } catch (DeploymentException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        logger.info(" ===> 连接 billibili 成功");
        ByteArrayOutputStream dataBytes = buildCertifyByte(init(this.roomId));
        if (Objects.isNull(dataBytes)) {
            throw new RuntimeException(" ===> 认证数据结果为空");
        }
        int size = dataBytes.size();
        byte[] bytes = dataBytes.toByteArray();

        binaryHandleUtil.setUnit(0, UintEnum.UINT32, size + 16)
                .setUnit(4, UintEnum.UINT16, 16)
                .setUnit(6, UintEnum.UINT16, 1)
                .setUnit(8, UintEnum.UINT32, 7)
                .setUnit(12, UintEnum.UINT32, 1);
        for (int i = 0; i < size; i++) {
            binaryHandleUtil.setUnit(16 + i, UintEnum.UINT8, Integer.parseInt(bytes[i] + ""));
        }

        ByteBuffer byteBuffer = ByteBuffer.wrap(binaryHandleUtil.HexByteArray());
        try {
            session.getBasicRemote().sendBinary(byteBuffer);
            binaryHandleUtil.clearInstanceBuffer();//清除缓存
            ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
            executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    heartbeat();//心跳请求 保持连接
                }
            },1,30, TimeUnit.SECONDS);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }

    }

    @OnClose
    public void onClose() {
        logger.info(" ===> 关闭 billibili 成功");
    }

    @OnError
    public void onError(Throwable e) {
        logger.info(" ===> 连接bilibili 发生异常");
        logger.error(e.getMessage(), e);
    }

    @OnMessage
    public void onMessage(Session session, byte[] message) {
//        logger.info(" ===> message:{}", message);
        int[] uintArray = binaryHandleUtil.toUintArrayFromByteArray(message);//转成无符号整型 uint: 0, 0, 1, -12, 0, 16, 0, 1, 0, 0, 0, 8

        Integer packageLength = readIntFromByteArray(uintArray, 0, 4);
        Integer headLength = readIntFromByteArray(uintArray, 4, 2);
        Integer ver = readIntFromByteArray(uintArray, 6, 2);
        Integer op = readIntFromByteArray(uintArray, 8, 4);
        Integer seq = readIntFromByteArray(uintArray, 12, 4);
        if (Objects.equals(op, 5)) {
            String bodyStr;
            int offset = 0;
            while (offset < packageLength) {
                int countPackageLength = readIntFromByteArray(uintArray, offset + 0, 4);
                int countHeadLength = 16;
                int[] data = Arrays.copyOfRange(uintArray, offset + countHeadLength, offset + countPackageLength);
                StringBuffer buffer = new StringBuffer();
                if (Objects.equals(ver, 2)) {
                    //协议版本为 2 时  数据有进行压缩
                    for (int value : data) {//data: 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 8
                        buffer = binaryHandleUtil.toBinaryStrFromUintArray(value, buffer);
                    }
                    bodyStr = binaryHandleUtil.getStrByDecompress(buffer);
                    logger.info(" ===> strByDecompress:{}", bodyStr);
                } else {
                    //协议版本为 0 时  数据没有进行压缩
                    for (int value : data) {
                        //没不需要解压直接把无符号整型转成字节 在重新构造字符串
                        byte[] array = binaryHandleUtil.getByteArrayFromInt(value);
                        String strByByteArray = new String(array, StandardCharsets.UTF_8);
                        buffer.append(strByByteArray);
                    }
                    bodyStr = buffer.toString();
                    logger.info(" ===> str:{}", bodyStr);
                }

                // 同一条弹幕消息中可能存在多条信息,用正则筛出来
                Matcher matcher = PATTERN.matcher(bodyStr);
                StringBuffer stringBuffer = new StringBuffer();
                String group = "";
                JSONObject jsonObject = null;
                if (matcher.find()) {
                    group = matcher.group();//group: "{\"cmd\":\"DANMU_MSG\",\"info\":[[0,1,25,16777215,1673881110416,1673881101,0,\"748aa4fc\",0,0,0,\"\",0,\"{}\",\"{}\",{\"mode\":0,\"show_player_type\":0,\"extra\":\"{\\\"send_from_me\\\":false,\\\"mode\\\":0,\\\"color\\\":16777215,\\\"dm_type\\\":0,\\\"font_size\\\":25,\\\"player_mode\\\":1,\\\"show_player_type\\\":0,\\\"content\\\":\\\"kana;\\\",\\\"user_hash\\\":\\\"1955243260\\\",\\\"emoticon_unique\\\":\\\"\\\",\\\"bulge_display\\\":0,\\\"recommend_score\\\":0,\\\"main_state_dm_color\\\":\\\"\\\",\\\"objective_state_dm_color\\\":\\\"\\\",\\\"direction\\\":0,\\\"pk_direction\\\":0,\\\"quartet_direction\\\":0,\\\"anniversary_crowd\\\":0,\\\"yeah_space_type\\\":\\\"\\\",\\\"yeah_space_url\\\":\\\"\\\",\\\"jump_to_url\\\":\\\"\\\",\\\"space_type\\\":\\\"\\\",\\\"space_url\\\":\\\"\\\",\\\"animation\\\":{},\\\"emots\\\":null}\"},{\"activity_identity\":\"\",\"activity_source\":0,\"not_show\":0}],\"kana;\",[49515343,\"nenpenAIagi\",0,0,0,10000,1,\"\"],[],[0,0,9868950,\"\\u003e50000\",0],[\"\",\"\"],0,0,null,{\"ts\":1673881110,\"ct\":\"762E818\"},0,0,null,null,0,7]}\u0000\u0000\u0000{\u0000\u0010\u0000\u0000\u0000\u0000\u0000\u0005\u0000\u0000\u0000\u0000{\"cmd\":\"WATCHED_CHANGE\",\"data\":{\"num\":35975380,\"text_small\":\"3597.5万\",\"text_large\":\"3597.5万人看过\"}}";
                    char[] chars = group.toCharArray();
                    //遇到\u0000 后面就可以不需要遍历了 直接终止
                    for (char aChar : chars) {
                        if (Objects.equals(0, aChar - 0)) {
                            break;
                        }
                        stringBuffer.append(String.valueOf(aChar));
                    }
                    try {
                        jsonObject = JSON.parseObject(stringBuffer.toString());
                        LiveRespDanMuVO liveRespDanMuVO = liveResponseMsgService.ToVOFromJson(jsonObject);
                        logger.info(" ===> 弹幕内容:{}", liveRespDanMuVO.toString());
                    } catch (Exception e) {
                        logger.error(" ===> error:{}", stringBuffer.toString());
                    }
                }
                offset += countPackageLength;
            }
        }
    }

    private ByteArrayOutputStream buildCertifyByte(JSONObject jsonObject) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        String jsonStr = jsonObject.toJSONString();
        int length = jsonStr.length();
        char c;
        for (int i = 0; i < length; i++) {
            c = jsonStr.charAt(i);
            if (c >= 0x010000 && c <= 0x10FFFF) {
                bytes.write(((c >> 18) & 0x07) | 0xF0);
                bytes.write(((c >> 12) & 0x3F) | 0x80);
                bytes.write(((c >> 6) & 0x3F) | 0x80);
                bytes.write((c & 0x3F) | 0x80);
            } else if (c >= 0x000800 && c <= 0x00FFFF) {
                bytes.write(((c >> 12) & 0x0F) | 0xE0);
                bytes.write(((c >> 6) & 0x3F) | 0x80);
                bytes.write((c & 0x3F) | 0x80);
            } else if (c >= 0x000080 && c <= 0x0007FF) {
                bytes.write(((c >> 6) & 0x1F) | 0xC0);
                bytes.write((c & 0x3F) | 0x80);
            } else {
                bytes.write(c & 0xFF);
            }
        }
        return bytes;
    }

 /*   private void certifyRequest(Integer roomId, String url) {
        logger.info("===" + "发送认证请求");
        JsScriptUtil.certifyRequest(roomId, url);
    }*/

    //    @Scheduled(fixedRate = 30 * 1000)
    private void heartbeat() {
        binaryHandleUtil.setUnit(0, UintEnum.UINT32, 0)
                .setUnit(4, UintEnum.UINT16, 16)
                .setUnit(6, UintEnum.UINT16, 1)
                .setUnit(8, UintEnum.UINT32, 2)
                .setUnit(12, UintEnum.UINT32, 1);
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(binaryHandleUtil.HexByteArray());
            this.session.getBasicRemote().sendBinary(byteBuffer);
            logger.info(" ===> heartbeat ing");
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }

    private Integer readIntFromByteArray(int[] byteBuffer, Integer start, Integer len) {
        Double result = 0.0;
        for (int i = len - 1; i >= 0; i--) {
            result += Math.pow(256, len - i - 1) * byteBuffer[start + i];
        }
        return result.intValue();
    }

    private void clearBuffer(StringBuffer buffer) {
        buffer.delete(0, buffer.length());
    }


}
LiveBackendApplication:
@SpringBootApplication(scanBasePackages = "com.liveQIQI")
@EnableScheduling
public class LiveBackendApplication {

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

}

yaml文件:

spring:
  application:
    name: livebackend
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher


server:
  port: 9090
# mysql
datasource:
  url: jdbc:mysql://localhost:3306/powerlive?useUnicode=true&characterEncoding=utf-8&useOldAliasMetadataBehavior=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
  username: root
  password: 677saber
  driver-class-name: com.mysql.cj.jdbc.Driver

pom.xml:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--        websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!--        json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>

        <!--        knife4j-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

   

    </dependencies>

结果:

完整代码地址:https://gitee.com/power-live/live-backend

(仓库分支选择test-live分支)