一个帐号同一时间只能一人登录
对于一个帐号在同一时间只能一个人登录,可以通过下面的方法实现:
1 .在用户登录时,把用户添加到一个 ArrayList 中
2 .再次登录时查看 ArrayList 中有没有该用户,如果 ArrayList 中已经存在该用户,则阻止其登录
3 .当用户退出时,需要从该 ArrayList 中删除该用户,这又分为三种情况
① 使用注销按钮正常退出
② 点击浏览器关闭按钮或者用 Alt+F4 退出,可以用 javascript 捕捉该页面关闭事件,
执行一段 java 方法删除 ArrayList 中的用户
③ 非正常退出,比如客户端系统崩溃或突然死机,可以采用隔一段时间 session 没活动就删除该 session 所对应的用户来解决,这样用户需要等待一段时间之后就可以正常登录。
在 LoginAction 中定义:
// 用来在服务器端存储登录的所有帐号
public static List logonAccounts;
login() 登录方法中:
// 设置 session 不活动时间为 30 分
request.getSession().setMaxInactiveInterval(60*30);
if(logonAccounts==null){
logonAccounts = new ArrayList();
}
// 查看 ArrayList 中有没有该用户
if(!logonAccounts.contains(account.getAccountId())){
// 在用户登录时,把用户添加到一个 ArrayList 中
logonAccounts.add(account.getAccountId());
return "login";
}else{
return "denied";
}
① 使用注销按钮正常退出
logout() 退出方法中:
if(logonAccounts==null){
logonAccounts = new ArrayList();
}
if(logonAccounts.contains(account.getAccountId())){
logonAccounts.remove(account.getAccountId());
}
② 点击浏览器关闭按钮或者用 Alt+F4 退出:
在后台弹出一个窗口,在弹出窗口中删除 ArrayList 中的用户
function window.onbeforeunload(){
window.open('accountUnbound.jsp','',
'height=0,width=0,top=10000,left=10000')
}
accountUnbound.jsp : 弹出窗口中删除 ArrayList 中的用户
<%
Account account = (Account) request.getSession().getAttribute("account");
if(account != null){
if(LoginAction.logonAccounts==null){
LoginAction.logonAccounts = new ArrayList();
}
if(LoginAction.logonAccounts.contains(account.getAccountId())){
LoginAction.logonAccounts.remove(account.getAccountId());
}
}
%>
为了保证上面代码可以执行完毕, 3 秒后关闭此弹出窗口
<script>
setTimeout("closeWindow();",3000);
function closeWindow(){
window.close();
}
</script>
③ 使 implements HttpSessionListener ,并实现 sessionCreated/sessionDestroyed 方法
在 sessionDestroyed 中删除 ArrayList 中的用户(用户超过 30 分钟不活动则执行此方法)
Account account = (Account) request.getSession().getAttribute("account");
if(account != null){
if(LoginAction.logonAccounts==null){
LoginAction.logonAccounts = new ArrayList();
}
if(LoginAction.logonAccounts.contains(account.getAccountId())){
LoginAction.logonAccounts.remove(account.getAccountId());
}
}
注:
对于上面的,由于弹出窗口很容易被防火墙或者安全软件阻拦,造成无法弹出窗口,从而短时间不能登录,这种情况可以用 AJAX 来代替弹出窗口,同样在后台执行删除用户的那段代码,却不会受到防火墙限制:
<script>
// <![CDATA[
var http_request = false;
function makeRequest (url) {
http_request = false;
if (window.XMLHttpRequest) { // Mozilla, Safari,...
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
http_request.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
}
}
}
if (!http_request) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
http_request.onreadystatechange = alertContents;
http_request.open('GET', url, true);
http_request.send(null);
}
function alertContents() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
window.close();
} else {
alert('There was a problem with the request.');
}
}
}
function window. onbeforeunload() {
makeRequest ('accountUnbound.jsp');
}
//]]>
</script>
对于上面的这段 ajax 代码,在网上有很多详细的解释,把它加到 onbeforeunload() 浏览器关闭事件中,在后台执行代码的效果很好, 不必担心弹出窗口有时候会无效的问题 。
使用这段代码后,上面 ② 中 accountUnbound.jsp 中的那段关闭弹出窗口 window.close(); 的 js 代码就不需要了。
树形菜单一
根据dtree的要求,我们来建一个数据库表来存储树的节点信息,表名为functions,其结构如下:
id字段:varchar 10 主键--节点标识码 pid字段:varchar 10 not null--父节点标识码 name字段:varchar 20 not null url字段:varchar 50 not null--这个字段存储的是点击该节点时,要定位的资源(比如一个页面的url), 为了不使本文的篇幅过长,暂时不给出相应的页面, 您可以随便输入一个字母比如:a,以使本例能够正常运行。 title字段:varchar 20 target字段:varchar 10 icon字段:varchar 20 iconopen字段:varchar 20 opened字段:char 1 |
在表中输入如下一些记录以供后面的实验用:
0、-1、我的权限、javascript: void(0); 00、0、用户管理、javascript: void(0); 0001、00、创建新用户; 0002、00、删除用户; 01、0、 文章管理、javascript: void(0); 0101、01、添加新文章; 0102、01、修改文章; 0103、01、删除文章; |
到此,数据库方面的准备工作就告一段落。
接下来的工作我们仍然在先前介绍的mystruts项目中进行。先编写一个名为:FunctionsForm的ActionForm,其代码如下:
package entity; import org.apache.struts.action.*; import javax.servlet.http.*; public class FunctionsForm extends ActionForm { private String icon; private String iconOpen; private String id; private String name; private String opened; private String pid; private String target; private String title; private String url; public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon; } public String getIconOpen() { return iconOpen; } public void setIconOpen(String iconOpen) { this.iconOpen = iconOpen; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getOpened() { return opened; } public void setOpened(String opened) { this.opened = opened; } public String getPid() { return pid; } public void setPid(String pid) { this.pid = pid; } public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } |
因为我们的树型节点的数据都存储在数据库表中,接下来,要做一个数据访问对象类,名称为:FunctionsDao.java,其代码如下:
package db; import java.sql.*; import java.util.*; import entity.FunctionsForm; public class FunctionsDao { private static Connection con = null; public FunctionsDao(Connection con) { this.con=con; } public static Collection findTree() { PreparedStatement ps=null; ResultSet rs = null; ArrayList list=new ArrayList(); String sql="select * from functions"; try{ if(con.isClosed()){ throw new IllegalStateException("error.unexpected"); } ps=con.prepareStatement(sql); rs=ps.executeQuery(); while(rs.next()){ FunctionsForm functionsForm=new FunctionsForm(); functionsForm.setId(rs.getString("id")); functionsForm.setPid(rs.getString("pid")); functionsForm.setName(rs.getString("name")); functionsForm.setUrl(rs.getString("url")); functionsForm.setTitle(rs.getString("title")); functionsForm.setTarget(rs.getString("target")); functionsForm.setIcon(rs.getString("icon")); functionsForm.setIconOpen(rs.getString("iconOpen")); functionsForm.setOpened(rs.getString("opened")); list.add(functionsForm); } return list; } catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } finally{ try{ if(ps!=null) ps.close(); if(rs!=null) rs.close(); }catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } } } } |
这里值得注意的是:在以往我们见到的一些显示树型菜单的程序,如:一些asp程序中往往简单地采用递归调用的方法来查找到树的各个节点。这对那些树的深度不确定的场合还是有些用处,但这种处理方法也有一个致命的弱点,那就是反复地进行数据库查询,对一些节点较多的应用,对应用程序性能的影响是非常大的,有时会慢得让人难以接受;而在实际的应用中大多数情况下树的深度往往是有限的,如:用于会计科目的树一般最多也在六层以下。又如:用作网页功能菜单的情况,网页设计的原则就有一条是:达到最终目的地,鼠标点击次数最好不要多于三次。因此,在实际设计存储树型结构的表时要考虑查询的效率。对能确定树的最大深度的情况下,要设法尽量优化查询语句,减少查询次数,以提高应用程序的性能同时减少数据库的负荷。
本例对应的Action的名称为FunctionsAction,其代码如下:
package action; import entity.*; import org.apache.struts.action.*; import javax.servlet.http.*; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Collection; import db.FunctionsDao; public class FunctionsAction extends Action { public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { DataSource dataSource; Connection cnn=null; ActionErrors errors=new ActionErrors(); try{ dataSource = getDataSource(httpServletRequest,"A"); cnn = dataSource.getConnection(); FunctionsDao functionsDao=new FunctionsDao(cnn); Collection col=functionsDao.findTree(); httpServletRequest.setAttribute("treeList",col); return actionMapping.findForward("success"); } catch(Throwable e){ e.printStackTrace(); //throw new RuntimeException("未能与数据库连接"); ActionError error=new ActionError(e.getMessage()); errors.add(ActionErrors.GLOBAL_ERROR,error); } finally{ try{ if(cnn!=null) cnn.close(); } catch(SQLException e){ throw new RuntimeException(e.getMessage()); } } saveErrors(httpServletRequest,errors); return actionMapping.findForward("fail"); } } |
在struts-config.xml文件中加入如下内容:
<form-beans> <form-bean name="functionsForm" type="entity.FunctionsForm" /> </form-beans> <action-mappings> <action name="functionsForm" path="/functionsAction" scope="request" type="action.FunctionsAction" validate="false" > <forward name="success" path="/testDTree.jsp" /> <forward name="fail" path="/genericError.jsp" /> </action> </action-mappings> |
为了对应配置中的,我们还要提供一个显示错误信息的jsp页面,其代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html> <head> <title> genericError </title> <link href="css/mycss.css" rel="stylesheet" type="text/css"> </head> <body bgcolor="#ffffff"> <html:errors/> </body> </html> |
下面,我们来看一下我们显示树型菜单的页面代码,从配置中可以看出,页面的名称为testDTree.jsp,代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html> <head> <title> testDTree </title> <link rel="StyleSheet" href="css/dtree.css" type="text/css" /> </head> <body bgcolor="#eeeeee"> <body leftmargin="0" topmargin="0"><table width="180"> <tr><td height="300" valign="top" nowrap> <script type="text/javascript" src="js/dtree.js"></script> <script type='text/javascript'> tree = new dTree('tree'); tree.config.folderLinks=false; tree.config.useCookies=false; <logic:iterate id="functionsForm" name="treeList" scope="request" type="entity.FunctionsForm"> tree.add("<bean:write name="functionsForm" property="id"/>","<bean:write name="functionsForm" property="pid"/>","<bean:write name="functionsForm" property="name"/>","<bean:write name="functionsForm" property="url"/>","<bean:write name="functionsForm" property="title"/>","<bean:write name="functionsForm" property="target"/>","<bean:write name="functionsForm" property="icon"/>"); </logic:iterate> document.write(tree); </script> </td> </tr> </table> </body> </html> |
从 可以看出,我们要在mystruts目录下,建一个名为js的目录,并将下载的dtree文件dtree.js放在该目录中。
再在mystruts目录下分别建一个名为img和名为css的目录,将dtree中用到的图标和层叠样式表单文件分别放在相应的目录中。
有关dtree的使用方法,详见其说明文档,如:api.html。笔者在此要感谢dtree的作者为我们提供了一个结构如此清晰的javascript程序!
现在,可以编译执行这个例子程序了,编译后在浏览器中输入:http://127.0.0.1:8080/mystruts/functionsAction.do就可以看到运行效果。效果图为:
注:dtree的下载地址为: http://www.destroydrop.com/javascripts/tree/
使用hashtable对字符串操作
1.在一些字符串数组中,常会有重复的记录,比如手机号码,我们可以通过Hashtable来对其进行过滤
Hashtable < String, String > hash = new Hashtable < String, String > ();
for ( int i = 0 ;i < str.length;i ++ ) {
if ( ! hash.containsKey(str[i]))
hash.put(str[i], str[i]);
}
Enumeration enumeration = hash.keys();
String[] str_new = new String[hash.size()];
int i = 0 ;
while (enumeration.hasMoreElements()) {
str_new[i] = enumeration.nextElement().toString();
i ++ ;
}
return str_new;
}
示例:
String[] mobile={"13811071500","13811071500","13811071501","13811071503","13811071501"};
mobile=checkArray(mobile);
for(int i=0;i<mobile.length;i++)
System.out.println(mobile[i]);
输出结果为:
13811071503
13811071501
13811071500
2.A,B均为字符串数组,找出在A中存在,而在B中不存在的字符串
public String[] compareArray(String[] A,String[] B){
Hashtable<String, String> hash=new Hashtable<String, String>();
Hashtable<String, String> hash_new=new Hashtable<String, String>();
for(int i=0;i<B.length;i++)
hash.put(B[i], B[i]);
for(int i=0;i<A.length;i++){
if(!hash.containsKey(A[i]))
hash_new.put(A[i], A[i]);
}
String[] C=new String[hash_new.size()];
int i=0;
Enumeration enumeration=hash_new.keys();
while(enumeration.hasMoreElements()){
C[i]=enumeration.nextElement().toString();
i++;
}
return C;
}
示例:
String[] mobile1={"13811071500","13811071501","13811071502","13811071503","13811071504"};
String[] mobile2={"13811071500","13811071505","13811071502","13811071506","13811071504"};
String[] mobile3=compareArray(mobile1,mobile2);
for(int i=0;i<mobile3.length;i++)
System.out.println(mobile[i]);
输出结果:
13811071503
13811071501
存在的问题:
每次都是倒序,可以再对程序稍加改动,变成正序。
3.将一个字符串数组中某一个特定的字符串过滤掉
除,返回剩余的字符串数组
* @param str_array 字符串数组
* @param str_remove 待删除的字符串
* @return 过滤后的字符串
*/
public String[] removeStrFromArray(String[] str_array,String
str_remove) {
Hashtable < String, String > hash = new Hashtable < String, String > ();
for ( int i = 0 ;i < str_array.length;i ++ ) {
if ( ! str_array[i].equals(str_remove))
hash.put(str_array[i], str_array[i]);
}
// 生成一个新的数组
String[] str_new = new String[hash.size()];
int i = 0 ;
Enumeration enumeration = hash.keys();
while (enumeration.hasMoreElements()) {
str_new[i] = enumeration.nextElement().toString();
i ++ ;
}
return str_new;
}
java随机数
随机数发生器(Random)对象产生以后,通过调用不同的method:nextInt()、nextLong()、nextFloat()、nextDouble()等获得不同类型随机数。
1>生成随机数
Random random = new Random();
Random random = new Random(100);//指定种子数100
random调用不同的方法,获得随机数。
如果2个Random对象使用相同的种子(比如都是100),并且以相同的顺序调用相同的函数,那它们返回值完全相同。如下面代码中两个Random对象的输出完全相同
import java.util.*;
class TestRandom {
public static void main(String[] args) {
Random random1 = new Random(100);
System.out.println(random1.nextInt());
System.out.println(random1.nextFloat());
System.out.println(random1.nextBoolean());
Random random2 = new Random(100);
System.out.println(random2.nextInt());
System.out.println(random2.nextFloat());
System.out.println(random2.nextBoolean());
}
}
2>指定范围内的随机数
随机数控制在某个范围内,使用模数运算符%
import java.util.*;
class TestRandom {
public static void main(String[] args) {
Random random = new Random();
for(int i = 0; i < 10;i++) {
System.out.println(Math.abs(random.nextInt())%10);
}
}
}
获得的随机数有正有负的,用Math.abs使获取数据范围为非负数
3>获取指定范围内的不重复随机数
import java.util.*;
class TestRandom {
public static void main(String[] args) {
int[] intRet = new int[6];
int intRd = 0; //存放随机数
int count = 0; //记录生成的随机数个数
int flag = 0; //是否已经生成过标志
while(count<6){
Random rdm = new Random(System.currentTimeMillis());
intRd = Math.abs(rdm.nextInt())%32+1;
for(int i=0;i<count;i++){
if(intRet[i]==intRd){
flag = 1;
break;
}else{
flag = 0;
}
}
if(flag==0){
intRet[count] = intRd;
count++;
}
}
for(int t=0;t<6;t++){
System.out.println(t+"->"+intRet[t]);
}
}
}
Java中的随机数是否可以重复?Java中产生的随机数能否可以用来产生数据库主键?带着这个问题,我们做了一系列测试。
1.测试一: 使用不带参数的Random()构造函数
public class RandomTest {
public static void main(Str