今天分享一个openfire批量推送的插件,上次我们做的插件只是适合小范围推送,这次我们做一个给全部“在线”的用户推送消息的插件,只有在线用户才会收到推送消息,离线的用户不会收到消息(不会收到离线消息),如果同一账号使用多个终端登录那么多个终端都会收到推送消息。当然也可以把这个插件稍作修改就变成一个批量推送的插件,不一定要给全服的用户推送消息。
由于这次考虑到操作的用户量比较大,所以采用比较传统的方式
1. 推送的信息首先保存到数据库
2. checkThread以轮训的方式检查数据库是否有需要推送的消息
3. 有消息后创建Runnable对象并且扔到threadpool内执行
插件实现起来并不能,甚至是有一点简单,主要是如何获取数据库内的用户和如何检查用户是否在线,这两点了解的剩下的就简单了。
1. 检查是否有需要推送的消息
CheckThread.java
个人觉得这部分是写的最失败的地方,采用的是原始的数据库轮训的机制,如果是生产环境是可以使用像redis、activeMQ或者其他的方式来实现
package com.xxdd.openfire.batchpush.plugin.thread;
import java.util.List;
import org.xmpp.component.ComponentManager;
import org.xmpp.component.ComponentManagerFactory;
import com.xxdd.openfire.batchpush.plugin.model.OFBatchPush;
import com.xxdd.openfire.batchpush.plugin.model.OFUser;
import com.xxdd.openfire.batchpush.plugin.provider.OFBatchPushProvider;
import com.xxdd.openfire.batchpush.plugin.provider.OFUserProvider;
public class CheckThread extends Thread {
ComponentManager componentManager = ComponentManagerFactory.getComponentManager();
public void run() {
while (ThreadPool.RUNNING) {
OFBatchPush batPush = OFBatchPushProvider.getInstance().getPushContent();
if (batPush == null) {//没有需要推送的消息
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {//有需要推送的消息
//检查数据库内用户的数量
int count = OFUserProvider.getInstance().getUserCount();
int len = 20;
int maxPage = (int) Math.ceil((float) count / (float) len);
for (int i=0; i<maxPage; i++) {
//分页查询用户
List<OFUser> users = OFUserProvider.getInstance().findUsers(i*len, len);
if (users != null && users.size() > 0) {
for (OFUser user : users) {
//把要发送的消息内容和消息的接收人组装成Runnable对象
PushRunnable runnable = new PushRunnable(
user.getUsername(),
componentManager,
batPush.getBody()
);
//把runnable对象放到线程池内执行
ThreadPool.execute(runnable);
}
}
}
//最后发送完成,在数据库内删除消息内容
OFBatchPushProvider.getInstance().delPush(batPush.getId());
}
}
}
}
OFBatchPushProvider.java
package com.xxdd.openfire.batchpush.plugin.provider;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.jivesoftware.database.DbConnectionManager;
import com.xxdd.openfire.batchpush.plugin.model.OFBatchPush;
public class OFBatchPushProvider {
private static OFBatchPushProvider self;
private OFBatchPushProvider() {
}
public static OFBatchPushProvider getInstance() {
if (self == null)
self = new OFBatchPushProvider();
return self;
}
/**
* 获得需要推送的内容
* @return
*/
public OFBatchPush getPushContent() {
String loadPushSQL = "SELECT id, body, create_time FROM ofBatchpush ORDER BY create_time LIMIT 1";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(loadPushSQL);
rs = pstmt.executeQuery();
if (rs.next()) {
OFBatchPush batPush = new OFBatchPush();
batPush.setId(rs.getInt(1));
batPush.setBody(rs.getString(2));
batPush.setCreateTime(rs.getDate(3));
return batPush;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return null;
}
/**
* 删除推送消息内容
* @param id
*/
public void delPush(int id) {
String delPushSQL = "DELETE FROM ofBatchpush WHERE id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(delPushSQL);
pstmt.setInt(1,id);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
}
OFBatchPush.java
package com.xxdd.openfire.batchpush.plugin.model;
import java.util.Date;
public class OFBatchPush {
private int id;
private String body;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
OFUserProvider.java
package com.xxdd.openfire.batchpush.plugin.provider;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.database.DbConnectionManager;
import com.xxdd.openfire.batchpush.plugin.model.OFUser;
/**
*
* @author xxdd
*/
public class OFUserProvider {
private static OFUserProvider self = null;
private OFUserProvider() {
}
public static OFUserProvider getInstance() {
if (self == null)
self = new OFUserProvider();
return self;
}
/**
* 分页查询用户
* @param offset
* @param len
* @return
*/
public List<OFUser> findUsers(int offset, int len) {
String loadUsersSQL = "SELECT username FROM ofUser LIMIT ?,?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<OFUser> users = new ArrayList<OFUser>(len);
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(loadUsersSQL);
pstmt.setInt(1, offset);
pstmt.setInt(2, len);
rs = pstmt.executeQuery();
while (rs.next()) {
OFUser user = new OFUser(rs.getString(1));
users.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return users;
}
/**
* 获得用户总量
* @return
*/
public int getUserCount() {
String loadUserCountSQL = "SELECT COUNT(1) FROM ofUser";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int count = 0;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(loadUserCountSQL);
rs = pstmt.executeQuery();
rs.next();
count = rs.getInt(1);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return count;
}
}
OFUser.java
package com.xxdd.openfire.batchpush.plugin.model;
public class OFUser {
private String username;
public OFUser() {
}
public OFUser(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
2. 线程池
使用jdk自带的executor框架,创建一个简单的线程池,网上已经有很多的例子了,这里就不啰嗦了
package com.xxdd.openfire.batchpush.plugin.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
private static ExecutorService pool = null;
private static int minThread = 5;
private static int maxThread = 20;
private static LinkedBlockingDeque<Runnable> queue = new LinkedBlockingDeque<Runnable>(100);
public static boolean RUNNING = true;
static {
pool = new ThreadPoolExecutor(
minThread,
maxThread,
60L,
TimeUnit.SECONDS,
queue,
new ThreadPoolExecutor.CallerRunsPolicy()
);
RUNNING = true;
}
public static void execute(Runnable runnable) {
if (RUNNING) {
pool.execute(runnable);
}
}
public static void shutDown() {
RUNNING = false;
pool.shutdownNow();
}
}
3. 发送线程
这部分代码不叫关键,是整个推送的核心部分
package com.xxdd.openfire.batchpush.plugin.thread;
import java.util.Collection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.ClientSession;
import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManager;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Message.Type;
public class PushRunnable implements Runnable {
SessionManager sessionManager = SessionManager.getInstance();
String toUsername;
ComponentManager componentManager;
String body;
private static final String from = "system";
private static final String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
public PushRunnable(
String toUsername,
ComponentManager componentManager,
String body) {
this.toUsername = toUsername;
this.componentManager = componentManager;
this.body = body;
}
@Override
public void run() {
//获得在线用户session
Collection<ClientSession> sessions = sessionManager.getSessions(toUsername);
if (sessions != null && sessions.size() > 0) {//存在在线用户的session
Message message = new Message();
message.setBody(body);
message.setFrom(from + "@" + domain);
message.setTo(toUsername + "@" + domain);
message.setType(Type.normal);
//给单个用户的所有登录的客户端发送消息
for (ClientSession session : sessions) {
JID jid = session.getAddress();
Message cpMessage = message.createCopy();
cpMessage.setTo(jid);
try {
//给在线用户推送消息
componentManager.sendPacket(null, message);
} catch (ComponentException e) {
e.printStackTrace();
}
}
}
}
}
完整代码下载地址:http://download.csdn.net/detail/kaixinwoo5/7579365
有问题欢迎邮件讨论7325356@qq.com