Dec 15

Openfire是XMPP协议最好的服务器软件。最近修改了一个插件实现了类Twitter功能,发出来分享

Get started

我用的是debian作为Openfire服务器
下载Openfire,安装JRE(Jaav运行库),把Openfire跑起来
下载JDK 1.5,安装到/opt/jdk1.5.0_14
下载openfire源码,准备编译。当时我安装的oenfire是3.4.1所以源码也下载的是3.4.1版本。用tar -zxvf openfire_src_3_4_1.tar.gz解压到/opt/openfire_src

配置系统环境,声明JDK的环境变量export JAVA_HOME=/opt/jdk1.5.0_14/
下载ant编译工具,apt-get install ant 呵呵,debian就是爽。

编写Openfire插件

这是一个及其痛苦的过程,由于我不会Java,所以报了本《Thinking in Java》然后对照着SUN的官方文档边学边写,最后还是鼓捣出来了,呵呵
我的插件是基于Stefan Reuter的userstatus插件修改的

User Status Plugin is a plugin for the Openfire XMPP server to save the user status to the database.

This plugin automatically saves the last status (presence, IP address, logon and logoff time) per user and resource to userStatus table in the Openfire database.

Optionally you can archive user status entries (IP address, logon and logoff time) for a specified time. History entries are stored in the userStatusHistory table. The settings for history archiving can be configured on the “User Status Settings” page that you’ll find on the “Server” tab of the Openfire Admin Console.

目录结构有些变化,我的修改版可以在这里下载 userstatus.rar
目录结构是:

E:\\DORMFORCE\\NUTALK\\OPENFIRE\\EST\\USER-STATUS│  changelog.html│  plugin.xml│  readme.html│├─i18n│      user-status_i18n.properties│├─META-INF│  │  MANIFEST.MF│  ││  └─maven│      └─com.reucon.openfire.plugins│          └─user-status│                  pom.properties│                  pom.xml│├─src│  ├─database│  │      user-status_mysql.sql│  ││  ├─java│  │  └─com│  │      └─reucon│  │          └─openfire│  │              └─plugins│  │                  └─userstatus│  │                          UserStatusPlugin.java│  ││  └─web│          user-status-settings.jsp│└─web    │  user-status-settings.jsp    │    └─WEB-INF            web.xml

修改了user-status_mysql.sql ,在userStatus表增加一个字段status TEXT,,然后修改UserStatusPlugin.java的源码了
[code:java]
package com.reucon.openfire.plugins.userstatus;

import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.event.SessionEventListener;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.user.PresenceEventDispatcher;
import org.jivesoftware.openfire.user.PresenceEventListener;
import org.jivesoftware.util.*;
import org.xmpp.packet.Presence;
import org.xmpp.packet.JID;

import java.io.File;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;
//by est
import java.net.URL;
import java.net.URLEncoder;
import java.net.HttpURLConnection;

/**
* UserStatus plugin for Openfire. MOD by est
*/
public class UserStatusPlugin implements Plugin, PropertyEventListener, SessionEventListener, PresenceEventListener
{
private static final int SEQ_ID = 510;

private static final String ADD_USER_STATUS =
“Insert INTO userStatus (username, resource, online, lastIpAddress, lastLoginDate) ” +
“VALUES (?, ?, 1, ?, ?)”;

private static final String UPDATE_USER_STATUS =
“UPDATE userStatus SET online = 1, lastIpAddress = ?, lastLoginDate = ? ” +
“Where username = ? AND resource = ?”;

private static final String SET_PRESENCE =
“UPDATE userStatus SET presence = ?, status = ? Where username = ? AND resource = ?”; //By est

private static final String SET_OFFLINE =
“UPDATE userStatus SET online = 0, lastLogoffDate = ? Where username = ? AND resource = ?”;

private static final String SET_ALL_OFFLINE =
“UPDATE userStatus SET online = 0″;

private static final String ADD_USER_STATUS_HISTORY =
“Insert INTO userStatusHistory (historyID, username, resource, lastIpAddress,” +
“lastLoginDate, lastLogoffDate) VALUES (?, ?, ?, ?, ?, ?)”;

private static final String DELETE_OLD_USER_STATUS_HISTORY =
“DELETE From userStatusHistory Where lastLogoffDate < ?”;

public static final String HISTORY_DAYS_PROPERTY = “user-status.historyDays”;
public static final int DEFAULT_HISTORY_DAYS = -1;

/**
* Number of days to keep history entries.

* 0 for now history entries, -1 for unlimited.
*/
private int historyDays = DEFAULT_HISTORY_DAYS;

public static final String strSynXCallHubUrlFormat = “http://www2.dormforce.net/services/callhub.php?SynXPwd=___&username=%s&status=%s”;

public void initializePlugin(PluginManager manager, File pluginDirectory)
{
Connection con = null;
PreparedStatement pstmt = null;

historyDays = JiveGlobals.getIntProperty(HISTORY_DAYS_PROPERTY, DEFAULT_HISTORY_DAYS);
PropertyEventDispatcher.addListener(this);

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_ALL_OFFLINE);
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to clean up user status”, e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

for (ClientSession session : SessionManager.getInstance().getSessions())
{
sessionCreated(session);
}

SessionEventDispatcher.addListener(this);
PresenceEventDispatcher.addListener(this);
}

public void destroyPlugin()
{
PresenceEventDispatcher.removeListener(this);
SessionEventDispatcher.removeListener(this);
}

public void sessionCreated(Session session)
{
Connection con = null;
PreparedStatement pstmt = null;
int rowsUpdated = 0;

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_USER_STATUS);
pstmt.setString(1, getHostAddress(session));
pstmt.setString(2, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.setString(3, session.getAddress().getNode());
pstmt.setString(4, session.getAddress().getResource());
rowsUpdated = pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to update user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

if (rowsUpdated == 0)
{
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_USER_STATUS);
pstmt.setString(1, session.getAddress().getNode());
pstmt.setString(2, session.getAddress().getResource());
pstmt.setString(3, getHostAddress(session));
pstmt.setString(4, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to Insert user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}
}

public void sessionDestroyed(Session session)
{
Connection con = null;
PreparedStatement pstmt = null;
final Date logoffDate;

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

logoffDate = new Date();
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_OFFLINE);
pstmt.setString(1, StringUtils.dateToMillis(logoffDate));
pstmt.setString(2, session.getAddress().getNode());
pstmt.setString(3, session.getAddress().getResource());
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to update user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

// write history entry
if (historyDays != 0)
{
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_USER_STATUS_HISTORY);
pstmt.setLong(1, SequenceManager.nextID(SEQ_ID));
pstmt.setString(2, session.getAddress().getNode());
pstmt.setString(3, session.getAddress().getResource());
pstmt.setString(4, getHostAddress(session));
pstmt.setString(5, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.setString(6, StringUtils.dateToMillis(logoffDate));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to add user status history for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}

deleteOldHistoryEntries();
}

public void anonymousSessionCreated(Session session)
{
// we are not interested in anonymous sessions
}

public void anonymousSessionDestroyed(Session session)
{
// we are not interested in anonymous sessions
}

public void availableSession(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void unavailableSession(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void presencePriorityChanged(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void presenceChanged(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void subscribedToPresence(JID subscriberJID, JID authorizerJID)
{
// we are not interested in subscription updates
}

public void unsubscribedToPresence(JID unsubscriberJID, JID recipientJID)
{
// we are not interested in subscription updates
}

public void propertySet(String property, Map params)
{
if (HISTORY_DAYS_PROPERTY.equals(property))
{
final Object value = params.get(”value”);
if (value != null)
{
try
{
historyDays = Integer.valueOf(value.toString());
}
catch (NumberFormatException e)
{
historyDays = DEFAULT_HISTORY_DAYS;
}
deleteOldHistoryEntries();
}
}
}

public void propertyDeleted(String property, Map params)
{
if (HISTORY_DAYS_PROPERTY.equals(property))
{
historyDays = DEFAULT_HISTORY_DAYS;
deleteOldHistoryEntries();
}
}

public void xmlPropertySet(String property, Map params)
{
// we don’t use xml properties
}

public void xmlPropertyDeleted(String property, Map params)
{
// we don’t use xml properties
}

private void updatePresence(ClientSession session, Presence presence)
{
Connection con = null;
PreparedStatement pstmt = null;
String presenceText=”";
String statusText=”";

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

if (Presence.Type.unavailable.equals(presence.getType()))
{
presenceText = presence.getType().toString();
}
else if (presence.getShow() != null)
{
presenceText = presence.getShow().toString();
}
else if (presence.isAvailable())
{
presenceText = “available”;
}
else
{
return;
}

try
{
statusText = presence.getStatus();
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_PRESENCE);
pstmt.setString(1, presenceText);
pstmt.setString(2, statusText); //By est, following numbers are added by 1.
pstmt.setString(3, session.getAddress().getNode());
pstmt.setString(4, session.getAddress().getResource());
pstmt.executeUpdate();

/*SynX CallHub by est*/
URL url = new URL(String.format(strSynXCallHubUrlFormat,URLEncoder.encode( session.getAddress().getNode() ), URLEncoder.encode( statusText, “UTF-8″ )));
HttpURLConnection httpUrl = (HttpURLConnection)url.openConnection();
httpUrl.connect();

}
catch (Exception e)
{
Log.error(”Unable to update presence for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}

private void deleteOldHistoryEntries()
{
Connection con = null;
PreparedStatement pstmt = null;

if (historyDays > 0)
{
final Date deleteBefore;

deleteBefore = new Date(System.currentTimeMillis() - historyDays * 24L * 60L * 60L * 1000L);

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_OLD_USER_STATUS_HISTORY);
pstmt.setString(1, StringUtils.dateToMillis(deleteBefore));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to delete old user status history”, e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}
}

private String getHostAddress(Session session)
{
try
{
return session.getHostAddress();
}
catch (UnknownHostException e)
{
return “”;
}
}
}
[/code]
代码中 session.getAddress().getNode()); 的意思是获得当前的XMPP用户名。XMPP协议规定,用户名分三部份:
someone @ domain.tld / clientname
 ^     ^      ^
用户名node 域domain 客户端名字,又叫资源resource,用于区分一个ID多处同时登录

也就是说,用户在XMPP客户端更新了自己的status,Openfire会调用http://www2.dormforce.net/services/callhub.php?SynXPwd=___&username=%s&status=%s 一次。可以把这个地址理解为一个WebService。例如我我们DormForce的官方客户端里更新我的状态
14.jpg
Openfire收到后会自动调用这个网址:http://www2.dormforce.net/services/callhub.php?SynXPwd=___&username=est&status=%E5%8F%AF%E4%BB%A5%E6%8A%8A%4E%55%54%61%6C%6B%E5%BD%93%74%77%69%74%74%65%72%E7%94%A8%E4%BA%86

那么我们在callhub.php里编写任意代码了,比如我们可以自己实现一个类似Twitter的应用,也可以直接调用API把消息发送到真实的Twitter或则国内的fanfou

编译

首先删除 /opt/openfire_src/src/plugins/下的不需要的插件,然后把user-status文件夹上传到plugins目录下
user-status/WEB-INF 复制到根 / 。也就是 /WEB-INF/ 。不要问我为什么,我也不知道为什么,反正不这么做就是编译不成功。
然后cd /opt/openfire_src/build
ant clean && ant plugins

几分钟过后如果显示BUILD SUCCESSFUL那么说明编译成功了!

部署插件

编译好的插件在哪里呢?在/opt/openfire_src/target/openfire/plugins/user-status.jar
上传user-status.jar到openfire的安装目录下的plugins文件夹,到Openfire管理界面里看看,如果这个插件已经启用,那么说明我们的twitter平台已经搭建起来了

参考

基本是一些e文文档
Openfire接口文档 http://www.igniterealtime.org/builds/openfire/docs/latest/documentation/javadoc/index.html
JavaDoc 1.5 http://java.sun.com/j2se/1.5.0/docs/api/
user-status插件及其源码 http://maven.reucon.com/public/com/reucon/openfire/plugins/user-status/1.0.2/
Openfire开发指南 http://www.igniterealtime.org/projects/openfire/documentation.jsp

Dec 07

关键是看一个名字叫user-status的插件。
下面是UserStatus的源码:
[code:java]
package com.reucon.openfire.plugins.userstatus;

import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.event.SessionEventListener;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.user.PresenceEventDispatcher;
import org.jivesoftware.openfire.user.PresenceEventListener;
import org.jivesoftware.util.*;
import org.xmpp.packet.Presence;
import org.xmpp.packet.JID;

import java.io.File;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;

/**
* UserStatus plugin for Openfire.
*/
public class UserStatusPlugin implements Plugin, PropertyEventListener, SessionEventListener, PresenceEventListener
{
private static final int SEQ_ID = 510;

private static final String ADD_USER_STATUS =
“Insert INTO userStatus (username, resource, online, lastIpAddress, lastLoginDate) ” +
“VALUES (?, ?, 1, ?, ?)”;

private static final String UPDATE_USER_STATUS =
“UPDATE userStatus SET online = 1, lastIpAddress = ?, lastLoginDate = ? ” +
“Where username = ? AND resource = ?”;

private static final String SET_PRESENCE =
“UPDATE userStatus SET presence = ? Where username = ? AND resource = ?”;

private static final String SET_OFFLINE =
“UPDATE userStatus SET online = 0, lastLogoffDate = ? Where username = ? AND resource = ?”;

private static final String SET_ALL_OFFLINE =
“UPDATE userStatus SET online = 0″;

private static final String ADD_USER_STATUS_HISTORY =
“Insert INTO userStatusHistory (historyID, username, resource, lastIpAddress,” +
“lastLoginDate, lastLogoffDate) VALUES (?, ?, ?, ?, ?, ?)”;

private static final String DELETE_OLD_USER_STATUS_HISTORY =
“DELETE From userStatusHistory Where lastLogoffDate < ?”;

public static final String HISTORY_DAYS_PROPERTY = “user-status.historyDays”;
public static final int DEFAULT_HISTORY_DAYS = -1;

/**
* Number of days to keep history entries.

* 0 for now history entries, -1 for unlimited.
*/
private int historyDays = DEFAULT_HISTORY_DAYS;

public void initializePlugin(PluginManager manager, File pluginDirectory)
{
Connection con = null;
PreparedStatement pstmt = null;

historyDays = JiveGlobals.getIntProperty(HISTORY_DAYS_PROPERTY, DEFAULT_HISTORY_DAYS);
PropertyEventDispatcher.addListener(this);

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_ALL_OFFLINE);
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to clean up user status”, e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

for (ClientSession session : SessionManager.getInstance().getSessions())
{
sessionCreated(session);
}

SessionEventDispatcher.addListener(this);
PresenceEventDispatcher.addListener(this);
}

public void destroyPlugin()
{
PresenceEventDispatcher.removeListener(this);
SessionEventDispatcher.removeListener(this);
}

public void sessionCreated(Session session)
{
Connection con = null;
PreparedStatement pstmt = null;
int rowsUpdated = 0;

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_USER_STATUS);
pstmt.setString(1, getHostAddress(session));
pstmt.setString(2, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.setString(3, session.getAddress().getNode());
pstmt.setString(4, session.getAddress().getResource());
rowsUpdated = pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to update user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

if (rowsUpdated == 0)
{
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_USER_STATUS);
pstmt.setString(1, session.getAddress().getNode());
pstmt.setString(2, session.getAddress().getResource());
pstmt.setString(3, getHostAddress(session));
pstmt.setString(4, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to Insert user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}
}

public void sessionDestroyed(Session session)
{
Connection con = null;
PreparedStatement pstmt = null;
final Date logoffDate;

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

logoffDate = new Date();
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_OFFLINE);
pstmt.setString(1, StringUtils.dateToMillis(logoffDate));
pstmt.setString(2, session.getAddress().getNode());
pstmt.setString(3, session.getAddress().getResource());
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to update user status for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}

// write history entry
if (historyDays != 0)
{
try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_USER_STATUS_HISTORY);
pstmt.setLong(1, SequenceManager.nextID(SEQ_ID));
pstmt.setString(2, session.getAddress().getNode());
pstmt.setString(3, session.getAddress().getResource());
pstmt.setString(4, getHostAddress(session));
pstmt.setString(5, StringUtils.dateToMillis(session.getCreationDate()));
pstmt.setString(6, StringUtils.dateToMillis(logoffDate));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to add user status history for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}

deleteOldHistoryEntries();
}

public void anonymousSessionCreated(Session session)
{
// we are not interested in anonymous sessions
}

public void anonymousSessionDestroyed(Session session)
{
// we are not interested in anonymous sessions
}

public void availableSession(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void unavailableSession(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void presencePriorityChanged(ClientSession session, Presence presence)
{
// we are not interested in priority changes
}

public void presenceChanged(ClientSession session, Presence presence)
{
updatePresence(session, presence);
}

public void subscribedToPresence(JID subscriberJID, JID authorizerJID)
{
// we are not interested in subscription updates
}

public void unsubscribedToPresence(JID unsubscriberJID, JID recipientJID)
{
// we are not interested in subscription updates
}

public void propertySet(String property, Map params)
{
if (HISTORY_DAYS_PROPERTY.equals(property))
{
final Object value = params.get(”value”);
if (value != null)
{
try
{
historyDays = Integer.valueOf(value.toString());
}
catch (NumberFormatException e)
{
historyDays = DEFAULT_HISTORY_DAYS;
}
deleteOldHistoryEntries();
}
}
}

public void propertyDeleted(String property, Map params)
{
if (HISTORY_DAYS_PROPERTY.equals(property))
{
historyDays = DEFAULT_HISTORY_DAYS;
deleteOldHistoryEntries();
}
}

public void xmlPropertySet(String property, Map params)
{
// we don’t use xml properties
}

public void xmlPropertyDeleted(String property, Map params)
{
// we don’t use xml properties
}

private void updatePresence(ClientSession session, Presence presence)
{
Connection con = null;
PreparedStatement pstmt = null;
final String presenceText;

if (!XMPPServer.getInstance().getUserManager().isRegisteredUser(session.getAddress()))
{
return;
}

if (Presence.Type.unavailable.equals(presence.getType()))
{
presenceText = presence.getType().toString();
}
else if (presence.getShow() != null)
{
presenceText = presence.getShow().toString();
}
else if (presence.isAvailable())
{
presenceText = “available”;
}
else
{
return;
}

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SET_PRESENCE);
pstmt.setString(1, presenceText);
pstmt.setString(2, session.getAddress().getNode());
pstmt.setString(3, session.getAddress().getResource());
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to update presence for ” + session.getAddress(), e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}

private void deleteOldHistoryEntries()
{
Connection con = null;
PreparedStatement pstmt = null;

if (historyDays > 0)
{
final Date deleteBefore;

deleteBefore = new Date(System.currentTimeMillis() - historyDays * 24L * 60L * 60L * 1000L);

try
{
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_OLD_USER_STATUS_HISTORY);
pstmt.setString(1, StringUtils.dateToMillis(deleteBefore));
pstmt.executeUpdate();
}
catch (SQLException e)
{
Log.error(”Unable to delete old user status history”, e);
}
finally
{
DbConnectionManager.closeConnection(pstmt, con);
}
}
}

private String getHostAddress(Session session)
{
try
{
return session.getHostAddress();
}
catch (UnknownHostException e)
{
return “”;
}
}
}

[/code]

不懂Java,所以这两天一直很郁闷。估计要修改presenceChanged()事件的相关代码。这个插件的缺点是,只能用于MySQL数据库,所以用Openfire自带的HSQLDB就不行啦。还有这个插件界面是乱码。使用这个插件的时候最好把历史记录禁用了,否则你的数据库会在一周之内被撑暴。

下载 | JavaDoc | 源码

Dec 07

今天帮FourT搞定了Google App的域名问题,越来越多的组织和企业开始用Google提供的稳定Email服务托管啦。网管会用了也将近2个月了,说起来还真有不少经验

  • 申请Google App的时候,要申请for Education的,这个版本功能比standard多,也是免费的,而且多了一个API集成功能。我就很后悔当初申请的是Standard版本
  • 切换邮件系统前,一定要培训用户,将why, how
  • 登录Web界面的Google Mail for your Domain 一定要用 https://mail.google.com/a/domain.tld,防止GFW搞活动
  • 不要用国内的Email客户端,例如Foxmail,因为这些客户端不支持TLS的SMTP
  • 登录Google的POP或者SMTP服务器时,一定要加上@domain.tld,不然很多用户会抱怨:怎么登录到我的私人Gmail里去了?
Oct 31

万圣节前夜,嘎嘎,写一篇XMPP的文章,主要来自Jabber官方的Technical Overview

Overview

XMPPJabber软件基金会开发,最早在Jabber上实现。Jabber项目由Jeremie Miller在1998年开始的一个免费、开源的项目,用于提供给MSN、Yahoo!的IM服务。由于XMPP是一种基于XML架构的开放式协议,在IM通讯中被广泛采用,已经得到了互联网工程任务组(IETF)的批准(RFC 3920|RFC 3921)。

IQ

即Information Query,是XMPP协议中一个类似HTTP的GET、POST的动词。比如说修改Jabber密码:

<iq type='set' to='jabber.org'>
<query xmlns='jabber:iq:register'>
<username>username</username>
<password>newpassword</password>
</query>
</iq>

JID

即Jabber ID,格式为:
[node@]domain[/resource]
node是用户名,domain是域;如果是Google Talk的话域就是 gmail.com;resource表示工作状态,Jabber允许在不同的地方同时登陆一个Jabber账号,用resource来表示不同的状态,可以自定义,GTalk目前版本定义的resource就是 Talk.v104C0F37955
例如:estbot@googlemail.com/AtWork

Disco

Service Discovery,一个Jabber服务器可能不止提供了IM服务,还提供了和其它IM互通网关、群聊等特性。

Stanza

一个XML片段

Roster

联系人名单

Presence

即隐身功能,把你的在线/离线状态只告诉一部分联系人

Subscription

订阅一个联系人的在线状态,通俗的讲就是添加好友。A给服务器发送一个presence消息,要subscribe B,那么相当于A要添加B为好友。

show

忙碌状态
away――在线,但不能马上联系上
chat――在线并有兴趣聊天
dnd――在线,但不想被打扰(“dnd”表示“do not disturb”)
xa――在线,但已经离开很长时间了(“xa”表示“extended away”)
GTalk里,绿色图标表示<show />,没有任何show的值,如果是红色图标则是<show>dnd</show>,只有这两种状态

status

好友的个性签名。好友的头像是通过X标签来获得的

vCard

联系人名片,见 RFC 2426

Jingle

Google定义的Jabber扩展,包含了p2p、语聊、文件传输、穿防火墙/NAT/Proxy等高级功能。例如File Transfer模块
libjingle file transfer

剩下的想起来了再添加,看RFC真是一件超级麻烦的事情,CSDN上还有Jabber协议的中文版本 :-)

Oct 25

同时开启多个DrCom登录窗口会出现Code(21)。

Oct 23
#coding:utf-8import re, urllib2re.search("td>(\d+\.\d+\.\d+\.\d+)</td", urllib2.urlopen("http://whois.ipcn.org/").read(), re.M).group(1))

对了,whois.ipcn.org在教育网和电信访问都很快,推荐用来看自己的IP

Oct 17
C:\\Users\\est>arp -s 202.115.22.129 00-0d-bc-78-07-3fARP 项添加失败: 5
C:\\Users\\est>netsh i i show in
Idx  Met   MTU   状态          名称---  ---  -----  -----------  -------------------  1   50 4294967295  connected    Loopback Pseudo-Interface 1  8   20   1500  connected    本地连接
C:\\Users\\est>netsh -c "i i" add neighbors 8 "202.115.22.129" "00-0d-bc-78-07-3f"
C:\\Users\\est>arp -a
接口: 192.169.1.120 --- 0x8  Internet 地址         物理地址              类型  202.115.22.129        00-0d-bc-78-07-3f     静态  202.115.22.131        00-17-a4-e2-07-3f     动态  202.115.22.132        00-17-08-2e-78-41     静态  202.115.22.135        00-01-02-fd-4c-d6     动态  202.115.22.141        00-e0-5c-41-0d-98     动态  202.115.22.148        00-f0-4c-85-f4-4e     动态  202.115.22.149        00-0a-e4-fb-90-ac     动态  202.115.22.154        00-0a-eb-4f-1c-e2     动态  202.115.22.191        ff-ff-ff-ff-ff-ff     静态  224.0.0.22            01-00-5e-00-00-16     静态
C:\\Users\\est>

来源:http://www.alouz.com/weblog/?p=850

只有arp -s网关的时候才会出现 ARP 项添加失败: 5这个错误

Oct 08

俗话说网民分两种,一种是上Internet的,一种是上Intranet的。能上Internet的网民绕墙很有一套,恰好今天sonic问到ssh代理的问题,这里就发一个利用PuTTY绕墙/开代理的办法。

理论上说,支持ssh version2远程登录的主机可以当成一台socks5代理服务器。

PuTTY的图形化界面也可以配置出一个代理,但是那个用鼠标点击的不自动
putty ssh tunnel config
PuTTY完整版自带的PLINK.exe可以完美的做这个事情,命令是:

PLINK.EXE -C -N -D 127.0.0.1:7000 est@202.115.22.x:21314

解释成中文:

PLINK.EXE -启用数据压缩 -不要shell -端口动态转发 代理IP:端口 远程主机用户名@远程主机IP:端口

就这么简单。

如果是Linux下,直接使用ssh命令就可以了:

ssh -CfNg -D 127.0.0.1:7000 est@202.115.22.x:21314

代理设置好了之后,在Firefox下这样设置:

2.jpg

最后,到 http://labs.dormforce.net/ip/检查自己的IP是否变成代理服务器的IP了,恩,网速真快。

Oct 07

今天跟同学写一个WebService,我这边是用lighttpd+mod_fastcgi+django做的一个WebService服务,同学那边用C#来调用这个WebService。但是每次在Visual Studio 2005里调试的时候都显示

未处理 WebException远程服务器返回错误: (417) Expectation Failed。

我用Python的urllib2来POST这个WebService都是成功的,但是.NET里几个HTTP请求对象都不能调用。仔细对比抓包了2种语言的请求包,发现.NET的请求头多了一个Expect: 100-continue

起初我还怀疑是django不支持,后来查看了HTTP 1.1的文档,最后发现,lighttpd 1.4版本更本不会支持Expect: 100-continue这个HTTP头。或者装 lighttpd 1.5.x 的开发版本,或者换apache。

结合在CSDN上的相关问题,最终的解决办法是:在Http请求之前加上这一句:System.Net.ServicePointManager.Expect100Continue = false;

Oct 01

今天下午跟网管会的几个同学玩Counter Strike 1.6,联机的时候弄得比较郁闷。

版本

经研究发现最流行的最通用的CS1.6版本是3266。况且Vista下只能玩DcOo的那个3382+3266 Twins SP1版本

Invalid CD-KEY

出现这个错误,表明联机的CS客户端有CD-KEY冲突。国内一般用的盗版CD-KEY都是:

REGEDIT4
[HKEY_CURRENT_USER\Software\Valve]
[HKEY_CURRENT_USER\Software\Valve\Half-Life]
[HKEY_CURRENT_USER\Software\Valve\Half-Life\Settings]"ValveKey"="5RP2E-EPH3K-BR3LG-KMGTE-FN8PY"
[HKEY_CURRENT_USER\Software\Valve\Steam]"Language"="schinese""Skin"="""Rate"=""

另外还有一个可能性就是:CS1.6小版本不一致。

LAN servers are resticted to local clients (class c)

出现这个错误的原因是:本地连接 配置了多个IP,然后第一个IP(默认IP)不是当前局域网的。修改 网络连接 -> 本地连接 -> 属性 -> TCP/IP -> IP地址即可。

找不到建立好的服务器

别人能刷出来建立好的游戏,但是自己的机器始终刷不出来。这个问题是因为机器不在同一个 WORKGROUP 或者 MSHOME 里。让大家把 我的电脑 -> 属性 -> 计算机名 -> 更改 工作组 -> 隶属于 -> 都统一修改为 WORKGROUP 即可。
其实还有一个更简单用命令联机的方法。首先必须知道CS1.6服务器的IP地址,比如说网管会这里的某服务器 202.115.22.x ,在游戏开始界面按下感叹号旁边那个 ` 按键,调出console,输入 connect 202.115.22.x 回车,就联机了。

要打一个CS真累啊 @.@