天天看點

【鬥醫】【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”;

(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回車,按上面的使用者驗證,會發現前兩個英文用例成功,但中文用例存在亂碼問題,如下圖:  

<a href="http://s3.51cto.com/wyfs02/M02/23/83/wKioL1M5JCPgxu-WAAAe6wn9Xh4060.png" target="_blank"></a>

七、系統的編碼與亂碼

   系統出現亂碼的原因簡而言之是由于:輸入與輸出編碼不一緻,比如說浏覽器在Windows中文作業系統下運作,Chrome、FireFox預設是以GBK編碼顯示,此時若服務端傳給浏覽器的編碼是UTF-8,則就會形成亂碼。

<a href="http://s3.51cto.com/wyfs02/M01/23/82/wKiom1M5KiCy38apAAAbDg1Siy0785.png" target="_blank"></a>

解釋:

IE浏覽器根據作業系統預設選擇編碼,可通過“檢視 &gt; 編碼”來檢視;FireFox浏覽器在中文windows作業系統下預設使用unicode編碼,可通過“檢視 &gt; 字元編碼”檢視;Chrome浏覽器在中文windows作業系統下預設使用GBK編碼,可通過選擇“設定 &gt; 進階設定 &gt; 網絡内容 &gt; 自定義字型 &gt; 編碼”檢視;

Tomcat在中文windows作業系統下預設GBK編碼;但Java依賴于JVM的具體環境,一般都是以unicode編碼;

mysql安裝時預設以latin1編碼;

properties檔案當時設定時以utf-8編碼;

看着是不是有點暈了?是以若想不讓其亂碼,最好統一成同一個編碼,這裡使用UTF-8。

1、浏覽器顯示使用UTF-8編碼

這一點在前面寫HTML頁面時已指定頁面的編碼為UTF-8,如打開main.html在它的&lt;head&gt;中已說明

&lt;!--設定字元集--&gt;

&lt;meta http-equiv="content-type" content="text/html;charset=utf-8" /&gt;

2、浏覽器與Tomcat之間可以通過filter過濾器的方式,讓所有的請求和響應都使用UTF-8編碼

(1)在war\WEB-INF\web.xml配置編碼過濾器

&lt;filter&gt;

   &lt;filter-name&gt;encoder&lt;/filter-name&gt;

   &lt;filter-class&gt;com.medical.frame.FrameEncoderFilter&lt;/filter-class&gt;        

&lt;/filter&gt;

&lt;filter-mapping&gt;

   &lt;url-pattern&gt;/*&lt;/url-pattern&gt;

&lt;/filter-mapping&gt;

【備注】:由于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作業系統為例,進入“任務管理器 &gt; 程序”,右鍵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、打開所有程式 &gt; MySQL &gt; MySQL Server 5.5 &gt; MySQL 5.5 Command Line Client指令工具

II、在視窗中輸入密碼進入

III、執行如下SQL語句,如圖

<a href="http://s3.51cto.com/wyfs02/M00/23/82/wKiom1M5Mxix3xs7AAA44DZYCx0047.png" target="_blank"></a>

(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);

再測試一下用例三,結果如下:

<a href="http://s3.51cto.com/wyfs02/M01/23/83/wKiom1M5PJmjwRrUAAAUdNw1KrI980.png" target="_blank"></a>

八、全局資訊提示欄

   做過C/S架構開發的肯定知道模态對話框和非模态對話框的概念,提示資訊正是通過對話框來展現給使用者的;當然B/S架構也不離外,它也有模态和非模态的對話框。

   但細心的您肯定關注到了:現在的網站展現形式越來越“Web化”。以前大家都用翻頁突然一天各大中型網站好像都不翻頁了,而改為拖拽樣式,像浏覽百度圖檔等。

   這種樣式的變化不是必然的,它更符合使用者的操作習慣。

   全局資訊提示框的大概思路,由showSystemGlobalInfo()方法生成一個div,并把它追加到&lt;body&gt; Dom元素上,然後由navigation.css全局樣式渲染它,當使用時直接調用showSystemGlobalInfo()方法,5鈔鐘之後div自動隐藏掉。

1、在common.js中定義公共方法showSystemGlobalInfo()

* 全局資訊提示:在螢幕最下方顯示

var sytemGlobalInfoDiv = null;

function showSystemGlobalInfo(message)

   // 隻初始化一次

   if(!sytemGlobalInfoDiv)

       sytemGlobalInfoDiv = $("&lt;div /&gt;").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的使用者,此時應該有錯誤提示,如下圖:

<a href="http://s3.51cto.com/wyfs02/M01/23/AC/wKioL1M-JcDAUE5dAAAY4LkjVr0258.png" target="_blank"></a>

【備注】:截止目前登入部分算是完成,裡面還涉及一些不安全的處理,一些沒有考慮到的,這裡暫時保留。接下來完成“下戰書”部分業務。

<a href="http://down.51cto.com/data/2364257" target="_blank">附件:http://down.51cto.com/data/2364257</a>

     本文轉自qingkechina 51CTO部落格,原文連結:http://blog.51cto.com/qingkechina/1390410,如需轉載請自行聯系原作者