【斗医】【12】Web应用开发20天
在上文中有意埋了几个安全彩蛋,以便后面在聊网络安全时使用。书接前言,对上文做过实践的朋友肯定会发现:当用户注册/登录成功后页面跳转到了系统首页,但首页的导航菜单并没有显示用户名。本文重点实现这个特性,同时也谈谈系统的编码问题。
六、注册/登录成功后导航菜单显示当前用户名
与JSP不同之处在于,《斗医》本着Web本质特点,让不了解Web的朋友在脑海中有一个整体思路,不要把Web应用想的过于神秘,所以页面展示部分放到了HTML中,服务端部分仅提供查询页面和提供数据,它们之间通过HTTP协议贯通。
正是这个原因当用户注册/登录成功后跳转到系统首页,浏览器开始渲染main.html页面,由于此时还没有让Javascript去服务端读取用户信息,所以当前用户名没有显示。
下面实现这个特性:
1、系统首页调用common.js的公共接口
(function(window){
$(document).ready(function(){
// 生成系统菜单
generateSystemMenu();
// 设置首页菜单被选中
selectSystemMenu("system_home_menu");
// 获取用户信息
getBreifUserInfo();
});
})(window);
2、common.js中定义getBreifUserInfo()方法,以实现异步向服务端获取数据
/**
* 获取用户的信息:用户名
*/
function getBreifUserInfo(){
asyncRequest("userBrief.data", null, function(result){
var briefUser = eval(result); // 其中eval是JS的不安全方法,不建议使用,这里留个彩蛋
$("#system_login_user_name").text(briefUser.userId);
});
}
3、配置获取用户信息的业务
在war\WEB-INF\config\sm下定义system-data.xml文件,里面配置如下业务:
<?xml version="1.0" encoding="UTF-8" ?>
<business-config>
<!--获取用户信息,导航菜单使用-->
<business name="userBrief" business-class="com.medical.server.data.UserBriefDataAction" />
</business-config>
4、定义com.medical.server.data.UserBriefDataAction.java类,它继承FrameDefaultAction类,同时重写execute()方法
@Override
public String execute() throws FrameException
{
UserDAO loginUser = FrameCache.getInstance().getUserBySession(session);
if(loginUser == null)
{
loginUser = new UserDAO();
loginUser.setUserId("游客");
}
return gson.toJson(loginUser);
}
这个方法中使用了FrameCache.getInstance().getUserBySession(session)方法,这个方法是从系统全局缓存中读取会话中的用户。既然有读取那么也有对应的设置,方法如下:
public class FrameCache
{
public UserDAO getUserBySession(HttpSession session)
{
return (UserDAO)session.getAttribute(FrameConstant.SYSTEM_CURRENT_LOGIN_USER);
}
public void setUserBySession(HttpSession session, UserDAO currentUser)
{
session.setAttribute(FrameConstant.SYSTEM_CURRENT_LOGIN_USER, currentUser);
}
}
这里又涉及到一个常量定义,其具体为:FrameConstant.SYSTEM_CURRENT_LOGIN_USER = “systemCurrentLoginUser”;
5、在《【斗医】【11】Web应用开发50天》的注册和登录中没有把当前用户放入全局缓存,下面进行修改:
(1)修改UserUtil.isValideUser()方法,把它更名为getUserDAO(),同时返回值由原来的boolean改为UserDAO
public static UserDAO getUserDAO(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 null;
}
return (UserDAO)userList.get(0);
}
(2)修改UserLoginDataAction的用户注册方法
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);
// 3. 存入会话对应的内存
user = new UserDAO();
user.setUserId(userName);
FrameCache.getInstance().setUserBySession(session, user);
// 4. 返回用户注册成功JSON对象
UserLoginBean loginBean = new UserLoginBean();
loginBean.setErrorCode(FrameErrorCode.USER_LOGIN_SUCCESS);
loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode()));
loginBean.setForwardPath("index.act");
return gson.toJson(loginBean);
}
(3)修改UserLoginDataAction的用户登录方法
private String doLoginAction(String userName, String userAuth)
{
// 1. 判断数据库中是否已存在该用户名
UserDAO cacheUser = UserUtil.getUserDAO(userName, userAuth);
if (cacheUser == null)
{
UserLoginBean loginBean = new UserLoginBean();
loginBean.setErrorCode(FrameErrorCode.USER_NOT_EXIST_ERROR);
loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode()));
return gson.toJson(loginBean);
}
// 2. 存入会话对应的内存
cacheUser.setUserAuth(null);
FrameCache.getInstance().setUserBySession(session, cacheUser);
// 3. 返回用户登录成功JSON对象
UserLoginBean loginBean = new UserLoginBean();
loginBean.setErrorCode(FrameErrorCode.USER_LOGIN_SUCCESS);
loginBean.setErrorDesc(FrameUtil.getErrorDescByCode(loginBean.getErrorCode()));
loginBean.setForwardPath("index.act");
return gson.toJson(loginBean);
}
【备注】:由于客户端并不需要用户的密码,因此没有必要把用户信息暴露,增加网络安全风险
(4)用例验证
用例1:
前提:系统中没有qingkechina用户
操作:进入系统的登录页面,注册名为qingkechina的用户
期望:注册成功且系统的菜单右上角能显示qingkechina用户名
用例2:
前提:系统中已有qingkechina用户
操作:进入系统的登录页面,以qingkechina用户登录
期望:登录成功且系统的菜单右上角能显示qingkechina用户名
用例3:
前提:系统中没有“陈许诺”用户
操作:进入系统的登录页面,注册名为“陈许诺”的用户
期望:注册成功且系统的菜单右上角能显示“陈许诺”用户名
打开Eclipse启动Tomcat成功后,在浏览器中输入http://localhost:8080/medical回车,按上面的用户验证,会发现前两个英文用例成功,但中文用例存在乱码问题,如下图:
七、系统的编码与乱码
系统出现乱码的原因简而言之是由于:输入与输出编码不一致,比如说浏览器在Windows中文操作系统下运行,Chrome、FireFox缺省是以GBK编码显示,此时若服务端传给浏览器的编码是UTF-8,则就会形成乱码。
各个国家的程序开发人员为了让系统支持各国的语言,都会采用UTF-8编码来解决全球化的问题。今天在网上搜索时也发现一个比较好玩的文章《大话编码》,感兴趣的可以看一看,就本系统来言存在如下边界:
解释:
IE浏览器根据操作系统缺省选择编码,可通过“查看 > 编码”来查看;FireFox浏览器在中文windows操作系统下缺省使用unicode编码,可通过“查看 > 字符编码”查看;Chrome浏览器在中文windows操作系统下缺省使用GBK编码,可通过选择“设置 > 高级设置 > 网络内容 > 自定义字体 > 编码”查看;
Tomcat在中文windows操作系统下缺省GBK编码;但Java依赖于JVM的具体环境,一般都是以unicode编码;
mysql安装时缺省以latin1编码;
properties文件当时设置时以utf-8编码;
看着是不是有点晕了?所以若想不让其乱码,最好统一成同一个编码,这里使用UTF-8。
1、浏览器显示使用UTF-8编码
这一点在前面写HTML页面时已指定页面的编码为UTF-8,如打开main.html在它的<head>中已说明
<!--设置字符集-->
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
2、浏览器与Tomcat之间可以通过filter过滤器的方式,让所有的请求和响应都使用UTF-8编码
(1)在war\WEB-INF\web.xml配置编码过滤器
<filter>
<filter-name>encoder</filter-name>
<filter-class>com.medical.frame.FrameEncoderFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encoder</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
【备注】:由于url-pattern配置为/*,它表明所有的请求都经过FrameEncoderFilter
(2)定义FrameEncoderFilter,让其实现Filter接口
public class FrameEncoderFilter implements Filter
{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
chain.doFilter(request, response);
}
}
(3)查看Mysql的编码,发现它以latin1编码的(具体查看办法可以问google)
I、关闭mysql进程。以我用的windows操作系统为例,进入“任务管理器 > 进程”,右键mysqld.exe结束进程树
II、打开C:\Program Files\MySQL\MySQL Server 5.5\my.ini文件,找到如下内容修改为utf8
default-character-set=utf8
character-set-server=utf8
【备注】:因为我安装在C盘,请读者根据自己的实际情况处理
III、重启Mysql进程。进入C:\Program Files\MySQL\MySQL Server 5.5\bin,双击mysqld.exe可执行文件
(4)重创建数据库medical和数据表usertable
I、打开所有程序 > MySQL > MySQL Server 5.5 > MySQL 5.5 Command Line Client命令工具
II、在窗口中输入密码进入
III、执行如下SQL语句,如图
(5)把Java的unicode转换为utf-8编码
/**
* 把ISO编码的字符串转换为UTF-8编码
*/
public static String convert2UTF8(String resource)
{
String descStr = resource;
try
{
byte[] resourceArray = resource.getBytes("ISO-8859-1");
descStr = new String(resourceArray, "utf-8");
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
return descStr;
}
/**
* 由错误码获取错误描述信息
*/
public static String getErrorDescByCode(int errorCode)
{
String errorCodeStr = String.valueOf(errorCode);
String errorDesc = FrameCache.getInstance().getResourceValue(errorCodeStr);
if (isEmpty(errorDesc))
{
return convert2UTF8("系统异常,错误码:" + errorCode);
}
return convert2UTF8(errorDesc);
}
再测试一下用例三,结果如下:
八、全局信息提示栏
做过C/S架构开发的肯定知道模态对话框和非模态对话框的概念,提示信息正是通过对话框来展现给用户的;当然B/S架构也不离外,它也有模态和非模态的对话框。
但细心的您肯定关注到了:现在的网站展现形式越来越“Web化”。以前大家都用翻页突然一天各大中型网站好像都不翻页了,而改为拖拽样式,像浏览百度图片等。
这种样式的变化不是必然的,它更符合用户的操作习惯。
全局信息提示框的大概思路,由showSystemGlobalInfo()方法生成一个div,并把它追加到<body> Dom元素上,然后由navigation.css全局样式渲染它,当使用时直接调用showSystemGlobalInfo()方法,5钞钟之后div自动隐藏掉。
1、在common.js中定义公共方法showSystemGlobalInfo()
/**
* 全局信息提示:在屏幕最下方显示
*/
var sytemGlobalInfoDiv = null;
function showSystemGlobalInfo(message)
{
// 只初始化一次
if(!sytemGlobalInfoDiv)
{
sytemGlobalInfoDiv = $("<div />").attr("class", "system_global_info").text(message);
sytemGlobalInfoDiv.appendTo($("body"));
}
// 停留5s钟后提示框自动消失
sytemGlobalInfoDiv.text(message).show();
setTimeout(function(){
sytemGlobalInfoDiv.hide();
}, 5000);
}
2、在navigation.css中定义样式
.system_global_info{
width: 100%;
height: 45px;
line-height: 45px;
color: #FFF;
font-size: 14px;
font-weight: 600;
text-align: center;
background-color: #0767C8;
position: absolute;
bottom: 0;
}
3、把用户注册/登录失败的地方更换为调用此方法,涉及login.js的systemUserLogin()方法
var resultJson = eval(result);
if(resultJson.errorCode != 510)
{
alert(resultJson.errorDesc);
showSystemGlobalInfo(resultJson.errorDesc);
return;
}
4、功能测试
(1)先在系统中创建名称为qingkechina的用户,确保创建成功
(2)再次创建名称为qingkechina的用户,此时应该有错误提示,如下图:
【备注】:截止目前登录部分算是完成,里面还涉及一些不安全的处理,一些没有考虑到的,这里暂时保留。接下来完成“下战书”部分业务。
页:
[1]