上午发表了一篇博文吐槽了现在的环境,没想到这么多人回应。如果谁因为看到了博文而感到气愤,我在这里道歉。博客园毕竟是谈技术的地方,工作生活私下谈吧。
经过高手批评,叫LML框架就言过其实了,我只是仿着.net框架Castle MonoRail做的,只山寨了一点点皮毛。
成功源于执著,而我们却因此变得沉溺。
按照程序员的思维,登录并非一件简单的事情,因为你首先要验证用户正确性,而后分配权限和菜单,并记录相应的用户信息,进而还要导向用户首页。咋一看,貌似很复杂,一切的原始,在所有的操作之前我们还必须做一些和数据库相关的动作,比如新建数据库,新建用户表等,还要做映射文件,还有DAO等等。小小登录,是多少代人无法跨越的屏障。一直以来,我也是做过了各种各样的登录,包括类似搜狐的单点登录。下面先给出,一段超级简单的代码,然后再聊聊登录的那些事吧!
登录页面Action:
public String Login() { return SUCCESS; }
登录页面的HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" /> <title>蜜蜂的智慧</title> <link type="text/css" rel="stylesheet" href="../Theme/1/base/css/login.css" /> <script type="text/javascript" src="../Content/javascript/jquery-1.6.2.min.js"></script> <script type="text/javascript"> $(document).ready( function () { var Wheight = $(window).height(); var Wwidth = $(window).width(); var mw = (Wheight - 316) * 0.5; //$("#bg").height(Wheight); $("#bg").css("margin-top", mw); }); $(window).resize(function () { var Wheight = $(window).height(); $("#bg").height(Wheight); }); function ClearAll() { document.getElementById("UNAME").value = ""; document.getElementById("PASSWORD").value = ""; } function CheckForm() { return true; } function LoginIn() { $('#form1').submit(); } </script> </head> <body class="login_body"> <div id="bg" class="login"> <div id="content"> <form onsubmit="return CheckForm();" method="post" name="form1" action="FrameWork_LoginCheck.action" autocomplete="off" target="iframe_data" id="form1"> <table width="350" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td colspan="2" class="login_admin"> 蜜蜂的智慧 </td> </tr> <tr> <td class="white"> <div class="spac1"> 用户名</div> : </td> <td class="login_con"> <input id="UNAME" type="text" class="text" name="userName" maxlength="20" onmouseover="this.focus()" onfocus="this.select()" value="LML" /> </td> </tr> <tr> <td class="white"> <div class="spac"> 密码</div> : </td> <td class="login_con"> <input id="PASSWORD" type="password" class="text" name="userPassword" onmouseover="this.focus()" onfocus="this.select()" value="15376412198" /> </td> </tr> <tr> <td> </td> <td> <input type="submit" class="submit" value="" title="登录" /> <input type="button" class="cancel" value="" title="取消" onclick="ClearAll()" /> </td> </tr> </table> </form> <div class="copy"> </div> </div> </div> <iframe name="iframe_data" id="iframe_data" style="display: none;"></iframe> </body> </html>
登录验证的Action
public String LoginCheck() throws HibernateException, SQLException { if (userName.equals("LML") && userPassword.equals("15376412198")) { String sqlstr = "select * from syspower"; String power = ""; Result rs = DBHelper.ExecuteResult(sqlstr); for (SortedMap item : rs.getRows()) { power += item.get("id").toString() + ","; } GlobalInfo.setPower(power); GlobalInfo.setValue("roleId", "supper"); return JavaScript("alert('登陆成功,点击跳转!');parent.location.href='FrameWork_Index.action'"); } else { String sqlStr = "select * from sysuser where userName ='" + userName + "'"; Result rst = DBHelper.ExecuteResult(sqlStr); if (rst != null && rst.getRowCount() > 0) { String pwd = (String) rst.getRowsByIndex()[0][2]; if (pwd.equals(MD5Helper.MD5(userPassword))) { String role = (String) rst.getRowsByIndex()[0][3]; String power = ""; Result rstP = DBHelper .ExecuteResult(" select * from syspower where id in( select powerId from rolepower where roleId='" + role + "')"); for (SortedMap item : rstP.getRows()) { power += item.get("id").toString() + ","; } GlobalInfo.setPower(power); GlobalInfo.setValue("roleId", role); return JavaScript("alert('登陆成功,点击跳转!');parent.location.href='FrameWork_Index.action'"); } } return JavaScript("alert('用户名或者密码错误!')"); } }
登录的这些事:
1, 诚然,无论如何我们要有一个像模像样的登录界面,用户第一眼看到的就是界面,而不会察觉你是什么form提交,还是ajax。。。
2, 足够简洁,一般应用管理系统使用登录界面作为首页,首页必须给用户信心,坚决不能让用户失去了等待的耐心,所以必须简洁,必要时还需要进行特殊手段的优化。
3, 记住用户名和密码,几乎是必不可少的功能,因为我相信大部分用户都足够的懒,或者几乎每一个人都想节约一点点时间。
稍作解释:我们这里并没有做记住用户名密码的操作,领导说后台后台不要记住密码,包括单点登录也没有把与它类似的项目纳入。我们这个界面如其他项目的登录界面一样简洁,只有一个Form,两个input,还有一个submit,当然还有一个取消按钮。我们并没有采用Ajax提交和Ajax验证,大家应该都明白很多项目很难处理的一个事情就是验证问题,我们一般情况下很难处理form提交验证不通过而导致页面刷新的情况。假如页面刷新,而又没有做相关的操作重新自动填入用户名和密码,这将是非常苦恼的,不是吗?我们这里使用了一个小小的技巧,target="iframe_data",这样我们无论是验证之后输出脚本或者其他操作,都不会导致主界面刷新。当然了,这也带来了其他问题,假如我们想在输出的脚本中操作登录界面的元素,不得不使用parent了。
登录界面讲完了,下面再叙一叙后台相关代码和操作。
与代码相关的数据库上一章的链接中已经很出,本文结束也会给出下载链接。表结构大家可不必挑刺,不过是是我信手胡来的。可使用Navicat导入。
4, 特权用户。我觉得我们的系统都要有一个特权用户,可在不分配权限的情况下使用整个系统。甚至在什么情况下都可以使用,完全的自由。好吧,我承认,我甚至在某一个项目中让任何一个用户在某种满足的条件下可以随意进出系统而不经过系统权限验证。这难道没有一点点必要吗?我们这里的LML就是这样的家伙,当然实际情况下,切记不可这么的草草了事,一定要尽量保障安全。使出你的浑身解数吧。
5, 走正常的路线,必定要到数据库验证用户密码,用户状态等。密码一向比较敏感,最近还一直经常爆出某某网站的用户信息泄露的问题,甚至还有明文密码。任何解密算法都可以被解密,只不过时间的问题,但是解密依然还是能讨得用户欢心的,因为加把锁他总算是能够安下一点点心了。
下面分解一段关键代码
Result rst = DBHelper.ExecuteResult(sqlStr);
我估计用Result这种数据类型的人并不多吧?因为在互联网上我很难找到它相关的资料,偶然不知道在哪里发现了它,它藏在jsp包中。我一直叫它离线数据集,谁知道呢!我是有Table情结的。如果谁想了解更多,或者谁已经了解了更多,请多多交流。下面是一段迭代代码片段,使用起来要比ResultSet简单的多了。
for (SortedMap item : rs.getRows()) {
power += item.get("id").toString() + ",";
}
还有,我们可以直接访问某一行某一列,而不是使用游标来迭代:
rst.getRowsByIndex()[0][2]
6, 当一个登录用户被验证时合法用户之后,必然还要做两件事:初始化权限(还有菜单),记录(缓存)用户信息。至于权限和菜单与系统的可用性和安全性息息相关,绝对不可马虎。这一点必须根据相应的系统设计,灵活应变。缓存当前用户的信息也是必要的,因为我们甚至在很多模块都要关联当前用户的各种信息进行查询,缓存下来必定节省很多时间。时间才是最宝贵的。我们这里使用GlobalInfo.setter来记录各种信息,包括必须的,可自由扩种无限制的。这里埋下一个伏笔,一个开发人员比谁都了解我们用什么技术来记录用户登录信息,Cookie or Session?我允许自由选择。
7, 提示和跳转。假如验证无法通过,你总该提示一下吧。现在最高明的提示都是ajax实现的很美观的提示,我们力求简单,依然使用alert,所以我祈祷浏览器能够把alert做的漂亮一点。
return JavaScript("alert('用户名或者密码错误!')");
这样还不够简洁吗?
如果验证成功,我们的跳转这样写:
return JavaScript("alert('登陆成功,点击跳转!');parent.location.href='FrameWork_Index.action'");
首页是个什么样子,以后再说吧。一个简单的登录模块就完事了。我们简单,所以我们快,我们快所以我们有先机!有时候,我们在做一些时间紧迫的项目时,难道时间不是最重要的吗?我们真的应该把时间用在该用的地方,而不是用在了编写冗余的代码上。人月神话上说,一个项目我们用1/6的时间用来编码最合适。我不能完全同意,像我们这种类似小作坊式的软件公司,设计和测试都很薄弱,就算是把大部分时间花在设计和测试上也无济于事,那样一点意义也没有。我们本来就没有时间,也不会再编码上花费很多,所以我们经常加班。我们的时间(说一句脏话)都他妈花在维护上了,文件缺乏,代码复杂,人员流动大,你说怎么维护?那么我们既然没有能力,为什么不让代码简单一点呢!why not?
下集预告:Result和ResultSet。