在上文中有意埋了幾個安全彩蛋,以便後面在聊網絡安全時使用。
書接前言,對上文做過實踐的朋友肯定會發現:當使用者注冊/登入成功後頁面跳轉到了系統首頁,但首頁的導航菜單并沒有顯示使用者名。本文重點實作這個特性,同時也談談系統的編碼問題。
六、注冊/登入成功後導航菜單顯示目前使用者名
與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”;
(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)
userdao cacheuser = userutil.getuserdao(username, userauth);
if (cacheuser == null)
loginbean.seterrorcode(frameerrorcode.user_not_exist_error);
// 2. 存入會話對應的記憶體
cacheuser.setuserauth(null);
framecache.getinstance().setuserbysession(session, cacheuser);
// 3. 傳回使用者登入成功json對象
【備注】:由于用戶端并不需要使用者的密碼,是以沒有必要把使用者資訊暴露,增加網絡安全風險
(4)用例驗證
用例1:
前提:系統中沒有qingkechina使用者
操作:進入系統的登入頁面,注冊名為qingkechina的使用者
期望:注冊成功且系統的菜單右上角能顯示qingkechina使用者名
用例2:
前提:系統中已有qingkechina使用者
操作:進入系統的登入頁面,以qingkechina使用者登入
期望:登入成功且系統的菜單右上角能顯示qingkechina使用者名
用例3:
前提:系統中沒有“陳許諾”使用者
操作:進入系統的登入頁面,注冊名為“陳許諾”的使用者
期望:注冊成功且系統的菜單右上角能顯示“陳許諾”使用者名
打開eclipse啟動tomcat成功後,在浏覽器中輸入http://localhost:8080/medical回車,按上面的使用者驗證,會發現前兩個英文用例成功,但中文用例存在亂碼問題,如下圖:
七、系統的編碼與亂碼
系統出現亂碼的原因簡而言之是由于:輸入與輸出編碼不一緻,比如說浏覽器在windows中文作業系統下運作,chrome、firefox預設是以gbk編碼顯示,此時若服務端傳給浏覽器的編碼是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>
<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的使用者,此時應該有錯誤提示,如下圖:
【備注】:截止目前登入部分算是完成,裡面還涉及一些不安全的處理,一些沒有考慮到的,這裡暫時保留。接下來完成“下戰書”部分業務。