day03-功能實現02

2022-12-17 06:00:31

家居網購專案實現02

5.功能04-會員登入

5.1需求分析/圖解

需求如圖:

image-20221216165739849 image-20221216165757967

  1. 輸入使用者名稱、密碼後提交
  2. 判斷該使用者是否存在
  3. 如果存在,顯示登入成功頁面
  4. 否則返回登入頁面,要求重新登入
  5. 要求改進登入密碼為md5加密

5.2思路分析

5.3程式碼實現

根據上述分析圖,在對應的層新增方法

5.3.1dao層

  1. 修改MemberDAO介面,宣告queryMemberByUsernameAndPassword()方法

    //提供一個通過使用者名稱和密碼返回對應的Member的方法
    public Member queryMemberByUsernameAndPassword(String username,String password);
    
  2. 修改MemberDAOImpl實現類,實現queryMemberByUsernameAndPassword()方法

    /**
     * 通過使用者名稱和密碼返回對應的Member物件
     *
     * @param username 使用者名稱
     * @param password 密碼
     * @return 返回值為對應的Member物件,如果不存在則返回null
     */
    @Override
    public Member queryMemberByUsernameAndPassword(String username, String password) {
        String sql = "SELECT * FROM `member` WHERE `username`=? AND `password`=MD5(?);";
        return querySingle(sql, Member.class, username, password);
    }
    
  3. 在utils包中的MemberDAOImplTest類中增加測試方法

    @Test
    public void queryMemberByUsernameAndPassword() {
        Member member = memberDAO.queryMemberByUsernameAndPassword
                ("king", "king");
        System.out.println("member=" + member);
    }
    
    image-20221216183533442

    程式碼測試通過

5.3.2service層

  1. 修改MemberService介面,宣告login方法

    //登入使用者
    //相比於直接傳遞使用者名稱和密碼,傳遞一個Member物件拓展性會比較好一些
    public Member login(Member member);
    
  2. 修改MemberServiceImpl介面實現類,實現login方法

    /**
     * 根據登入傳入的member資訊,返回對應的在資料庫中的member物件
     *
     * @param member
     * @return 返回的是資料庫中的member物件,若不存在則返回null
     */
    @Override
    public Member login(Member member) {
        return memberDAO.queryMemberByUsernameAndPassword
                (member.getUsername(), member.getPassword());
    }
    
  3. 在utils包中的MemberServiceImplTest類中增加測試方法

    @Test
    public void login() {
        Member member = memberService.login
                (new Member(null, "admin", "admin", null));
        System.out.println("member=" + member);
    }
    
    image-20221216185341949

    程式碼測試通過

5.3.3web層

  1. 設定loginServlet

    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.li.furns.web.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/loginServlet</url-pattern>
    </servlet-mapping>
    
  2. 建立LoginServlet

    package com.li.furns.web;
    
    import com.li.furns.entity.Member;
    import com.li.furns.service.MemberService;
    import com.li.furns.service.impl.MemberServiceImpl;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    
    public class LoginServlet extends HttpServlet {
        private MemberService memberService = new MemberServiceImpl();
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //1.接收使用者名稱和密碼
            //如果前端輸入的是null,後臺接收的資料為空串""
            String username = request.getParameter("username");
            String password = request.getParameter("password");
    
            //構建一個member物件
            Member member = new Member(null, username, password, null);
            //2.呼叫MemberServiceImpl的login方法
            if (memberService.login(member) == null) {//資料庫中沒有該使用者,返回登入頁面
                //注意路徑
                request.getRequestDispatcher("/views/member/login.html")
                        .forward(request, response);
            } else {
                //否則,跳轉到登入成功頁面
                request.getRequestDispatcher("/views/member/login_ok.html")
                        .forward(request, response);
            }
        }
    }
    

5.4完成測試

image-20221216192022479 image-20221216191948442

6.功能05-登入錯誤提示,表單回顯

6.1需求分析/圖解

image-20221216191834383
  1. 輸入使用者名稱,密碼後提交
  2. 如果輸入有誤,則給出提示
  3. 在登入表單回顯使用者名稱

6.2思路分析

在5.2分析圖的基礎上修改如下兩處:

image-20221216193057250

6.3程式碼實現

6.3.1web層

  1. 修改LoginServlet,將錯誤提示和使用者名稱放入request域中

    package com.li.furns.web;
    
    import com.li.furns.entity.Member;
    import com.li.furns.service.MemberService;
    import com.li.furns.service.impl.MemberServiceImpl;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    
    public class LoginServlet extends HttpServlet {
        private MemberService memberService = new MemberServiceImpl();
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //1.接收使用者名稱和密碼
            //如果前端輸入的是null,後臺接收的資料為空串""
            String username = request.getParameter("username");
            String password = request.getParameter("password");
    
            //構建一個member物件
            Member member = new Member(null, username, password, null);
    
            //2.呼叫MemberServiceImpl的login方法
            if (memberService.login(member) == null) {//資料庫中沒有該使用者,返回登入頁面
                //登入失敗,將錯誤資訊和登入會員名放入request域中
                request.setAttribute("errInfo", "登入失敗,使用者名稱或者密碼錯誤");
                request.setAttribute("username", username);
                //注意路徑
                request.getRequestDispatcher("/views/member/login.jsp")
                        .forward(request, response);
            } else {
                //否則,跳轉到登入成功頁面
                request.getRequestDispatcher("/views/member/login_ok.html")
                        .forward(request, response);
            }
        }
    }
    
  2. 將login.html改為login.jsp(檔案右鍵Refactor-->Rename,在彈窗中點選Do Refactor,會把其他檔案參照login.html的資訊自動改為login.jsp)

    部分程式碼,詳細程式碼請看 https://github.com/liyuelian/furniture_mall.git

    <div class="login-register-form">
        <%--提示錯誤資訊--%>
        <span class="errorMsg" 
              style="float: right; font-weight: bold; font-size: 20pt; margin-left: 10px;">
            ${requestScope.errInfo}
        </span>
        <form action="loginServlet" method="post">
            <input type="text" name="username" placeholder="Username" value="${requestScope.username}"/>
            <input type="password" name="password" placeholder="Password"/>
            <div class="button-box">
                <div class="login-toggle-btn">
                    <input type="checkbox"/>
                    <a class="flote-none" href="javascript:void(0)">Remember me</a>
                    <a href="#">Forgot Password?</a>
                </div>
                <button type="submit"><span>Login</span></button>
            </div>
        </form>
    

6.4完成測試

image-20221216201148932 image-20221216201221612

7.功能06-web層servlet減肥

7.1需求分析/圖解

image-20221216204810122
  1. 如圖,一個請求對應一個Servlet,會造成Servlet太多,不利於管理
  2. 在專案開發中,同一個業務(模組),一般對應一個Servlet即可,比如LoginServlet和RegisterServlet都處理和會員相關的業務,應當合併

7.2方案一-if-else

image-20221216210835186

前端頁面兩個表單login和register的action都提交到MemberServlet中

  1. 分別給兩個表單新增hidden元素,分別表示註冊和登入
  2. 當資訊提交到MemberServlet後,獲取action引數值
  3. 再根據不同的值來呼叫對應的方法即可(將原來的業務分別封裝到login方法和Register方法中)

7.3方案一程式碼實現

  1. 修改login.jsp,分別在login和register表單中新增hidden,兩個表單都提交到MemberServlet處理

    image-20221216211107418 image-20221216211200389
  2. 在web.xml中設定MemberServlet

    <servlet>
        <servlet-name>MemberServlet</servlet-name>
        <servlet-class>com.li.furns.web.MemberServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MemberServlet</servlet-name>
        <url-pattern>/memberServlet</url-pattern>
    </servlet-mapping>
    
  3. 實現MemberServlet

    package com.li.furns.web;
    
    import com.li.furns.entity.Member;
    import com.li.furns.service.MemberService;
    import com.li.furns.service.impl.MemberServiceImpl;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    
    public class MemberServlet extends HttpServlet {
        private MemberService memberService = new MemberServiceImpl();
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //獲取提交表單的hidden元素值,判斷進行login還是register業務
            String action = request.getParameter("action");
            if ("login".equals(action)) {
                //進入登入業務
                login(request, response);
    
            } else if ("register".equals(action)) {
                //進入註冊業務
                register(request, response);
            }
        }
    
        public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //1.接收使用者名稱和密碼
            //如果前端輸入的是null,後臺接收的資料為空串""
            String username = request.getParameter("username");
            String password = request.getParameter("password");
    
            //構建一個member物件
            Member member = new Member(null, username, password, null);
    
            //2.呼叫MemberServiceImpl的login方法
            if (memberService.login(member) == null) {//資料庫中沒有該使用者,返回登入頁面
                //登入失敗,將錯誤資訊和登入會員名放入request域中
                request.setAttribute("errInfo", "登入失敗,使用者名稱或者密碼錯誤");
                request.setAttribute("username", username);
                //注意路徑
                request.getRequestDispatcher("/views/member/login.jsp")
                        .forward(request, response);
            } else {
                //否則,跳轉到登入成功頁面
                request.getRequestDispatcher("/views/member/login_ok.html")
                        .forward(request, response);
            }
        }
    
        public void register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //接收使用者註冊資訊--引數名要以前端頁面的變數名為準
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String email = request.getParameter("email");
    
            //如果返回false,說明該使用者資訊可以註冊
            if (!memberService.isExistsUsername(username)) {
                //構建一個member物件
                Member member = new Member(null, username, password, email);
                if (memberService.registerMember(member)) {
                    //如果註冊成功,請求轉發到register_ok.html
                    request.getRequestDispatcher("/views/member/register_ok.html")
                            .forward(request, response);
                } else {
                    //註冊失敗,請求轉發到register_fail.html
                    request.getRequestDispatcher("/views/member/register_fail.html")
                            .forward(request, response);
                }
            } else {//否則不能進行註冊
                //請求轉發到login.html
                //後面可以加入提示資訊
                request.getRequestDispatcher("/views/member/login.jsp")
                        .forward(request, response);
            }
        }
    }
    

7.4方案二-反射+模板設計模式+動態繫結

雖然方案一也可以實現業務需求,但是隨著業務的增加,if-else語句也會隨之增多,程式碼可讀性變差,因此這裡使用第二種方案實現,思想如下:

image-20221216220943007 image-20221216220928145

每一個業務Servlet類中都會有doPost和doGet方法,現在建立一個BasicServlet抽象類,其他的業務Servlet類都繼承BasicServlet抽象類。

將業務類中的doPost和doGet方法抽象到BasicServlet中,當http請求到業務類時,因為業務類中沒有重寫doPost和doGet,就會到父類別BasicServlet中找並呼叫。

同時在父類別BasicServlet的doPost()方法中使用動態繫結,通過反射去獲取到子類中的某個業務方法,然後呼叫。

7.5方案二程式碼實現

  1. 修改MemberServlet,將doPost方法抽象到父類別BasicServlet中:

    package com.li.furns.web;
    
    import com.li.furns.entity.Member;
    import com.li.furns.service.MemberService;
    import com.li.furns.service.impl.MemberServiceImpl;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    
    /**
     * 該Servlet處理和Member相關的請求
     *
     * @author 李
     * @version 1.0
     */
    public class MemberServlet extends BasicServlet {
        private MemberService memberService = new MemberServiceImpl();
    
        /**
         * 處理會員登入業務
         *
         * @param request
         * @param response
         * @throws ServletException
         * @throws IOException
         */
        public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //1.接收使用者名稱和密碼
            //如果前端輸入的是null,後臺接收的資料為空串""
            String username = request.getParameter("username");
            String password = request.getParameter("password");
    
            //構建一個member物件
            Member member = new Member(null, username, password, null);
    
            //2.呼叫MemberServiceImpl的login方法
            if (memberService.login(member) == null) {//資料庫中沒有該使用者,返回登入頁面
                //登入失敗,將錯誤資訊和登入會員名放入request域中
                request.setAttribute("errInfo", "登入失敗,使用者名稱或者密碼錯誤");
                request.setAttribute("username", username);
                //注意路徑
                request.getRequestDispatcher("/views/member/login.jsp")
                        .forward(request, response);
            } else {
                //否則,跳轉到登入成功頁面
                request.getRequestDispatcher("/views/member/login_ok.html")
                        .forward(request, response);
            }
        }
    
        /**
         * 處理會員註冊業務
         *
         * @param request
         * @param response
         * @throws ServletException
         * @throws IOException
         */
        public void register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //接收使用者註冊資訊--引數名要以前端頁面的變數名為準
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String email = request.getParameter("email");
    
            //如果返回false,說明該使用者資訊可以註冊
            if (!memberService.isExistsUsername(username)) {
                //構建一個member物件
                Member member = new Member(null, username, password, email);
                if (memberService.registerMember(member)) {
                    //如果註冊成功,請求轉發到register_ok.html
                    request.getRequestDispatcher("/views/member/register_ok.html")
                            .forward(request, response);
                } else {
                    //註冊失敗,請求轉發到register_fail.html
                    request.getRequestDispatcher("/views/member/register_fail.html")
                            .forward(request, response);
                }
            } else {//否則不能進行註冊
                //請求轉發到login.html
                //後面可以加入提示資訊
                request.getRequestDispatcher("/views/member/login.jsp")
                        .forward(request, response);
            }
        }
    }
    
  2. 建立BasicServlet,在該抽象類中使用使用模板模式+反射+動態繫結

    package com.li.furns.web;
    
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    /**
     * 業務servlet的共同父類別
     * BasicServlet 是供子類去繼承的,不需要在web.xml中設定
     * 使用模板模式+反射+動態繫結===>簡化了多個if-else的語句
     *
     * @author 李
     * @version 1.0
     */
    public abstract class BasicServlet extends HttpServlet {
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //獲取提交表單的隱藏域元素的值
            //如果我們使用模板模式+反射+動態繫結,要滿足action的值要和方法名一致
            String action = req.getParameter("action");
    
            //使用反射,獲取到當前物件的方法
            //1.this就是請求的業務Servlet,即執行型別
            //2.declaredMethod 方法物件就是當前請求的業務servlet對應的action名稱的方法
            try {
                /**
                 * public Method getDeclaredMethod(){}
                 * 該方法返回一個Method物件,它反射此Class物件所表示的類或介面的指定已宣告方法。
                 * 引數:此方法接受兩個引數:
                 * -方法名稱,這是要獲取的方法。
                 * -引數型別 這是指定的方法的引數型別的陣列。
                 * 返回值:此方法以 Method 物件的形式返回此類的指定方法。
                 */
                Method declaredMethod =
                        this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
                //使用方法物件進行反射呼叫
                //public Object invoke(Object obj, Object... args){}
                declaredMethod.invoke(this, req, resp);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

之後再去開發業務類,只需要繼承BasicServlet即可,推薦使用方案二

7.6完成測試

註冊業務:

image-20221216212506916 image-20221216212521536

image-20221216212552315

登入業務:

image-20221216212830237 image-20221216212847672