Files
IM-server-new/client/src/main/java/cn/wildfirechat/client/IMClient.java
T

425 lines
17 KiB
Java

package cn.wildfirechat.client;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.xiaoleilu.loServer.pojos.InputRoute;
import io.moquette.spi.impl.security.AES;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Connected;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Connecting;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Unconnected;
public class IMClient implements Listener {
private final String userId;
private final String token;
private final String clientId;
private final String host;
private final int port;
private ConnectionStatusCallback connectionStatusCallback;
private ReceiveMessageCallback receiveMessageCallback;
private ConnectionStatus connectionStatus;
protected String mqttServerIp;
protected long mqttServerPort;
private static byte[] commonSecret= {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F};
protected String privateSecret;
private long messageHead;
private transient MQTT mqtt = new MQTT();
private transient CallbackConnection connection = null;
public interface ReceiveMessageCallback {
void onReceiveMessages(List<WFCMessage.Message> messageList, boolean hasMore);
void onRecallMessage(long messageUid);
}
public interface ConnectionStatusCallback {
void onConnectionStatusChanged(ConnectionStatus newStatus);
}
public interface SendMessageCallback {
void onSuccess(long messageUid, long timestamp);
void onFailure(int errorCode);
}
public enum ConnectionStatus {
ConnectionStatus_Unconnected,
ConnectionStatus_Connecting,
ConnectionStatus_Connected,
}
public IMClient(String userId, String token, String clientId, String host, int port) {
this.userId = userId;
byte[] data = Base64.getDecoder().decode(token);
data = AES.AESDecrypt(data, commonSecret, false);
String s = new String(data);
String[] ss = s.split("\\|");
this.token = ss[0];
this.privateSecret = ss[1];
this.clientId = clientId;
this.host = host;
this.port = port;
AES.init(commonSecret);
}
public void connect() {
if(route(userId, token)) {
try {
mqtt.setHost("tcp://" + mqttServerIp + ":" + mqttServerPort);
mqtt.setVersion("3.1.1");
mqtt.setKeepAlive((short)180);
mqtt.setClientId(clientId);
mqtt.setConnectAttemptsMax(100);
mqtt.setReconnectAttemptsMax(100);
mqtt.setUserName(userId);
byte[] password = AES.AESEncrypt(token, privateSecret);
mqtt.setPassword(new UTF8Buffer(password));
connection = mqtt.callbackConnection();
connection.listener(this);
//connecting
connectionStatus = ConnectionStatus_Connecting;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
connection.connect(new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
if (value != null) {
try {
WFCMessage.ConnectAckPayload ackPayload = WFCMessage.ConnectAckPayload.parseFrom(value);
System.out.println(ackPayload.getMsgHead());
messageHead = ackPayload.getMsgHead();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
System.out.println("on connect success");
//connected
connectionStatus = ConnectionStatus_Connected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("on connect failure");
connectionStatus = ConnectionStatus_Unconnected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void disconnect(boolean clearSession, final Callback<Void> onComplete) {
this.connection.disconnect(clearSession, onComplete);
}
public void sendMessage(WFCMessage.Conversation conversation, WFCMessage.MessageContent messageContent, final SendMessageCallback callback) {
WFCMessage.Message message = WFCMessage.Message.newBuilder().setConversation(conversation).setContent(messageContent).setFromUser(userId).build();
byte[] data = message.toByteArray();
data = AES.AESEncrypt(data, privateSecret);
connection.publish("MS", data, QoS.AT_LEAST_ONCE, false, new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
if (value[0] == 0) {
byte[] data = new byte[value.length-1];
for (int i = 0; i < data.length; i++) {
data[i] = value[i+1];
}
data = AES.AESDecrypt(data, privateSecret, true);
ByteBuffer buffer = ByteBuffer.wrap(data, 0,16);
long messageUid = buffer.getLong();
long timestamp = buffer.getLong();
callback.onSuccess(messageUid, timestamp);
} else {
callback.onFailure(value[0]);
}
}
@Override
public void onFailure(Throwable value) {
callback.onFailure(-1);
}
});
}
protected boolean route(String userId, String token) {
HttpPost httpPost;
try{
HttpClient httpClient = HttpClientBuilder.create().build();
httpPost = new HttpPost("http://" + host + ":" + port + "/route");
InputRoute inputRoute = new InputRoute();
inputRoute.setUserId(userId);
inputRoute.setClientId(clientId);
inputRoute.setToken(token);
WFCMessage.RouteRequest routeRequest = WFCMessage.RouteRequest.newBuilder().setPlatform(0).build();
WFCMessage.IMHttpWrapper request = WFCMessage.IMHttpWrapper.newBuilder().setClientId(clientId).setToken(token).setRequest("ROUTE").setData(ByteString.copyFrom(routeRequest.toByteArray())).build();
byte[] data = AES.AESEncrypt(request.toByteArray(), privateSecret);
data = Base64.getEncoder().encode(data);
StringEntity entity = new StringEntity(new String(data), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
byte[] cidByte = AES.AESEncrypt(clientId.getBytes(), commonSecret);
cidByte = Base64.getEncoder().encode(cidByte);
String cid = new String(cidByte);
httpPost.setHeader("cid", cid);
byte[] uidByte = AES.AESEncrypt(userId.getBytes(), commonSecret);
uidByte = Base64.getEncoder().encode(uidByte);
String uid = new String(uidByte);
httpPost.setHeader("uid", uid);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
if (response.getStatusLine().getStatusCode() != 200) {
System.out.println("Http response error {" + response.getStatusLine().getStatusCode() + "}");
return false;
}
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
try {
byte[] bytes = new byte[resEntity.getContent().available()];
resEntity.getContent().read(bytes);
if (bytes[0] == 0) {
byte[] bytes1 = new byte[bytes.length -1];
for (int i = 0; i < bytes1.length; i++) {
bytes1[i] = bytes[i+1];
}
byte[] rawData = AES.AESDecrypt(bytes1, privateSecret, true);
WFCMessage.RouteResponse routeResponse = WFCMessage.RouteResponse.parseFrom(rawData);
mqttServerIp = routeResponse.getHost();
mqttServerPort = routeResponse.getLongPort();
routeResponse.getShortPort();
return true;
} else {
System.out.println("the route failure:" + bytes[0]);
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
}
} else {
System.out.println("Http response nil");
}
}catch(Exception ex){
ex.printStackTrace();
}
return false;
}
@Override
public void onConnected() {
System.out.println("onConnected");
connectionStatus = ConnectionStatus_Connecting;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onDisconnected() {
System.out.println("onDisconnected");
connectionStatus = ConnectionStatus_Unconnected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
System.out.println("onPublish" + topic.toString());
ack.run();
if (topic.toString().equals("MN")) {
try {
WFCMessage.NotifyMessage notifyMessage = WFCMessage.NotifyMessage.parseFrom(body.toByteArray());
WFCMessage.PullMessageRequest request = WFCMessage.PullMessageRequest.newBuilder().setId(messageHead).setType(notifyMessage.getType()).build();
byte[] data = request.toByteArray();
data = AES.AESEncrypt(data, privateSecret);
connection.publish("MP", data, QoS.AT_LEAST_ONCE, false, new Callback<byte[]>(){
@Override
public void onSuccess(byte[] value) {
if (value == null || value.length == 0) {
System.out.println("Invalide data");
}
if (value[0] != 0) {
System.out.println("Pull message error with errorCode:" + value[0]);
}
byte[] data = new byte[value.length - 1];
for (int i = 0; i < data.length; i++) {
data[i] = value[i+1];
}
try {
data = AES.AESDecrypt(data, privateSecret, true);
try {
WFCMessage.PullMessageResult result = WFCMessage.PullMessageResult.parseFrom(data);
if (receiveMessageCallback != null && result.getMessageList().size() > 0) {
List<WFCMessage.Message> messages = result.getMessageList();
List<WFCMessage.Message> out = new ArrayList<>();
for (WFCMessage.Message msg : messages) {
if (msg.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Private && msg.getConversation().getTarget().equals(userId)) {
msg = msg.toBuilder().setConversation(msg.getConversation().toBuilder().setTarget(msg.getFromUser())).build();
}
out.add(msg);
}
receiveMessageCallback.onReceiveMessages(out, false);
}
messageHead = result.getHead();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println("onSuccess");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onFailure");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onDisconnected" + value.toString());
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(ConnectionStatus_Unconnected);
}
}
public void setConnectionStatusCallback(ConnectionStatusCallback connectionStatusCallback) {
this.connectionStatusCallback = connectionStatusCallback;
}
public void setReceiveMessageCallback(ReceiveMessageCallback receiveMessageCallback) {
this.receiveMessageCallback = receiveMessageCallback;
}
public String getUserId() {
return userId;
}
public String getToken() {
return token;
}
public String getClientId() {
return clientId;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public ConnectionStatus getConnectionStatus() {
return connectionStatus;
}
public static void main(String[] args) {
//token与userid和clientid是绑定的,使用时一定要传入正确的userid和clientid,不然会认为token非法
//clientId唯一代表一个设备,只能有一个登录。如果使用同一个clientId登录多次,会出现不可预料问题。
IMClient client = new IMClient("KNK_K_00", "6I7UTfKSQT7VhsUJfU6UCvDeCyblLfcXPfajAFxgFGOcqvfFVQCvYy8A5vfAGUj2LSYgoM5W02MHh6jtG/otQs6ft8zLT4Efz315UlDLfrz+OSQYfwAuGFw9m0/x4ckrgiooprsKFxPMaNcCDact0qvp1dBhbeGUipdWVhI33b4=", "5B6DEB72-8A3D-45E2-A88A-47DC4E692B00", "192.168.0.193", 80);
client.setReceiveMessageCallback(new ReceiveMessageCallback() {
@Override
public void onReceiveMessages(List<WFCMessage.Message> messageList, boolean hasMore) {
System.out.println("recieved messages");
}
@Override
public void onRecallMessage(long messageUid) {
System.out.println("recalled messages");
}
});
client.setConnectionStatusCallback((ConnectionStatus newStatus) -> {
if (newStatus == ConnectionStatus_Connected) {
WFCMessage.Conversation conversation = WFCMessage.Conversation.newBuilder().setType(0).setTarget("IMININnn").setLine(0).build();
WFCMessage.MessageContent messageContent = WFCMessage.MessageContent.newBuilder().setSearchableContent("helloworld").setType(1).build();
client.sendMessage(conversation, messageContent, new SendMessageCallback() {
@Override
public void onSuccess(long messageUid, long timestamp) {
System.out.println("send success");
}
@Override
public void onFailure(int errorCode) {
System.out.println("send failure");
}
});
}
});
client.connect();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}