Commit 049d1154 by zhiwei

直播平台采集程序

parents
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhiwei.live</groupId>
<artifactId>live-crawler</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.zhiwei.tools</groupId>
<artifactId>zhiwei-tools</artifactId>
<version>0.1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.zhiwei.crawler</groupId>
<artifactId>crawler-core</artifactId>
<version>0.1.1-RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.33.Final</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.zhiwei.live.danmu.douyu;
/**
*
* 功能描述:斗鱼弹幕服务API
* 基于(斗鱼弹幕服务器第三方接入协议v1.6.2)开发
*
* @auther: coffee
* @date: 2018-07-04 15:45:56
* 修改日志:
*
*/
public class DouYuApi {
public static String LOGIN_REQ = "type@=loginreq/roomid@=%s/";
public static String JOIN_GROUP = "type@=joingroup/rid@=%s/gid@=%s/";
public static String KEEP_LIVE = "type@=mrkl/";
public static String LOGOUT = "type@=logout/";
}
package com.zhiwei.live.danmu.douyu;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.constants.DouYuConstants;
import com.zhiwei.live.danmu.douyu.constants.MsgType;
import com.zhiwei.live.danmu.douyu.entity.BaseMsg;
import com.zhiwei.live.danmu.douyu.entity.ChatMsg;
import com.zhiwei.live.danmu.douyu.entity.DgbMsg;
import com.zhiwei.live.danmu.douyu.entity.ErrorMsg;
import com.zhiwei.live.danmu.douyu.entity.GgbbMsg;
import com.zhiwei.live.danmu.douyu.entity.MsgEntity;
import com.zhiwei.live.danmu.douyu.entity.SpbcMsg;
import com.zhiwei.live.danmu.douyu.entity.SsdMsg;
import com.zhiwei.live.danmu.douyu.entity.UenterMsg;
import com.zhiwei.live.danmu.douyu.exceptions.DouYuSDKException;
import com.zhiwei.live.danmu.douyu.util.DataUtil;
import com.zhiwei.live.danmu.douyu.util.STTUtil;
import com.zhiwei.live.danmu.douyu.util.UUIDUtil;
/**
* 功能描述:斗鱼SDK
*
* @auther: coffee
* @date: 2018-07-04 15:19:51
* 修改日志:
*/
public class DouYuClient {
private final static Logger logger = LoggerFactory.getLogger(DouYuClient.class);
private String host; //API Host
private int port; //API 端口
private String roomId; //房间ID(房间号)
private String groupId = DouYuConstants.MASSIVE_GID; //分组ID
private Socket socket; //通讯socket对象
private Thread dataSyncThread; //异步读取消息线程
private Boolean isExitMark = false; //退出标记
private MessageHandler messageHandler;
private List<MessageListener> messageListenerList = new ArrayList<>();
public DouYuClient(String host, int port, String roomId) {
this.host = host;
this.port = port;
this.roomId = roomId;
//初始化
this.init();
}
/**
* 初始化Client
*/
private void init() {
logger.info("初始化斗鱼SDK_Client...");
this.connect();
}
/**
* 打开socket连接
*/
private void connect() {
try {
logger.info("从服务器({}:{})获取弹幕服务器数据", host, port);
socket = new Socket(host, port);
messageHandler = new MessageHandler(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 登入斗鱼弹幕服务器
*/
public DouYuClient login() {
logger.info("登录房间 {}", roomId);
String content = String.format(DouYuApi.LOGIN_REQ, roomId);
messageHandler.send(content);
return this;
}
/**
* 加入房间分组并接收房间消息
*
* @return
*/
private DouYuClient joinGroup(String groupId) {
if (DouYuConstants.MASSIVE_GID.equals(groupId)) {
logger.info("开启海量弹幕接收模式");
} else {
logger.info("关闭海量弹幕接收模式");
}
logger.info("加入分组({})并开始接收消息", groupId);
String content = String.format(DouYuApi.JOIN_GROUP, roomId, groupId);
messageHandler.send(content);
return this;
}
/**
* 注册消息监听器
*
* @param messageListener
* @return
*/
public DouYuClient registerMessageListener(MessageListener messageListener) {
logger.info("注册消息监听器:{}", messageListener.getClass());
this.messageListenerList.add(messageListener);
return this;
}
/**
* 开始同步并接收房间消息
*/
public DouYuClient sync() {
//加入海量弹幕分组,接收所有消息
this.joinGroup(groupId);
//开启异步线程接收房间消息
dataSyncThread = new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
while (true) {
//判断是否退出线程
if (isExitMark == true) {
break;
}
//读取弹幕消息
byte[] bytes = messageHandler.read();
String msg = new String(Arrays.copyOfRange(bytes, 8, bytes.length));
//获取消息类型
String msgType = DataUtil.getMsgType(msg);
if (msgType == null) {
logger.error("获取消息类型失败,消息:{}", msg);
continue;
}
//封装基础消息对象
BaseMsg msgBase = new BaseMsg();
msgBase.setUuid(UUIDUtil.simpleUUID());
msgBase.setType(msgType);
msgBase.setMessage(msg);
//根据不同的消息类型 序列化不同的 实体对象
MsgEntity entity = null;
if (MsgType.CHAT_MSG.equals(msgType)) {
entity = STTUtil.toBean(msg, ChatMsg.class);
} else if (MsgType.DGB.equals(msgType)) {
entity = STTUtil.toBean(msg, DgbMsg.class);
} else if (MsgType.GGBB.equals(msgType)) {
entity = STTUtil.toBean(msg, GgbbMsg.class);
} else if (MsgType.SPBC.equals(msgType)) {
entity = STTUtil.toBean(msg, SpbcMsg.class);
} else if (MsgType.SSD.equals(msgType)) {
entity = STTUtil.toBean(msg, SsdMsg.class);
} else if (MsgType.UENTER.equals(msgType)) {
entity = STTUtil.toBean(msg, UenterMsg.class);
} else if (MsgType.ERROR.equals(msgType)) {
entity = STTUtil.toBean(msg, ErrorMsg.class);
}
if (entity != null) {
entity.setMessage(msg);
entity.setUuid(msgBase.getUuid());
}
//消息监听器处理
for (MessageListener messageListener : messageListenerList) {
try {
//基础消息监听器处理
if (messageListener.getMsgClazz() == BaseMsg.class) {
messageListener.read(msgBase);
}
//指定类型消息监听器处理
else if (entity != null && messageListener.getMsgClazz() == entity.getClass()) {
messageListener.read(entity);
}
//String消息监听器处理
else if (messageListener.getMsgClazz() == String.class) {
messageListener.read(msg);
}
} catch (Exception e) {
logger.error("消息处理出现异常:", e);
}
}
//发送心跳消息保持通道
long end = System.currentTimeMillis();
if (end - start > 30000) {
doKeepLive();
start = System.currentTimeMillis();
}
//休眠1毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new DouYuSDKException(e);
}
}
//客户端关闭,断开socket通道
messageHandler.close();
logger.info("斗鱼弹幕SDK客户端已成功退出");
}
});
dataSyncThread.start();
return this;
}
/**
* 发送心跳消息,保持通道
*/
public void doKeepLive() {
String content = String.format(DouYuApi.KEEP_LIVE);
logger.info("发送心跳信息,保持通道中...");
messageHandler.send(content);
}
/**
* 发送登出消息,用于退出
*/
public void logout(){
String content = String.format(DouYuApi.LOGOUT);
logger.info("发送登出消息中...");
messageHandler.send(content);
}
public void exit() {
logout();
isExitMark = true;
logger.info("斗鱼弹幕SDK客户端正在退出中...");
}
}
package com.zhiwei.live.danmu.douyu;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
/**
*
* 功能描述:斗鱼消息实体
*
* @auther: coffee
* @date: 2018-07-04 15:06:45
* 修改日志:
*
*/
public class Message {
/**
* 请求消息体包含五部分:
* 1.计算后四部分的字节长度,占4个字节
* 2.内容同上
* 3.请求代码,固定,发到斗鱼是0xb1,0x02,0x00,0x00,接收是0xb2,0x02,0x00,0x00,4个字节
* 4.消息正文
* 5.尾部1个空字节
*/
private int[] length1;
private int[] length2;
private int[] magic;
private String content;
private int[] end;
public Message(String content) {
length1 = new int[]{calcMessageLength(content), 0x00, 0x00, 0x00};
length2 = new int[]{calcMessageLength(content), 0x00, 0x00, 0x00};
magic = new int[]{0xb1, 0x02, 0x00, 0x00};
this.content = content;
end = new int[]{0x00};
}
/**
* 计算消息体长度
*/
private int calcMessageLength(String content) {
return 4 + 4 + (content == null ? 0 : content.length()) + 1;
}
@Override
public String toString() {
return "Message{" +
"length1=" + Arrays.toString(length1) +
", length2=" + Arrays.toString(length2) +
", magic=" + Arrays.toString(magic) +
", content='" + content + '\'' +
", end=" + Arrays.toString(end) +
'}';
}
/**
* 将Message对象转化为字节数组
*/
public byte[] getBytes() throws IOException {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
//写入长度1
for(int b : length1){
byteArray.write(b);
}
//写入长度2
for(int b : length2){
byteArray.write(b);
}
//写入消息类型
for(int b : magic){
byteArray.write(b);
}
//写入消息内容
if(content != null){
byteArray.write(content.getBytes("ISO-8859-1"));
}
//写入尾部结束符
for(int b : end){
byteArray.write(b);
}
return byteArray.toByteArray();
}
}
package com.zhiwei.live.danmu.douyu;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.exceptions.DouYuSDKException;
/**
* 功能描述:消息处理助手
*
* @auther: coffee
* @date: 2018-07-04 15:11:13
* 修改日志:
*/
public class MessageHandler {
private final static Logger logger = LoggerFactory.getLogger(MessageHandler.class);
private Socket socket;
public MessageHandler(Socket socket) {
this.socket = socket;
}
/**
* 发送消息
*
* @param content
*/
public void send(String content) {
try {
Message message = new Message(content);
OutputStream out = socket.getOutputStream();
out.write(message.getBytes());
out.flush();
} catch (IOException e) {
throw new DouYuSDKException(e);
}
}
/**
* 读取消息
*
* @return
*/
public byte[] read(){
try {
InputStream inputStream = socket.getInputStream();
//下条信息的长度
int contentLen = 0;
//读取前4个字节,得到数据长度
for (int i = 0; i < 4; i++) {
int tmp = inputStream.read();
contentLen += tmp * Math.pow(16, 2 * i);
}
int len = 0;
int readLen = 0;
byte[] bytes = new byte[contentLen];
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
while ((len = inputStream.read(bytes, 0, contentLen - readLen)) != -1) {
byteArray.write(bytes, 0, len);
readLen += len;
if (readLen == contentLen) {
break;
}
}
return byteArray.toByteArray();
} catch (IOException e) {
throw new DouYuSDKException(e);
}
}
/**
* 关闭socket通道
*
* @throws IOException
*/
public void close(){
try {
socket.close();
} catch (IOException e) {
logger.warn("socket通道关闭异常",e);
}
}
}
package com.zhiwei.live.danmu.douyu;
import java.lang.reflect.ParameterizedType;
/**
* 功能描述:消息监听器
*
* @auther: coffee
* @date: 2018-07-04 16:22:22
* 修改日志:
*/
public abstract class MessageListener<T> {
private Class<T> msgClazz;
public MessageListener() {
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.msgClazz = (Class<T>) type.getActualTypeArguments()[0];
}
/**
* 消息读取
*/
public abstract void read(T message);
public Class<T> getMsgClazz() {
return msgClazz;
}
}
package com.zhiwei.live.danmu.douyu.constants;
/**
*
* 功能描述:斗鱼常量
*
* @auther: coffee
* @date: 2018-07-06 11:27:32
* 修改日志:
*
*/
public class DouYuConstants {
//海量弹幕分组
public final static String MASSIVE_GID = "-9999";
}
package com.zhiwei.live.danmu.douyu.constants;
/**
*
* 功能描述:消息类型
*
* @auther: coffee
* @date: 2018-07-07 16:22:36
* 修改日志:
*
*/
public class MsgType {
//登录响应
public static final String LOGIN_RES = "loginres";
//服务心跳响应
public static final String KEEP_LIVE = "loginres";
//弹幕消息
public static final String CHAT_MSG = "chatmsg";
//领取鱼丸暴击
public static final String ONLINE_GIFT = "onlinegift";
//赠送礼物消息
public static final String DGB = "dgb";
//用户进入房间
public static final String UENTER = "uenter";
//房间开播关播
public static final String RSS = "rss";
//房间贡献排行榜更新广播
public static final String RANK_LIST = "ranklist";
//超级弹幕
public static final String SSD = "ssd";
//房间内礼物广播
public static final String SPBC = "spbc";
//房间用户抢红包
public static final String GGBB = "ggbb";
//房间分区排名变化消息
public static final String RANK_UP = "rankup";
//错误信息
public static final String ERROR = "error";
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:通用消息实体
*
* @auther: coffee
* @date: 2018-07-07 17:16:27
* 修改日志:
*/
public class BaseMsg implements MsgEntity{
private String uuid;
private String type;
private String message;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public BaseMsg() {
}
public BaseMsg(String uuid, String type, String message) {
this.uuid = uuid;
this.type = type;
this.message = message;
}
@Override
public String toString() {
return "BaseMsg{" +
"uuid='" + uuid + '\'' +
", type='" + type + '\'' +
", message='" + message + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:弹幕消息
*
* @auther: coffee
* @date: 2018-07-04 19:44:16
* 修改日志:
*/
public class ChatMsg implements MsgEntity {
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 弹幕唯一ID
*/
private String cid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 房间ID
*/
private String rid;
/**
* 发送者 uid
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 消息内容
*/
private String txt;
/**
* 用户等级
*/
private String level;
/**
* 礼物头衔:默认值 0(表示没有头衔)
*/
private String gt;
/**
* 消息颜色:默认值 0(表示默认颜色弹幕)
*/
private String col;
/**
* 客户端类型:默认值 0
*/
private String ct;
/**
* 房间权限组:默认值 1(表示普通权限用户)
*/
private String rg;
/**
* 平台权限组:默认值 1(表示普通权限用户)
*/
private String pg;
/**
* 弹幕具体类型: 默认值 0(普通弹幕)
*/
private String cmt;
/**
* 用户头像
*/
private String ic;
/**
* 贵族等级
*/
private String nl;
/**
* 贵族弹幕标识,0-非贵族弹幕,1-贵族弹幕,默认值 0
*/
private String nc;
/**
* 粉丝牌名称
*/
private String bnn;
/**
* 粉丝牌等级
*/
private String bl;
/**
* 粉丝牌关联房间号
*/
private String brid;
/**
* 徽章信息校验码
*/
private String hc;
/**
* 主播等级
*/
private String ol;
/**
* 是否反向弹幕标记: 0-普通弹幕,1-反向弹幕, 默认值 0
*/
private String rev;
/**
* 是否高亮弹幕标记: 0-普通,1-高亮, 默认值 0
*/
private String hl;
/**
* 是否粉丝弹幕标记: 0-非粉丝弹幕,1-粉丝弹幕, 默认值 0
*/
private String ifs;
/**
* 弹幕发送时间
*/
private String cst;
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getTxt() {
return txt;
}
public void setTxt(String txt) {
this.txt = txt;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getGt() {
return gt;
}
public void setGt(String gt) {
this.gt = gt;
}
public String getCol() {
return col;
}
public void setCol(String col) {
this.col = col;
}
public String getCt() {
return ct;
}
public void setCt(String ct) {
this.ct = ct;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public String getPg() {
return pg;
}
public void setPg(String pg) {
this.pg = pg;
}
public String getCmt() {
return cmt;
}
public void setCmt(String cmt) {
this.cmt = cmt;
}
public String getIc() {
return ic;
}
public void setIc(String ic) {
this.ic = ic;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
public String getNc() {
return nc;
}
public void setNc(String nc) {
this.nc = nc;
}
public String getBnn() {
return bnn;
}
public void setBnn(String bnn) {
this.bnn = bnn;
}
public String getBl() {
return bl;
}
public void setBl(String bl) {
this.bl = bl;
}
public String getBrid() {
return brid;
}
public void setBrid(String brid) {
this.brid = brid;
}
public String getHc() {
return hc;
}
public void setHc(String hc) {
this.hc = hc;
}
public String getOl() {
return ol;
}
public void setOl(String ol) {
this.ol = ol;
}
public String getRev() {
return rev;
}
public void setRev(String rev) {
this.rev = rev;
}
public String getHl() {
return hl;
}
public void setHl(String hl) {
this.hl = hl;
}
public String getIfs() {
return ifs;
}
public void setIfs(String ifs) {
this.ifs = ifs;
}
public String getCst() {
return cst;
}
public void setCst(String cst) {
this.cst = cst;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "ChatMsg{" +
"cid='" + cid + '\'' +
", gid='" + gid + '\'' +
", rid='" + rid + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", txt='" + txt + '\'' +
", level='" + level + '\'' +
", gt='" + gt + '\'' +
", col='" + col + '\'' +
", ct='" + ct + '\'' +
", rg='" + rg + '\'' +
", pg='" + pg + '\'' +
", cmt='" + cmt + '\'' +
", ic='" + ic + '\'' +
", nl='" + nl + '\'' +
", nc='" + nc + '\'' +
", bnn='" + bnn + '\'' +
", bl='" + bl + '\'' +
", brid='" + brid + '\'' +
", hc='" + hc + '\'' +
", ol='" + ol + '\'' +
", rev='" + rev + '\'' +
", hl='" + hl + '\'' +
", ifs='" + ifs + '\'' +
", cst='" + cst + '\'' +
'}';
}
/**
* 转换为弹幕消息展示 (name: txt)
*
* @return
*/
public String toChatStr() {
return String.format("%s: %s", nn, txt);
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:赠送礼物
*
* @auther: coffee
* @date: 2018-07-08 23:59:55
* 修改日志:
*
*/
public class DgbMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 礼物ID
*/
private String gfid;
/**
* 礼物显示样式
*/
private String gs;
/**
* 用户ID
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 大礼物标识:默认值为 0(表示是小礼物)
*/
private String bg;
/**
* 礼物个数:默认值 1(表示 1 个礼物)
*/
private String gfcnt;
/**
* 礼物连击次数:默认值 1(表示 1 连击)
*/
private String hits;
/**
* 贵族等级
*/
private String nl;
/**
* 粉丝牌名称
*/
private String bnn;
/**
* 粉丝牌等级
*/
private String bl;
/**
* 粉丝牌关联房间号
*/
private String brid;
/**
* 徽章信息校验码
*/
private String hc;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getGfid() {
return gfid;
}
public void setGfid(String gfid) {
this.gfid = gfid;
}
public String getGs() {
return gs;
}
public void setGs(String gs) {
this.gs = gs;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getBg() {
return bg;
}
public void setBg(String bg) {
this.bg = bg;
}
public String getGfcnt() {
return gfcnt;
}
public void setGfcnt(String gfcnt) {
this.gfcnt = gfcnt;
}
public String getHits() {
return hits;
}
public void setHits(String hits) {
this.hits = hits;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
public String getBnn() {
return bnn;
}
public void setBnn(String bnn) {
this.bnn = bnn;
}
public String getBl() {
return bl;
}
public void setBl(String bl) {
this.bl = bl;
}
public String getBrid() {
return brid;
}
public void setBrid(String brid) {
this.brid = brid;
}
public String getHc() {
return hc;
}
public void setHc(String hc) {
this.hc = hc;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "DgbMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", gfid='" + gfid + '\'' +
", gs='" + gs + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", bg='" + bg + '\'' +
", gfcnt='" + gfcnt + '\'' +
", hits='" + hits + '\'' +
", nl='" + nl + '\'' +
", bnn='" + bnn + '\'' +
", bl='" + bl + '\'' +
", brid='" + brid + '\'' +
", hc='" + hc + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:错误消息
* <p>
* 0 操作成功
* 51 数据传输出错
* 52 服务器关闭
* 204 房间id错误
*
* @auther: coffee
* @date: 2018-07-16 16:09:02
* 修改日志:
*/
public class ErrorMsg implements MsgEntity {
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 错误代码
*/
private String code;
/**
* 错误描述
*/
private String desc;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
if (desc == null && code != null) {
switch (code) {
case "0":
desc = "斗鱼弹幕客户端-操作成功";
break;
case "51":
desc = "斗鱼弹幕客户端-数据传输出错";
break;
case "52":
desc = "斗鱼弹幕客户端-服务已登出 或 服务器关闭";
break;
case "204":
desc = "斗鱼弹幕客户端-房间ID错误";
break;
}
}
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:房间内用户抢红包消息
*
* @auther: coffee
* @date: 2018-07-09 11:36:12
* 修改日志:
*
*/
public class GgbbMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 抢到的鱼丸数量
*/
private String sl;
/**
* 礼包产生者 id
*/
private String sid;
/**
* 抢礼包者 id
*/
private String did;
/**
* 礼包产生者昵称
*/
private String snk;
/**
* 抢礼包者昵称
*/
private String dnk;
/**
* 礼包类型
*/
private String rpt;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getSl() {
return sl;
}
public void setSl(String sl) {
this.sl = sl;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getDid() {
return did;
}
public void setDid(String did) {
this.did = did;
}
public String getSnk() {
return snk;
}
public void setSnk(String snk) {
this.snk = snk;
}
public String getDnk() {
return dnk;
}
public void setDnk(String dnk) {
this.dnk = dnk;
}
public String getRpt() {
return rpt;
}
public void setRpt(String rpt) {
this.rpt = rpt;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "GgbbMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", sl='" + sl + '\'' +
", sid='" + sid + '\'' +
", did='" + did + '\'' +
", snk='" + snk + '\'' +
", dnk='" + dnk + '\'' +
", rpt='" + rpt + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:
*
* @auther: coffee
* @date: 2018-07-07 17:27:36
* 修改日志:
*
*/
public interface MsgEntity {
String getMessage();
void setMessage(String message);
String getUuid();
void setUuid(String uuid);
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:礼物广播消息
*
* @auther: coffee
* @date: 2018-07-09 10:28:48
* 修改日志:
*
*/
public class SpbcMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 赠送者昵称
*/
private String sn;
/**
* 受赠者昵称
*/
private String dn;
/**
* 礼物名称
*/
private String gn;
/**
* 礼物数量
*/
private String gc;
/**
* 赠送房间
*/
private String drid;
/**
* 广播样式
*/
private String gs;
/**
* 是否有礼包(0-无礼包,1-有礼包)
*/
private String gb;
/**
* 广播展现样式(1-火箭,2-飞机)
*/
private String es;
/**
* 礼物 id
*/
private String gfid;
/**
* 特效 id
*/
private String eid;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getDn() {
return dn;
}
public void setDn(String dn) {
this.dn = dn;
}
public String getGn() {
return gn;
}
public void setGn(String gn) {
this.gn = gn;
}
public String getGc() {
return gc;
}
public void setGc(String gc) {
this.gc = gc;
}
public String getDrid() {
return drid;
}
public void setDrid(String drid) {
this.drid = drid;
}
public String getGs() {
return gs;
}
public void setGs(String gs) {
this.gs = gs;
}
public String getGb() {
return gb;
}
public void setGb(String gb) {
this.gb = gb;
}
public String getEs() {
return es;
}
public void setEs(String es) {
this.es = es;
}
public String getGfid() {
return gfid;
}
public void setGfid(String gfid) {
this.gfid = gfid;
}
public String getEid() {
return eid;
}
public void setEid(String eid) {
this.eid = eid;
}
@Override
public String toString() {
return "SpbcMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", sn='" + sn + '\'' +
", dn='" + dn + '\'' +
", gn='" + gn + '\'' +
", gc='" + gc + '\'' +
", drid='" + drid + '\'' +
", gs='" + gs + '\'' +
", gb='" + gb + '\'' +
", es='" + es + '\'' +
", gfid='" + gfid + '\'' +
", eid='" + eid + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:超级弹幕消息
*
* @auther: coffee
* @date: 2018-07-09 10:24:53
* 修改日志:
*
*/
public class SsdMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 超级弹幕ID
*/
private String sdid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 跳转房间ID
*/
private String trid;
/**
* 超级弹幕内容
*/
private String content;
public String getSdid() {
return sdid;
}
public void setSdid(String sdid) {
this.sdid = sdid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getTrid() {
return trid;
}
public void setTrid(String trid) {
this.trid = trid;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "SsdMsg{" +
"sdid='" + sdid + '\'' +
", rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", trid='" + trid + '\'' +
", content='" + content + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:用户进房通知消息
*
* @auther: coffee
* @date: 2018-07-09 00:28:36
* 修改日志:
*
*/
public class UenterMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 用户ID
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 用户等级
*/
private String level;
/**
* 礼物头衔:默认值 0(表示没有头衔)
*/
private String gt;
/**
* 房间权限组:默认值 1(表示普通权限用户)
*/
private String rg;
/**
* 平台权限组:默认值 1(表示普通权限用户)
*/
private String pg;
/**
* 用户头像
*/
private String ic;
/**
* 贵族等级
*/
private String nl;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getGt() {
return gt;
}
public void setGt(String gt) {
this.gt = gt;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public String getPg() {
return pg;
}
public void setPg(String pg) {
this.pg = pg;
}
public String getIc() {
return ic;
}
public void setIc(String ic) {
this.ic = ic;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
@Override
public String toString() {
return "UenterMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", level='" + level + '\'' +
", gt='" + gt + '\'' +
", rg='" + rg + '\'' +
", pg='" + pg + '\'' +
", ic='" + ic + '\'' +
", nl='" + nl + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.enums;
/**
* 功能描述:贵族等级
*
* @auther: coffee
* @date: 2018-07-08 23:38:22
* 修改日志:
*/
public enum NobleLevel {
LEVEL_0("0", "游侠"),
LEVEL_1("1", "骑士"),
LEVEL_2("2", "子爵"),
LEVEL_3("3", "伯爵"),
LEVEL_4("4", "公爵"),
LEVEL_5("5", "国外"),
LEVEL_6("6", "皇帝"),;
NobleLevel(String code, String description) {
this.code = code;
this.description = description;
}
private String code;
private String description;
}
package com.zhiwei.live.danmu.douyu.exceptions;
/**
*
* 功能描述:
*
* @auther: coffee
* @date: 2018-07-04 15:50:12
* 修改日志:
*
*/
public class DouYuSDKException extends RuntimeException{
private static final long serialVersionUID = -2974352294232725589L;
public DouYuSDKException(Exception e) {
super(e);
}
}
package com.zhiwei.live.danmu.douyu.exceptions;
/**
* 工具类异常
*
* @author coffee
*/
public class UtilException extends RuntimeException {
private static final long serialVersionUID = 8247610319171014183L;
public UtilException(Throwable e) {
super(UtilException.getMessage(e), e);
}
public UtilException(String message) {
super(message);
}
public UtilException(String messageTemplate, Object... params) {
super(String.format(messageTemplate, params));
}
public UtilException(String message, Throwable throwable) {
super(message, throwable);
}
public UtilException(Throwable throwable, String messageTemplate, Object... params) {
super(String.format(messageTemplate, params), throwable);
}
/**
* 获得完整消息,包括异常名
*
* @param e 异常
* @return 完整消息
*/
public static String getMessage(Throwable e) {
if (null == e) {
return "null";
}
return String.format("{}: {}", e.getClass().getSimpleName(), e.getMessage());
}
}
package com.zhiwei.live.danmu.douyu.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.MessageListener;
import com.zhiwei.live.danmu.douyu.entity.BaseMsg;
/**
*
* 功能描述:默认的BaseMsg消息监听器
*
* @auther: coffee
* @date: 2018-07-07 18:48:48
* 修改日志:
*
*/
public class DefaultBaseMsgListener extends MessageListener<BaseMsg> {
private final static Logger logger = LoggerFactory.getLogger(DefaultBaseMsgListener.class);
@Override
public void read(BaseMsg message) {
logger.info(message.toString());
}
}
package com.zhiwei.live.danmu.douyu.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.MessageListener;
import com.zhiwei.live.danmu.douyu.entity.ChatMsg;
/**
* 功能描述:默认ChatMsg消息监听处理器
*
* @auther: coffee
* @date: 2018-07-08 21:23:08
* 修改日志:
*/
public class DefaultChatMsgListener extends MessageListener<ChatMsg> {
private final static Logger logger = LoggerFactory.getLogger(DefaultBaseMsgListener.class);
@Override
public void read(ChatMsg message) {
logger.info(message.toChatStr());
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.lang.reflect.Array;
/**
* 数组工具类
*
* @author Looly
*
*/
public class ArrayUtil {
/**
* 将新元素添加到已有数组中<br>
* 添加新元素会生成一个新的数组,不影响原数组
*
* @param <T> 数组元素类型
* @param buffer 已有数组
* @param newElements 新元素
* @return 新数组
*/
@SafeVarargs
public static <T> T[] append(T[] buffer, T... newElements) {
if(isEmpty(buffer)) {
return newElements;
}
return insert(buffer, buffer.length, newElements);
}
/**
* 将新元素插入到到已有数组中的某个位置<br>
* 添加新元素会生成一个新的数组,不影响原数组<br>
* 如果插入位置为为负数,从原数组从后向前计数,若大于原数组长度,则空白处用null填充
*
* @param <T> 数组元素类型
* @param buffer 已有数组
* @param index 插入位置,此位置为对应此位置元素之前的空档
* @param newElements 新元素
* @return 新数组
* @since 4.0.8
*/
@SafeVarargs
public static <T> T[] insert(T[] buffer, int index, T... newElements) {
if (isEmpty(newElements)) {
return buffer;
}
if(isEmpty(buffer)) {
return newElements;
}
if (index < 0) {
index = (index % buffer.length) + buffer.length;
}
final T[] result = newArray(buffer.getClass().getComponentType(), Math.max(buffer.length, index) + newElements.length);
System.arraycopy(buffer, 0, result, 0, Math.min(buffer.length, index));
System.arraycopy(newElements, 0, result, index, newElements.length);
if (index < buffer.length) {
System.arraycopy(buffer, index, result, index + newElements.length, buffer.length - index);
}
return result;
}
// ---------------------------------------------------------------------- isEmpty
/**
* 数组是否为空
*
* @param <T> 数组元素类型
* @param array 数组
* @return 是否为空
*/
@SuppressWarnings("unchecked")
public static <T> boolean isEmpty(final T... array) {
return array == null || array.length == 0;
}
// ---------------------------------------------------------------------- isNotEmpty
/**
* 数组是否为非空
*
* @param <T> 数组元素类型
* @param array 数组
* @return 是否为非空
*/
@SuppressWarnings("unchecked")
public static <T> boolean isNotEmpty(final T... array) {
return (array != null && array.length != 0);
}
/**
* 新建一个空数组
*
* @param <T> 数组元素类型
* @param componentType 元素类型
* @param newSize 大小
* @return 空数组
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArray(Class<?> componentType, int newSize) {
return (T[]) Array.newInstance(componentType, newSize);
}
/**
* 新建一个空数组
*
* @param <T> 数组元素类型
* @param newSize 大小
* @return 空数组
* @since 3.3.0
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArray(int newSize) {
return (T[]) new Object[newSize];
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 功能描述:数据处理工具类
*
* @auther: coffee
* @date: 2018-07-07 16:23:11
* 修改日志:
*/
public class DataUtil {
static Pattern nickNamePattern = Pattern.compile("/nn@=(.*?)/");
static Pattern msgTextPattern = Pattern.compile("/txt@=(.*?)/");
static Pattern typePattern = Pattern.compile("type@=(.*?)/");
private static String match(String source, Pattern pattern, int groupId) {
Matcher matcher = pattern.matcher(source);
if (matcher.find()) {
return matcher.group(groupId);
} else {
return "";
}
}
public static String getNickName(String source) {
return match(source, nickNamePattern, 1);
}
public static String getMsg(String source) {
return match(source, msgTextPattern, 1);
}
/**
* 从消息中获取消息类型
* @param source
* @return
*/
public static String getMsgType(String source) {
return match(source, typePattern, 1);
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.lang.reflect.Field;
import com.zhiwei.live.danmu.douyu.exceptions.UtilException;
/**
* 反射工具类
*
* @author coffee
*/
public class ReflectUtil {
/**
* 字段缓存
*/
private static final SimpleCache<Class<?>, Field[]> FIELDS_CACHE = new SimpleCache<>();
/**
* 获得一个类中所有字段列表,包括其父类中的字段
*
* @param beanClass 类
* @return 字段列表
* @throws SecurityException 安全检查异常
*/
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
Field[] allFields = FIELDS_CACHE.get(beanClass);
if (null != allFields) {
return allFields;
}
allFields = getFieldsDirectly(beanClass, true);
return FIELDS_CACHE.put(beanClass, allFields);
}
/**
* 获得一个类中所有字段列表,直接反射获取,无缓存
*
* @param beanClass 类
* @param withSuperClassFieds 是否包括父类的字段列表
* @return 字段列表
* @throws SecurityException 安全检查异常
*/
public static Field[] getFieldsDirectly(Class<?> beanClass, boolean withSuperClassFieds) throws SecurityException {
if (beanClass == null) {
throw new UtilException("反射获取字段出现异常,参数 beanClass 为空");
}
Field[] allFields = null;
Class<?> searchType = beanClass;
Field[] declaredFields;
while (searchType != null) {
declaredFields = searchType.getDeclaredFields();
if (null == allFields) {
allFields = declaredFields;
} else {
allFields = ArrayUtil.append(allFields, declaredFields);
}
searchType = withSuperClassFieds ? searchType.getSuperclass() : null;
}
return allFields;
}
/**
* 查找指定类中的所有字段(包括非public字段),也包括父类和Object类的字段, 字段不存在则返回<code>null</code>
*
* @param beanClass 被查找字段的类,不能为null
* @param name 字段名
* @return 字段
* @throws SecurityException 安全异常
*/
public static Field getField(Class<?> beanClass, String name) throws SecurityException {
final Field[] fields = getFields(beanClass);
if (ArrayUtil.isNotEmpty(fields)) {
for (Field field : fields) {
if ((name.equals(field.getName()))) {
return field;
}
}
}
return null;
}
/**
* 设置字段值
*
* @param obj 对象
* @param fieldName 字段名
* @param value 值,值类型必须与字段类型匹配,不会自动转换对象类型
* @throws UtilException 包装IllegalAccessException异常
*/
public static void setFieldValue(Object obj, String fieldName, Object value) throws UtilException {
if (obj == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
if (fieldName == null || "".equals(fieldName)) {
throw new UtilException("[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
}
setFieldValue(obj, getField(obj.getClass(), fieldName), value);
}
/**
* 设置字段值
*
* @param obj 对象
* @param field 字段
* @param value 值,值类型必须与字段类型匹配,不会自动转换对象类型
* @throws UtilException UtilException 包装IllegalAccessException异常
*/
public static void setFieldValue(Object obj, Field field, Object value) throws UtilException {
if (obj == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
if (field == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
field.setAccessible(true);
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
throw new UtilException(e, "IllegalAccess for {}.{}", obj.getClass(), field.getName());
}
}
/**
* 实例化对象
*
* @param <T> 对象类型
* @param clazz 类名
* @return 对象
* @throws UtilException 包装各类异常
*/
@SuppressWarnings("unchecked")
public static <T> T newInstance(String clazz) throws UtilException {
try {
return (T) Class.forName(clazz).newInstance();
} catch (Exception e) {
throw new UtilException(e, "Instance class [{}] error!", clazz);
}
}
/**
* 实例化对象
*
* @param clazz 对象类型
* @param <T> 类名
* @return 对象
* @throws UtilException 包装各类异常
*/
public static <T> T newInstance(Class<T> clazz) throws UtilException {
try {
return (T) clazz.newInstance();
} catch (Exception e) {
throw new UtilException(e, "Instance class [{}] error!", clazz);
}
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* 功能描述: 斗鱼通讯序列化工具<br>
* 协议版本: 1.62<br>
* <p>
* 为增强兼容性、可读性斗鱼后台通讯协议采用文本形式的明文数据。同时针
* 对本平台数据特点,斗鱼自创序列化、反序列化算法。即 STT 序列化。下面详
* 细介绍 STT 序列化和反序列化。STT 序列化支持键值对类型、数组类型。规定<br>
* 如下:<br>
* 1. 键 key 和值 value 直接采用‘@=’分割<br>
* 2. 数组采用‘/’分割<br>
* 3. 如果 key 或者 value 中含有字符‘/’,则使用‘@S’转义<br>
* 4. 如果 key 或者 value 中含有字符‘@’,使用‘@A’转义<br>
*
* @auther: coffee
* @date: 2018-07-08 18:18:50
* 修改日志:
*/
public class STTUtil {
/**
* STT字符串转换为Map
*
* @param sttString
* @return
*/
public static Map<String, Object> toMap(String sttString) {
Map<String, Object> map = new HashMap<>();
String[] arrays = sttString.split("/");
for (String s : arrays) {
if (s == null || "".equals(s)) {
continue;
}
String[] kvArrays = s.split("@=");
String key = kvArrays[0];
String value = null;
if (kvArrays.length > 1) {
value = kvArrays[1];
}
key = decrypt(key); //对key中的特殊字符解码
value = decrypt(decrypt(value)); //对value中的特殊字符解码(目前最多2次解码)
map.put(key, value);
}
return map;
}
/**
* STT字符串转换为bean
*
* @param sttString
* @param beanClass
* @param <T>
* @return
*/
public static <T> T toBean(String sttString, Class<T> beanClass) {
T obj = ReflectUtil.newInstance(beanClass);
Map<String, Object> dataMap = toMap(sttString);
Field[] fields = ReflectUtil.getFields(beanClass);
for (Field field : fields) {
String fieldName = field.getName();
Object value = dataMap.get(fieldName);
ReflectUtil.setFieldValue(obj, field, value);
}
return obj;
}
/**
* 解码key或value中的转码字符<br>
* 如果给定的值为空,返回null<br>
* (如果 key 或者 value 中含有字符‘/’,则使用‘@S’转义)<br>
* (如果 key 或者 value 中含有字符‘@’,使用‘@A’转义)<br>
*
* @param str
* @return
*/
public static String decrypt(String str) {
if (str == null) {
return str;
}
if (str.indexOf("@S") > -1 || str.indexOf("@A") > -1) {
str = str.replaceAll("@S", "/");
str = str.replaceAll("@A", "@");
}
return str;
}
public static void main(String[] args) {
String msg = "type@=loginres/userid@=0/roomgroup@=0/pg@=0/sessionid@=0/username@=/nickname@=/live_stat@=0/is_illegal@=0/ill_ct@=/ill_ts@=0/now@=0/ps@=0/es@=0/it@=0/its@=0/npv@=0/best_dlev@=0/cur_lev@=0/nrc@=0/ih@=0/sid@=72983/sahf@=0/sceneid@=0/";
msg = "type@=uenter/rid@=4835718/uid@=20003668/nn@=须予银鞍绣毂迎倾心人/level@=39/rg@=4/ic@=avatar_v3@S201807@S79596c14f4a7b4dd245d7a0a33f63ca5/nl@=3/rni@=0/el@=eid@AA=1500000113@ASetp@AA=1@ASsc@AA=1@ASef@AA=0@AS@S/sahf@=0/wgei@=0/cbid@=11011/fl@=18/";
Map<String, Object> dataMap = STTUtil.toMap(msg);
for (String key : dataMap.keySet()) {
System.out.println("key:" + key + " val:" + dataMap.get(key));
}
System.out.println(decrypt("eid@AA=1500000113@ASetp@AA=1@ASsc@AA=1@ASef@AA=0@AS@S"));
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
*/
public class SimpleCache<K, V> {
/**
* 池
*/
private final Map<K, V> cache = new WeakHashMap<>();
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
private final ReadLock readLock = cacheLock.readLock();
private final WriteLock writeLock = cacheLock.writeLock();
/**
* 从缓存池中查找值
*
* @param key 键
* @return 值
*/
public V get(K key) {
// 尝试读取缓存
readLock.lock();
V value;
try {
value = cache.get(key);
} finally {
readLock.unlock();
}
return value;
}
/**
* 放入缓存
*
* @param key 键
* @param value 值
* @return 值
*/
public V put(K key, V value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
return value;
}
/**
* 移除缓存
*
* @param key 键
* @return 移除的值
*/
public V remove(K key) {
writeLock.lock();
try {
return cache.remove(key);
} finally {
writeLock.unlock();
}
}
/**
* 清空缓存池
*/
public void clear() {
writeLock.lock();
try {
this.cache.clear();
} finally {
writeLock.unlock();
}
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.UUID;
/**
*
* 功能描述:UUID工具类
*
* @auther: coffee
* @date: 2018-07-07 18:26:09
* 修改日志:
*
*/
public class UUIDUtil {
/**
* @return 随机UUID
*/
public static String randomUUID() {
return UUID.randomUUID().toString();
}
/**
* 简化的UUID,去掉了横线
*
* @return 简化的UUID,去掉了横线
* @since 3.2.2
*/
public static String simpleUUID() {
return randomUUID().replace("-", "");
}
}
package com.zhiwei.live.danmu.pandamtv;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhiwei.crawler.core.HttpBoot;
import com.zhiwei.crawler.core.RequestUtils;
import com.zhiwei.live.danmu.pandamtv.util.Connector;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.timeout.IdleStateHandler;
import okhttp3.Request;
import okhttp3.Response;
/**
* @ClassName: PandaClient
* @Description: TODO
* @author 0xff
* @date Jan 25, 2019 3:24:33 PM
*/
public class PandaClient {
private static HttpBoot httpBoot = new HttpBoot();
public static void main(String[] args) throws Exception {
String roomId = "337852";
Request request = RequestUtils.wrapGet("http://riven.panda.tv/chatroom/getinfo?roomid=" + roomId);
String host = null;
int port = -1;
JSONObject json = null;
try(Response response = httpBoot.syncCall(request)) {
json = JSON.parseObject(response.body().string());
if(json.getInteger("errno") != 0) {
throw new IllegalStateException("房间号: " + roomId + " 不存在");
}
json = json.getJSONObject("data");
String[] address = json.getJSONArray("chat_addr_list").getString(0).split(":");
host = address[0];
port = Integer.parseInt(address[1]);
} catch(Exception e) {
throw new IllegalArgumentException("获取聊天服务器地址失败", e);
}
String appid = json.getString("appid");
String rid = json.getString("rid");
String sign = json.getString("sign");
String authType = json.getString("authType");
String ts = json.getString("ts");
Connector.asynchronizedTcpConnect(new NioEventLoopGroup(), host, port,
new IdleStateHandler(0, 0, 45), new PandaMessageHandler(appid,rid,ts, sign, authType)).sync();
}
}
package com.zhiwei.live.danmu.pandamtv;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.zhiwei.live.danmu.pandamtv.entity.PandamMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/**
* @ClassName: PandaMessageHandler
* @Description: TODO
* @author 0xff
* @date Jan 25, 2019 11:03:20 AM
*/
public class PandaMessageHandler extends ChannelInboundHandlerAdapter {
private static final byte[] FIRST_REQ = new byte[] { 0x00, 0x06, 0x00, 0x02, 0x00 };
private static final byte[] FIRST_RES = new byte[] { 0x00, 0x06, 0x00, 0x06 };
private static final byte[] PING = new byte[] { 0x00, 0x06, 0x00, 0x00 };
private static final byte[] REQ = new byte[] { 0x00, 0x06, 0x00, 0x03 };
private static Pattern pattern = Pattern.compile("\\{\"type\":\"1\".+?\\}\\}");
private String rid;
private String appid;
private String ts;
private String sign;
private String authType;
/**
* Constructor
*
* @param roomId
* 房间号
*/
public PandaMessageHandler(String appid, String rid, String ts, String sign, String authType) {
this.appid = appid;
this.rid = rid;
this.sign = sign;
this.authType = authType;
this.ts = ts;
}
/*
* @see
* io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.
* ChannelHandlerContext)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("TCP 连接建立成功: " + ctx.channel());
byte[] body = StringUtils
.join("u:", rid, "@", appid, "\nk:1\nt:300\nts:", ts, "\nsign:", sign, "\nauthtype:", authType)
.getBytes();
ByteBuf loginMsg = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(FIRST_REQ),
Unpooled.buffer(1).writeByte(body.length), Unpooled.wrappedBuffer(body));
System.out.println("发送登录消息: \n" + ByteBufUtil.prettyHexDump(loginMsg));
ctx.writeAndFlush(loginMsg);
}
/*
* @see
* io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.
* ChannelHandlerContext, java.lang.Object)
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
// System.out.println("收到消息: \n" + ByteBufUtil.prettyHexDump(buf));
String source = buf.toString(CharsetUtil.UTF_8);
Matcher matcher = pattern.matcher(source);
while(matcher.find()) {
JSONObject dataJson = JSONObject.parseObject(matcher.group());
PandamMessage pandamMessage = new PandamMessage(dataJson);
System.out.println(pandamMessage);
}
}
ReferenceCountUtil.release(msg);
}
/*
* @see
* io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.
* channel.ChannelHandlerContext, java.lang.Object)
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.ALL_IDLE) {
ByteBuf pingMsg = Unpooled.wrappedBuffer(PING);
System.out.println("发送心跳消息: \n" + ByteBufUtil.prettyHexDump(pingMsg));
ctx.writeAndFlush(pingMsg);
} else {
ctx.fireUserEventTriggered(evt);
}
} else {
ctx.fireUserEventTriggered(evt);
}
}
}
package com.zhiwei.live.danmu.pandamtv.entity;
import com.alibaba.fastjson.JSONObject;
public class PandamMessage {
String messageType; //弹幕消息类型
String time; //弹幕发布时间
String user_id; //发布者uid
String nickName; //发布者昵称
String content; //弹幕内容
String room_id; //直播间信息
public PandamMessage(JSONObject json) throws Exception {
constructJson(json);
}
private void constructJson(JSONObject json) throws Exception{
try {
messageType = json.getString("type");
time = json.getString("time");
user_id = json.getJSONObject("data").getJSONObject("from").getString("rid");;
nickName = json.getJSONObject("data").getJSONObject("from").getString("nickName");
content = json.getJSONObject("data").getString("content");
room_id = json.getJSONObject("data").getJSONObject("to").getString("toroom");
} catch (Exception e) {
throw new Exception();
}
}
@Override
public String toString() {
return "new PandamMessage["
+ "user_id = " + user_id
+ ", nickName = " + nickName
+ ", messageType = " + messageType
+ ", time = " + time
+ ", content = " + content
+ ", room_id = " + room_id
+ "]";
}
public String getMessageType() {
return messageType;
}
public String getTime() {
return time;
}
public String getUser_id() {
return user_id;
}
public String getNickName() {
return nickName;
}
public String getContent() {
return content;
}
public String getRoom_id() {
return room_id;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
public void setTime(String time) {
this.time = time;
}
public void setUser_id(String user_id) {
this.user_id = user_id;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public void setContent(String content) {
this.content = content;
}
public void setRoom_id(String room_id) {
this.room_id = room_id;
}
}
package com.zhiwei.live.danmu.pandamtv.listener;
import java.util.List;
import java.util.Vector;
import com.zhiwei.live.danmu.pandamtv.entity.PandamMessage;
public class PandamMessageListener extends Thread{
public static List<PandamMessage> messages = new Vector<>();
}
package com.zhiwei.live.danmu.pandamtv.util;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @ClassName: Connector
* @Description: 网络连接创建器
* @author 0xff
* @date 2018年6月15日 下午5:07:47
*/
public final class Connector {
/**
* Constructor
*/
private Connector() {
}
/**
* 异步创建到指定地址的 TCP 连接
*
* @param eventLoopGroup
* 线程组
* @param host
* 连接地址
* @param port
* 连接端口
* @param handlers
* 处理器
* @return ChannelFuture
*/
public static ChannelFuture asynchronizedTcpConnect(EventLoopGroup eventLoopGroup, String host, int port,
ChannelHandler... handlers) {
Bootstrap bootstrap = buildTcpBootstrap(eventLoopGroup, handlers);
return bootstrap.connect(new InetSocketAddress(host, port));
}
/**
* 同步创建到指定地址的 TCP 连接
*
* @param eventLoopGroup
* 线程组
* @param host
* 连接地址
* @param port
* 连接端口
* @param Handlers
* 处理器
* @return ChannelFuture
*/
public static ChannelFuture synchronizedTcpConnect(EventLoopGroup eventLoopGroup, String host, int port,
ChannelHandler... handlers) {
ChannelFuture future = null;
try {
future = asynchronizedTcpConnect(eventLoopGroup, host, port, handlers).await();
if (!future.isSuccess()) {
throw new IOException("Failed to connect [" + host + ":" + port + "]");
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
return future;
}
/**
* 异步创建 UDP 连接
*
* @param eventLoopGroup
* 线程组
* @param handlers
* 处理器
* @return ChannelFuture
*/
public static ChannelFuture asynchronizedUdpConnect(EventLoopGroup eventLoopGroup, ChannelHandler... handlers) {
Bootstrap bootstrap = buildUdpBootstrap(eventLoopGroup, handlers);
return bootstrap.bind(0);
}
/**
* 同步创建 UDP 连接
*
* @param eventLoopGroup
* 线程组
* @param Handlers
* 处理器
* @return ChannelFuture
*/
public static ChannelFuture synchronizedUdpConnect(EventLoopGroup eventLoopGroup, ChannelHandler... handlers) {
ChannelFuture future = null;
if (handlers != null) {
try {
future = asynchronizedUdpConnect(eventLoopGroup, handlers).await();
if (!future.isSuccess()) {
throw new IOException("UDP 连接失败");
}
} catch (Exception e) {
throw new IllegalStateException("UDP 同步连接创建失败", e);
}
}
return future;
}
/**
* 创建 TCP 连接器
*
* @param eventLoopGroup
* 线程组
* @param Handlers
* 处理器
* @return Bootstrap
*/
private static Bootstrap buildTcpBootstrap(EventLoopGroup eventLoopGroup, ChannelHandler... handlers) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator())
.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(handlers);
}
});
return bootstrap;
}
/**
* 创建 UDP 连接器
*
* @param eventLoopGroup
* 线程组
* @param Handlers
* 处理器
* @return Bootstrap
*/
private static Bootstrap buildUdpBootstrap(EventLoopGroup eventLoopGroup, ChannelHandler... handlers) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioDatagramChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator())
.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch) throws Exception {
ch.pipeline().addLast(handlers);
}
});
return bootstrap;
}
/**
* 获取空闲 TCP 端口
*
* @throws IOException
* @return int
*/
public static int getUnoccupiedTcpPort() throws IOException {
ServerSocket server = new ServerSocket(0);
int port = server.getLocalPort();
server.close();
return port;
}
/**
* 获取空闲 UDP 端口
*
* @throws IOException
* @return int
*/
public static int getUnoccupiedUdpPort() throws IOException {
DatagramSocket server = new DatagramSocket(0);
int port = server.getLocalPort();
server.close();
return port;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment