Openfire客户端的开发是借助Smack提供的方法。Smack可以从官网下载。如果是Openfire3.7.1的版本,建议使用Smack3.2.2与其匹配。官网地址:http://www.igniterealtime.org/downloadServlet?filename=smack/smack_3_2_2.zip
下载之后解压,将里面的4个jar复制到网络工程WEB-INF下的lib文件夹,这样,即可以使用smack的api进行开发。
目标是做出如下样子的工程,在网页版提供用户注册、登录和密码修改的功能。
网络工程的目录结构如下:
web.xml,没什么好说的,仅仅是一些简单的struts2配置,没有其它任何的内容。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
DB.java这是在《【Java】利用单例模式、可变参数优化Java操作Mysql数据库、JDBC代码的写作》( 点击打开链接)我已经详细说明过的工具类,这里不再多说了。
package tool; import java.sql.*; import java.util.*; public class DB { // 一、单例初始化连接 private Connection con; // 以下代码,保证该类只能有一个实例 private DB() { try { Class.forName("com.mysql.jdbc.Driver"); // 其中test是我们要链接的数据库,user是数据库用户名,password是数据库密码。 // 3306是mysql的端口号,一般是这个 // 后面那串长长的参数是为了防止乱码,免去每次都需要在任何语句都加入一条SET NAMES UTF8 String url = "jdbc:mysql://localhost:3306/openfire?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true"; String user = "root"; String password = "root"; con = DriverManager.getConnection(url, user, password); } catch (Exception e) { e.printStackTrace(); } } // 私有无参构造方法 // 在自己内部定义自己的一个实例,只供内部调用 private static DB db = null; // 这个类必须自动向整个系统提供这个实例对象 // 这里提供了一个供外部访问本class的静态方法,可以直接访问 public static DB getInstance() { if (db == null) { db = new DB(); } return db; } // 二、查询 // 使用SQL查询,查询的结果是一个结果集(视图) public List<Object[]> getBySql(String sql) { List<Object[]> result_list = new ArrayList<Object[]>(); try { PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while (rs.next()) { ResultSetMetaData md = rs.getMetaData(); int columnCount = md.getColumnCount(); Object[] row_data_set = new Object[columnCount]; for (int i = 1; i <= columnCount; i++) { row_data_set[i - 1] = rs.getObject(i); } result_list.add(row_data_set); } return result_list; } catch (SQLException e) { e.printStackTrace(); return null; } } // 查询sql语句带参数的情况 public List<Object[]> getBySql(String sql, Object[] param) { List<Object[]> result_list = new ArrayList<Object[]>(); try { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < param.length; i++) { ps.setObject(i + 1, param[i]); } ResultSet rs = ps.executeQuery(); while (rs.next()) { ResultSetMetaData md = rs.getMetaData(); int columnCount = md.getColumnCount(); Object[] row_data_set = new Object[columnCount]; for (int i = 1; i <= columnCount; i++) { row_data_set[i - 1] = rs.getObject(i); } result_list.add(row_data_set); } return result_list; } catch (SQLException e) { e.printStackTrace(); return null; } } // 使用SQL查询,查询的结果是唯一 public Object getBySql_result_unique(String sql) { try { PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); rs.next(); return rs.getObject(1); } catch (SQLException e) { e.printStackTrace(); return null; } } // 查询sql语句带参数的情况 public Object getBySql_result_unique(String sql, Object[] param) { try { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < param.length; i++) { ps.setObject(i + 1, param[i]); } ResultSet rs = ps.executeQuery(); rs.next(); return rs.getObject(1); } catch (SQLException e) { e.printStackTrace(); return null; } } // 三、增删改 // insert、update、delete等修改数据库的语句 public void setBySql(String sql) { try { PreparedStatement ps = con.prepareStatement(sql); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } // sql语句带参数的情况 public void setBySql(String sql, Object[] param) { try { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < param.length; i++) { ps.setObject(i + 1, param[i]); } ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } // 析构函数,中断数据库的连接 protected void finalize() throws Exception { if (!con.isClosed() || con != null) { con.close(); } } }
index.jsp也就是如上的效果截图,布局很简单,就三个表单指向相应action,<%@ page isELIgnored="false"%>是为了保证EL表达式,能够在Tomcat5.5使用。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page isELIgnored="false"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>index</title> </head> <body> ${msg} <hr> <p> 用户注册<br> <form method="post" action="register"> 用户名:<input type="text" name="username" /><br> 密码:<input type="password" name="password" /><br> <input type="submit" value="注册" /> </form> </p> <hr> <p> 用户登录<br> <form method="post" action="login"> 用户名:<input type="text" name="username" /><br> 密码:<input type="password" name="password" /><br> <input type="submit" value="登录" /> </form> </p> <hr> <p> 修改密码<br> <form method="post" action="modify_password"> 用户名:<input type="text" name="username" /><br> 旧密码:<input type="password" name="password" /><br> 新密码:<input type="password" name="newpassword" /><br> <input type="submit" value="修改" /> </form> </p> </body> </html>
chat.jsp是用户成功登录,所跳转的页面,很简单。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>聊天</title> </head> <body> 欢迎,${sessionScope.username},<a href="logout">登出</a> </body> </html>
核心关键在于两个,一个是从Openfire源代码中拷贝过来的Blowfish.java,这个文件的详细说明请看《【Openfire】验证用户输入密码是否正确》( 点击打开链接)。
这个文件很长,修改的地方,主要是里面关于org.slf4j记录日志的信息的注释掉,然后是对其42-53行的构造方法进行修改,这样以后用到就不用每次都要写代码,在数据库中查询ofproperty中passwordKey所对应的值。修改之后如下:
public Blowfish() {// 改过 // hash down the password to a 160bit key DB db = DB.getInstance(); String password = (String) db .getBySql_result_unique("select propValue from ofproperty where name='passwordKey'"); MessageDigest digest = null; try { digest = MessageDigest.getInstance("SHA1"); digest.update(password.getBytes()); } catch (Exception e) { // Log.error(e.getMessage(), e); } // setup the encryptor (use a dummy IV) m_bfish = new BlowfishCBC(digest.digest(), 0); digest.reset(); }
最后,所有核心方法都在test.user这个类中。对照struts.xml所配的action:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="test" extends="struts-default"> <action name="register" class="test.user" method="register"> <result name="success" type="redirect">/index.jsp</result> </action> <action name="login" class="test.user" method="login"> <result name="success" type="redirect">/chat.jsp</result> <result name="input" type="redirect">/index.jsp</result> </action> <action name="logout" class="test.user" method="logout"> <result name="success" type="redirect">/index.jsp</result> </action> <action name="modify_password" class="test.user" method="modify_password"> <result name="success" type="redirect">/index.jsp</result> </action> </package> </struts>这个user.java的代码如下:
package test; import java.util.Map; import org.jivesoftware.smack.AccountManager; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import tool.Blowfish; import tool.DB; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class user extends ActionSupport { private static final long serialVersionUID = 5570391705428296402L; private String username; private String password; private String newpassword; private String msg; // 判断输入的密码是否与数据库密码一致 private boolean checkPassword(String username, String password) { Blowfish blowfish = new Blowfish(); DB db = DB.getInstance(); String db_password = (String) db.getBySql_result_unique( "select encryptedPassword from ofuser where username=?", new Object[] { username }); if (blowfish.decryptString(db_password).equals(password)) { return true; } else { return false; } } // 用户注册 public String register() throws XMPPException { DB db = DB.getInstance(); if (username.length() == 0) { msg = "用户名不得为空!"; return SUCCESS; } if (db.getBySql("select * from ofuser where username=?", new Object[] { username }).size() > 0) { msg = "该用户名已经存在!"; } else { ConnectionConfiguration config = new ConnectionConfiguration( "127.0.0.1", 5222); Connection connection = new XMPPConnection(config); connection.connect(); AccountManager amgr = connection.getAccountManager(); amgr.createAccount(username, password);// 会自动使用Openfire的Blowfish编码加密密码。 msg = "注册成功!"; System.out.println("新用户注册:" + username + ",密码:" + password); connection.disconnect(); } return SUCCESS; } // 用户登录 public String login() throws XMPPException { if (checkPassword(username, password)) { msg = "登录成功!"; ConnectionConfiguration config = new ConnectionConfiguration( "127.0.0.1", 5222); Connection connection = new XMPPConnection(config); connection.connect(); connection.login(username, password); Map session = ActionContext.getContext().getSession(); session.put("username", username); session.put("connection", connection); return SUCCESS; } else { msg = "密码错误!"; return INPUT; } } // 用户登出 public String logout() { Map session = ActionContext.getContext().getSession(); Connection connection = (Connection) session.get("connection"); connection.disconnect(); session.clear(); return SUCCESS; } // 修改密码 public String modify_password() throws XMPPException { if (checkPassword(username, password)) { ConnectionConfiguration config = new ConnectionConfiguration( "127.0.0.1", 5222); Connection connection = new XMPPConnection(config); connection.connect(); connection.login(username, password);// 登录之后才能修改密码 connection.getAccountManager().changePassword(newpassword); System.out.println("用户:" + username + ",修改密码从:" + password + ",变为:" + newpassword); connection.disconnect(); msg = "修改成功!"; } else { msg = "密码错误!"; } return SUCCESS; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getNewpassword() { return newpassword; } public void setNewpassword(String newpassword) { this.newpassword = newpassword; } }
注册、登录、修改密码本来没有什么好说的,因为smack已经提供了一定量的api。
但是,由于smack并没有提供判断用户所输入密码是否正确的方法,只能自己手动到数据库,再配合Blowfish算法查询。如果密码是不正确的,直接利用connect.login方法的话,会直接报错,导致这个网络工程崩溃,try和catch也是没用的。所以在connect.login之前,需要自己查询用户输入的密码是否正确。
再有一点是,如果用户登录正确,那么这个connect对象就应该压入session,跟着他所登录的用户名一起走,为后续的聊天和加好友操作的提供依据,也就是以后的所有动作,最简单就是上面代码的登录,都要使用这个connection来操作,而不是自己再new一个。