【斗医】【11】Web应用开发50天
本文在上文的基础上完成用户登录验证功能。
四、获取数据请求业务处理封装
1、配置数据读取方式,它的作用是使用FrameDataGainer响应以.data结尾的请求,并把处理后的数据返回给客户端。打开D:\medical\war\WEB-INF\web.xml文件,填充如下内容:
<servlet> <servlet-name>data</servlet-name> <servlet-class>com.medical.frame.FrameDataGainer</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>data</servlet-name> <url-pattern>*.data</url-pattern> </servlet-mapping>
2、FrameDataGainer的作用说明:
当浏览器通过uri下发XXX.data请求时,web容器tomcat将该请求交由FrameDataGainer处理,它通过getDataName()方法获取请求名XXX后,然后再从全局缓存FrameCache中根据XXX获取定义业务类名称,接着根据定义业务类名称把其反射出FrameDefaultAction对象,最后调用FrameDefaultAction对象的execute()进行具体的业务处理,并返回给客户端。这个过程与请求跳转封装类似,接下来跟着思维一步步实现这个过程:
3、定义FrameDataGainer类,让其继承HttpServlet对象,同时在init()方法中初始化配置类
public class FrameDataGainer extends HttpServlet { /** * 定义日志对象 */ private static final Logger logger = LoggerFactory.getLogger(FrameDataGainer.class); @Override public void init() throws ServletException { ServletContext context = getServletContext(); // 加载业务配置文件 try { FrameConfigUtil.loadDataBusiness(context); } catch (FrameException e) { throw new ServletException("[FrameDataGainer] init error.", e); } } }
4、在FrameConfigUtil中定义loadDataBusiness()方法,通过该方法把xxx-data.xml等文件加载进入内存
/** * 加载D:\medical\war\WEB-INF\config下的所有数据业务配置文件 */ public static void loadDataBusiness(ServletContext context) throws FrameException { findDataFile(context, "/WEB-INF/config"); parseDataBusiness(context); }
其中findDataFile()用于查找D:\medical\war\WEB-INF\config下所有的xxx-data.xml文件;parseDataBusiness()用于把xxx-data.xml中的配置加载到内存中。这里不再列出,感兴趣的话可以附件中查看源码。
5、FrameDataGainer.getDataName()方法用于把请求的动作名称从HTTP中解析出来:
public class FrameDataGainer extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { String dataName = getDataName(request); // 省略 } }
6、当dataName为空时通过FrameDataGainer.getErrorResult()返回异常JSON对象,所以这里引入gson.jar
(1)进入https://code.google.com/p/google-gson/downloads/detail?name=google-gson-2.2.4-release.zip下载
(2)解压后把gson-2.2.4.jar复制到应用运行环境下的WEB-INF\lib下。我的环境位置为D:\medical\war\WEB-INF\lib
(3)在Eclipse工程中引入gson-2.2.4.jar
(4)定义com.medical.frame.bean.FrameResultBean.java对象
public class FrameResultBean { /** * 错误码 */ private int errorCode = 0; /** * 错误描述 */ private String errorDesc = null; public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getErrorDesc() { return errorDesc; } public void setErrorDesc(String errorDesc) { this.errorDesc = errorDesc; } }
(5)定义FrameDataGainer.getErrorResult()方法
/** * 返回请求异常Json对象 */ private String getErrorResult() { FrameResultBean resultBean = new FrameResultBean(); resultBean.setErrorCode(FrameErrorCode.REQUEST_BUS_NOT_EXIST); String errorCode = String.valueOf(FrameErrorCode.REQUEST_BUS_NOT_EXIST); String errorDesc = FrameCache.getInstance().getResourceValue(errorCode); resultBean.setErrorDesc(errorDesc); return gson.toJson(resultBean); }
(6)请求数据名称没有配置返回异常JSON对象
public class FrameDataGainer extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { String dataName = getDataName(request); if (dataName == null) { response.getWriter().write(getErrorResult()); return; } } }
7、当请求数据名称存在时,根据数据名称从缓存中读取业务对象
public class FrameDataGainer extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { // 省略 FrameDataBusiness business = FrameCache.getInstance().getDataBusinessMap(dataName); if (business == null) { response.getWriter().write(getErrorResult()); return; } } }
8、从业务配置中反射出数据请求动作处理类对象,并把相应的处理结果写入Response对象中
public class FrameDataGainer extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { // 省略 FrameDefaultAction action = FrameActionFactory.getInstance().implement(business.getBusinessClass()); action.setRequest(request); action.setResponse(response); action.setSession(request.getSession()); String resultData = action.execute(); response.getWriter().write(resultData == null ? getErrorResult() : resultData); } }
由于描述可能不清楚,可以参见流程,其处理过程与页面跳转的封装类似:
五、开发注册和登录功能
1、为让用户输入的密码不以明文显示,把D:\medical\war\module\login\login.html中的密码输入组件更改为password,如下:
<inputtype="password"class="login_user_input"placeholder="请输入密码"id="login_dynamic_user_pass"/>
2、修改login.html文件中注册、登录部分代码如下:
<div class="login_button_wrapper"> <a class="login_confirm_button" href="javascript:systemUserLogin()">登录</a> <a class="login_regist_button" href="javascript:systemUserRegist()">注册</a> </div>
3、在D:\medical\war\js\login\login.js文件中完成按钮响应动作:
(function( window){ /** * 用户登录方法 */ function systemUserLogin(isRegist) { var userName = $("#login_dynamic_user_name").val(); var userAuth = $("#login_dynamic_user_pass").val(); //1. 用户名或密码为空 if(!userName || !userAuth) { return; } //2. 用户名或密码长度不能超过20 if(userName.length > 20 || userAuth.length > 20) { return; } //3. 下发请求 var data = {"isRegist": isRegist, "userName": userName, "userAuth": userAuth}; asyncRequest("login.data", data, function(result) { // TODO alert(1); }); } /** * 对外公开方法 */ window.systemUserLogin = systemUserLogin; window.systemUserRegist = systemUserRegist; })( window );
4、由于在第3步中调用了common.js中封装的asyncRequest()方法,这里把代码粘贴如下:
/** * 下发AJAX异步请求 */ function asyncRequest(action, param, callback) { $.ajax( { type: "GET", dataType: "JSON", url: action, data: param, success: function(result) { callback(result); } }); }
5、定义数据业务处理配置文件login-data.xml,并把其放置到D:\medical\war\WEB-INF\config\login下
<?xml version="1.0" encoding="UTF-8"?> <business-config> <business name="login" business-class="com.medical.server.data.UserLoginDataAction" /> </business-config>
6、通过第四步的封装和login-data.xml的定义,当在客户端输入用户名和密码后,点击登录或注册按钮,就可以调用到UserLoginDataAction业务处理类,其定义如下:
/** * 斗医系统用户登录逻辑处理类 * * @author qingkechina */ public class UserLoginDataAction extends FrameDefaultAction { /** * 全局Gson对象 */ private final static Gson gson = new Gson(); /** * 缺省响应动作 */ public String execute() throws FrameException { // 1.从客户端获取用户名和密码 String userName = getParameter("userName"); String userAuth = getParameter("userAuth"); if (FrameUtil.isEmpty(userName) || FrameUtil.isEmpty(userAuth)) { UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_LOGIN_ERROR); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); return gson.toJson(loginBean); } // 2.用户名和密码的长度不能超过数据库字段的长度 if (userName.length() > 20 || userAuth.length() > 64) { UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_LOGIN_ERROR); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); return gson.toJson(loginBean); } // 3.用户名和密码一般都是汉字、字母、数字和特殊字符 // 这里留一个雷区,后面再讲解SQL注入时再进行完善 // 4.判断是注册还是登录 String isRegistUser = getParameter("isRegist"); if ("TRUE".equalsIgnoreCase(isRegistUser)) { return doRegistAction(userName, userAuth); } else { return doLoginAction(userName, userAuth); } } // 略去doRegistAction()和doLoginAction()方法 }
【备注】:这里暂时不考虑用户信息安全,自棱镜门之后网络安全在国内得到越来越多的重视。用户注册登录的用户信息在客户端输入、传输、服务端验证、加解密等角度都需要考虑,这里暂时留一个彩蛋,以备后续安全专题时使用。
7、用户注册的处理逻辑:
先拿输入的用户名去数据库查找
(1)若查到说明存在重名的用户,请注册用户再选一个名称
(2)若查不到则把用户数据插入数据库,并返回注册成功信息
/** * 用户注册处理方法 */ private String doRegistAction(String userName, String userAuth) { // 1. 判断数据库中是否已存在该用户名 UserDAO user = UserUtil.getUserByName(userName); if(user != null) { UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_SAME_ERROR); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); return gson.toJson(loginBean); } // 2. 把用户入库 UserUtil.insertUser(userName, userAuth); UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_REGIST_SUCCESS); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); loginBean.setForwardPath("main.act"); return gson.toJson(loginBean); }
【备注】:
(1)、在《【斗医】【9】Web应用开发50天》文中,UserDao对象的数据映射文件主键定义了如下的样式:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.medical.server.dao"> <class name="UserDAO" table="USERTABLE"> <id name="userId" column="userId" type="string"> <generator class="uuid.hex" /> </id> <property name="userAuth" column="userAuth" /> <property name="userSign" column="userSign" /> <property name="attention" column="attention" /> </class> </hibernate-mapping>
请留意<generator class="uuid.hex" />,它表明在向数据库插入记录时,Hibernate会调用uuid.hex的方式生成一个32位的主键;另一方面在创建数据表usertable时,指定userId字段的长度为20,所以入库时会抛字错内容这长的异常提示,这里把映射文件中的<generator class="uuid.hex" />删除掉,具体见附件。
(2)、再说一下Hibernate的保存问题,我在UserUtil.java中定义了保存UserDAO的一个方法,如下:
/** * 把用户入库 */ public static void insertUser(String userName, String userAuth) { UserDAO userDao = new UserDAO(); userDao.setUserId(userName); userDao.setUserAuth(userAuth); Session session = FrameDBUtil.openSession(); Transaction transaction = session.beginTransaction(); session.save(userDao); transaction.commit(); FrameDBUtil.closeSession(); }
网上有网友讨论保存不成功,因为他的写法如下:
Session session = FrameDBUtil.openSession();
session.save(userDao);
FrameDBUtil.closeSession();
因为这样的做法只是把userDao对象放入Hibernate缓存,而没有真正的入库。
8、用户登录的处理逻辑。先拿输入的用户名和密码去数据库查找,若查找到说明用户合法,否则用户非法
/** * 用户登录处理方法 */ private String doLoginAction(String userName, String userAuth) { // 1. 判断数据库中是否已存在该用户名 boolean isValideUser = UserUtil.isValideUser(userName, userAuth); if (isValideUser == false) { UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_NOT_EXIST_ERROR); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); return gson.toJson(loginBean); } // 2.返回用户登录成功JSON对象 UserLoginBean loginBean = new UserLoginBean(); loginBean.setErrorCode(FrameErrorCode.USER_LOGIN_SUCCESS); loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode())); loginBean.setForwardPath("main.act"); return gson.toJson(loginBean); }
【备注】:
由于涉及到UserUtil.java的相关方法,没有在文中专门指出,这里列出相关代码
/** * 通过用户名称查找用户DAO对象 */ public static UserDAO getUserByName(String userName) { Session session = FrameDBUtil.openSession(); Criteria criteria = session.createCriteria(UserDAO.class); criteria.add(Restrictions.eq("userId", userName)); List<?> userList = criteria.list(); FrameDBUtil.closeSession(); if (FrameUtil.isEmpty(userList)) { return null; } UserDAO userDao = (UserDAO)userList.get(0); return userDao; } /** * 通过用户名和密码验证用户是否合法 */ public static boolean isValideUser(String userName, String userAuth) { Session session = FrameDBUtil.openSession(); Criteria criteria = session.createCriteria(UserDAO.class); criteria.add(Restrictions.eq("userId", userName)).add(Restrictions.eq("userAuth", userAuth)); List<?> userList = criteria.list(); FrameDBUtil.closeSession(); if (FrameUtil.isEmpty(userList)) { return false; } return true; }
8、客户端JS收到服务端返回的结果后进行相应的逻辑处理
(1)若登录或注册失败,用户一定要看到失败信息,所以需要在war\etc\local\zh\resource.properties中定义相关的错误资源
500=用户名或密码错误 505=系统中已有同名用户 515=系统中不存在该用户
(2)完善war\js\login\login.js的异步请求,把从服务端收到的结果进行处理。当用户登录或注册成功后进入系统首页,否则弹框提示用户
/** * 用户登录方法 */ function systemUserLogin(isRegist) { var userName = $("#login_dynamic_user_name").val(); var userAuth = $("#login_dynamic_user_pass").val(); //1. 用户名或密码为空 if(!userName || !userAuth) { return; } //2. 用户名或密码长度不能超过20 if(userName.length > 20 || userAuth.length > 20) { return; } //3. 下发请求 var data = {"isRegist": isRegist, "userName": userName, "userAuth": userAuth}; asyncRequest("login.data", data, function(result) { var resultJson = eval(result); if(resultJson.errorCode != 510) { alert(resultJson.errorDesc); return; } // 跳转到相应页面 top.location = resultJson.forwardPath; }); }
到此为至,用户的注册和登录已处理完毕,在Eclipse中运行Tomcat,把http://localhost:8080/medical敲入浏览器中后,点击登录出现如下界面:
在该输入框中注册用户后页面可以正确地跳转到系统的首页,但细心的用户会发现系统首页的用户名那儿还是显示“登录”,而不是真正的用户名,由于51cto的这个编辑组件写的内容过长时,系统运行非常迟顿,所以在下一文处理这个问题。
【备注】:
(1)用户若在使用Hibernate过程中出现如下错误,说明是缺少antlr-3.1.2-1.jar.zip包
java.lang.ClassNotFoundException: antlr.SemanticException
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1462)
(2)截止目前系统所使用的jar包有如下,由于附件大小有限制,所以没有把jar悉数上传,若有需要的用户可以单独找我联系。
本文出自 “青客” 博客,请务必保留此出处http://qingkechina.blog.51cto.com/5552198/1386941
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。