JavaWeb開發網上商城
JavaWeb開發知識總結(網上商城專案小結)
1. 資料庫設計
表的關係的設計如下:
2. 使用技術總結
2.1 BaseServlet的設計
實現處理請求的方式1:
* 針對每一個請求均建立一個Servlet的實現類進行處理,弊端是:當業務較為複雜和請求較多時,會使得Servlet類過多。 // 請求方式: // http://localhost:8080/website/UserLoginServlet -- 使用者登陸的Servlet public class UserLoginServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp){ ... } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { ... } } // http://localhost:8080/website/UserLogoutServlet -- 使用者退出的Servlet public class UserLogoutServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp){ ... } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { ... } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
實現處理請求的方式2:
* 每一個模組建立一個Servlet類,然後通過在請求的引數內傳遞要執行處理的方法名的方式進行整合,弊端:需要在Servlet中的get或post方法中使用if做多層判斷,使得邏輯較為混亂。 // 請求方式: // http://localhost:8080/website/UserServlet?method=login -- 訪問使用者登陸 public class UserServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp){ // 獲取要執行的方法的名稱,通過方法名稱執行對應的方法 String methodname = request.getParameter("method"); if("login".equals(methodname)) { login(req,resp); } else if () { } .... } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { this.doGet(req,resp); } public void login() { ... } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
實現處理請求的方式3:較為常用
* 編寫一個統一的繼承HttpServlet類的Servlet類,重寫service方法,在service方法中獲取請求中method引數,根據該引數通過反射執行對應的方法。並且所有的請求都會經過Servlet,方便控制,不直接訪問jsp頁面 /** * 所有的Servlet的基類,該類不在web.xml中配置 * 所有的Servlet均繼承至該類 * 該類封裝方法的執行及資源的轉發(封裝的方式根據需要可以自定義) */ public class BaseServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override // 重寫service方法,封裝所有的Servlet的請求處理方法 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 接收引數,獲取方法的名稱 String methodName = req.getParameter("method"); // 傳遞的執行的方法為空或者為空字串時,直接返回 if(methodName == null || "".equals(methodName)) { resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().println("執行的method方法為null"); return; } // 獲取當前物件的class物件,通過反射的方式執行對於該請求的對應的方法 Class<? extends BaseServlet> clas = this.getClass(); try { // 獲取傳遞過來的方法名的方法 Method method = clas.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 執行引數中的方法,返回值表示要轉發的資源路徑 String path = (String) method.invoke(this, req, resp); if(path != null) { // 轉發資源 req.getRequestDispatcher(path).forward(req, resp); } } catch (Exception e) { e.printStackTrace(); } } } // 使用者的Servlet繼承至BaseServlet // 訪問http://localhost:8080/website/UserServlet?method=userRegistUI -- 訪問使用者註冊頁面 public class UserServlet extends BaseServlet { private static final long serialVersionUID = 1L; // 跳轉使用者註冊的頁面 public String userRegistUI(HttpServletRequest request, HttpServletResponse response){ return "/jsp/regist.jsp"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
2.2 IOC方式實現介面和實現類的解耦合
Java程式設計提倡面向介面程式設計,並且要遵循OCP(open-close-principle)原則(對程式擴充套件開放,對程式原始碼修改關閉)。
# 1. 面向介面程式設計: 在設計中,提供dao層和service的介面,並提供實現類,在需要使用dao層處理資料時,直接建立dao層實現類的物件進行處理,這種方式使得介面和實現類出現了高度的耦合,當dao層實現類變化時,需要修改原始碼建立實現類物件,違背了OCP原則. # 2.使用工廠設計模式: 工廠模式是在工廠類中提供建立dao層和service介面的實現類物件的方法,在需要使用dao層或service介面的實現類時,工廠類提供建立介面實現類的物件,通過工廠類進行獲取,但當介面的實現類發生變化時,需要在工廠類中進行改變,這種方式實際上是將介面和實現類的耦合轉變為了介面和工廠類的耦合,並且也需要更改原始碼。 # 3.IOC模式實現程式間接耦合:IOC-控制反轉 --> 工廠模式+反射+配置檔案 在工廠方法中通過讀取配置檔案中配置介面的實現類的完全限定名,通過反射建立介面的實現類物件,當需求改變時,只需改變配置檔案中介面對應的實現類即可。 XML配置檔案:配置介面和實現類的對應關係; dom4j:實現讀取配置檔案(使用XPath技術輔助讀取) 反射:使用反射獲取讀取介面對應的實現類的物件
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
/** * dao層和業務層介面的工廠類 */ public class BeanFactory { // 獲取業務層和dao層的介面的實現類 public static Object getBean(String beanid) { try { // 讀取配置檔案,從xml配置檔案中獲取介面的實現類 // 建立讀取xml檔案的物件 SAXReader reader = new SAXReader(); // 獲取檔案的Document物件 Document document = reader.read(BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.xml")); // 獲取指定id名稱的子元素,通過XPath獲取元素 Element element = (Element) document.selectSingleNode("//bean[@id='"+beanid+"']"); // 獲取子元素中class屬性的值 String classname = element.attributeValue("class"); // 通過反射獲取class物件 Class<?> class1 = Class.forName(classname); // 建立例項 return class1.newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
<!--applicationContext.xml 介面和實現類的配置檔案,放置在專案的src目錄下--> <?xml version="1.0" encoding="UTF-8"?> <beans> <!-- dao層的實現類配置 --> <!-- UserDao的配置實現類 --> <bean id="UserDao" class="com.itheima.store.dao.impl.UserDaoImpl"/> <!-- CategoryDao的配置實現類 --> <bean id="CategoryDao" class="com.itheima.store.dao.impl.CategoryDaoImpl"/> <!-- ProductDao的配置實現類 --> <bean id="ProductDao" class="com.itheima.store.dao.impl.ProductDaoImpl"/> <!-- OrderDao的配置實現類 --> <bean id="OrderDao" class="com.itheima.store.dao.impl.OrderDaoImpl"/> <!-- 業務層層的實現類配置 --> <!-- UserService的配置實現類 --> <bean id="UserService" class="com.itheima.store.service.impl.UserServiceImpl"/> <!-- CategoryService的配置實現類 --> <bean id="CategoryService" class="com.itheima.store.service.impl.CategoryServiceImpl"/> <!-- ProductService的配置實現類 --> <bean id="ProductService" class="com.itheima.store.service.impl.ProductServiceImpl"/> <!-- OrderService的配置實現類 --> <bean id="OrderService" class="com.itheima.store.service.impl.OrderServiceImpl"/> </beans>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
2.3 分頁資料工具類的封裝
分頁資料的顯示:分頁顯示有5個必要元素:
當前頁
,每頁顯示條數
,總記錄數
,總頁數
,要顯示的資料
.將這5個必須元素封裝到javabean類中,並且其中的總記錄數和顯示的資料需要通過查詢獲取,其他的可以直接計算,則需要定義兩個方法獲取總記錄數和顯示的資料的集合。
// 分頁的javabean類封裝,封裝時資料集合採用泛型形式,可以進行重用 public class PageBean<T> { private Integer currPage; // 當前頁 private Integer totalPage; // 總頁數 private Integer totalCount; // 記錄的總條數 private Integer pageSize; // 每頁顯示的條數 private List<T> list; // 查詢的資料的集合 ... get和set方法 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
/** * 生成分頁資料的工具類 */ public class PageUtils { // 定義每頁顯示的資料條數,預設值 public static Integer pageSize = 12; /** * 分頁資料封裝 * @param: classname 要執行Dao層實現類 * @param: countmethod 計算總條數的方法名 * @param: pagemethod 計算總頁數的方法名 * @param: param 查詢資料的條件 * @param: currPage 當前頁 * @return: PageBean<T> 返回PageBean物件 */ public static <T> PageBean<T> getPagebean(Object obj, String countmethod,String pagemethod, String param, Integer currPage, Integer spageSize) { // 每頁顯示記錄預設是12條,當傳入的引數為不為null是就顯示定製顯示的條數 if(spageSize != null) { pageSize = spageSize; } PageBean<T> pageBean = null; try { // 獲取class物件 Class<?> c = obj.getClass(); // 定義分頁封裝物件 pageBean = new PageBean<T>(); // 1.設定當前頁 pageBean.setCurrPage(currPage); // 2.設定查詢的資料的總條數 // 呼叫dao層查詢資料的總條數 Method method_count = c.getMethod(countmethod, String.class); Integer totalCount = (Integer) method_count.invoke(obj, param); pageBean.setTotalCount(totalCount); // 3.設定每頁顯示的條數 pageBean.setPageSize(pageSize); // 4.設定總頁數 Double ceil = Math.ceil((totalCount*1.0 / pageSize)); int totalPage = ceil.intValue(); pageBean.setTotalPage(totalPage); // 5.查詢資料 // 呼叫業務層查詢資料 Method method_page = c.getMethod(pagemethod, String.class, Integer.class, Integer.class); int begin = (currPage - 1) * pageSize; List<T> list = (List<T>) method_page.invoke(obj, param, begin, pageSize); // 將資料封裝到PageBean物件中 pageBean.setList(list); } catch (Exception e) { e.printStackTrace(); } // 將每頁顯示的記錄條資料設為初始值 pageSize = 12; return pageBean; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
2.4 全域性字符集編碼過濾器
為解決通過request獲取引數時中文亂碼問題,可以設定統一字符集過濾器:
/** * 通過動態代理的方式,建立request物件的動態代理物件 */ public class CharacterEncodingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 將ServletRequest的物件轉換為HttpServletRequest物件 final HttpServletRequest hsrequest = (HttpServletRequest) request; // 通過動態代理的方式,在原始的request的獲取引數之前設定編碼格式,防止中文亂碼 HttpServletRequest myrequest = (HttpServletRequest) Proxy.newProxyInstance(hsrequest.getClass().getClassLoader(), hsrequest.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取請求的方式 String reqmethod = hsrequest.getMethod(); // get方式只增強getParameter方法 // 獲取方法的名稱 String methodName = method.getName(); if("get".equalsIgnoreCase(reqmethod)) { // 當方法名為getParameter改變編碼 if("getParameter".equals(methodName)) { // 獲取引數的值,並進行轉換編碼 String value = (String) method.invoke(hsrequest, args); return new String(value.getBytes("ISO-8859-1"), "UTF-8"); } // post方式只增強getParameter和getParameterMap方法 } else if("post".equalsIgnoreCase(reqmethod)) { if("getParameter".equals(methodName) || "getParameterMap".equals(methodName)) { // 設定request域中的編碼 hsrequest.setCharacterEncoding("UTF-8"); } } return method.invoke(hsrequest, args); } }); chain.doFilter(myrequest, response); } @Override public void destroy() { } } // 在專案的web.xml檔案中配置過濾器 <!-- 統一字符集編碼過濾器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>com.store.web.filter.CharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> // 過濾所有的請求 </filter-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
2.5 使用者模組:
2.5.1 使用者註冊
非同步校驗註冊的使用者名稱是否可用:
通過Ajax的技術,在使用者輸入註冊的使用者名稱時進行向後臺校驗輸入的使用者名稱是否可用。
/** * 使用者模組的Servlet */ public class UserServlet extends BaseServlet { private static final long serialVersionUID = 1L; /** * 使用者名稱的非同步校驗是否可用 */ public String checkUsername(HttpServletRequest request, HttpServletResponse response){ try { // 接收引數 String username = request.getParameter("username"); // 呼叫業務層進行查詢 // UserService userService = new UserServiceImpl(); UserService userService = (UserService) BeanFactory.getBean("UserService"); // 查詢使用者 User existUser = userService.findByUsername(username); response.setContentType("text/html;charset=UTF-8"); if(existUser == null) { // 使用者名稱可以使用,返回ok // json表示式需要加雙引號,否則在jsp頁面中無法識別為json資料 response.getWriter().println("{\"msg\":\"ok\"}"); } else { // 使用者名稱已被佔用,返回no response.getWriter().println("{\"msg\":\"no\"}"); } } catch (Exception e) { e.printStackTrace(); } return null; // Ajax的請求不進行轉發 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
$.post(path+"/UserServlet",{method:"checkUsername",username:$("#username").val()}, function(data){ var state = data.msg; // msg為ok代表使用者名稱可用 if(state == "ok") { // 提示資訊 layer.tips('使用者名稱可用', '#username', { tips: [2, '#038E09'] //還可配置顏色 綠色:#00FF00 紅色:FF0000 }); $("#subbtn").attr("disabled", false); // msg為no代表使用者名稱不可用 } else { layer.tips('使用者名稱已被佔用', '#username', { tips: [2, '#FF0000'] //還可配置顏色 綠色:#00FF00 紅色:FF0000 }); $("#subbtn").attr("disabled", true); } },"json");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
使用者註冊:使用BeanUtils封裝資料時,封裝時間型別的資料,需要自定義轉換類,然後在Beanutils封裝資料時進行註冊。
// 註冊的方法 public String registUser(HttpServletRequest request, HttpServletResponse response){ try { // 獲取令牌口口令的字串,防止表單的重複提交 // 獲取session域中的口令 String stoken = (String) request.getSession().getAttribute("token"); // 當session中的口令資料為null,代表表單是重複提交,直接返回到msg.jsp頁面進行提示 if(stoken == null) { request.setAttribute("msg", "親,您註冊請求已提交,請您不要重複提交!"); return "/jsp/msg.jsp"; } // 當session中的口令資料不為空時,表示該表單是第一次提交 // 獲取表單中隱藏欄位的口令資料 String ftoken = request.getParameter("token"); // 當session域中的口令資料和表單中的口令資料不一致時,表示表單中的口令被篡改,直接返回提示 if(!stoken.equals(ftoken)) { request.setAttribute("msg", "親,您的註冊資料被篡改,請您重新註冊!"); return "/jsp/msg.jsp"; } // 當session中的口令資料和表單中的口令資料一致時進行使用者的註冊 // 接收引數,封裝資料 Map<String, String[]> parameterMap = request.getParameterMap(); User user = new User(); // 定義字串轉換為date型別 ConvertUtils.register(new DateConver(), Date.class); // 呼叫beanutils進行封裝資料 BeanUtils.populate(user, parameterMap); // 呼叫業務層進行註冊使用者 // UserService userService = new UserServiceImpl(); UserService userService = (UserService) BeanFactory.getBean("UserService"); int status = userService.registUser(user); // 當status不為0時表示註冊成功 if(status != 0) { request.setAttribute("msg", "歡迎您的註冊,請前往您的 "+user.getEmail()+" 郵箱進行啟用!"); } else { request.setAttribute("msg", "對不起,註冊失敗,請重新註冊!"); } // 使用者註冊完畢後需要將session域中的令牌口令資料清除 request.getSession().removeAttribute("token"); } catch (Exception e) { e.printStackTrace(); } return "/jsp/msg.jsp"; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
/** * 日期和字串轉換的工具類 */ public class DateConver implements Converter { @Override /** * 將value 轉換 c 對應型別 * 存在Class引數目的編寫通用轉換器,如果轉換目標型別是確定的,可以不使用c 引數 */ public Object convert(Class c, Object value) { // 將String轉換為Date --- 需要使用日期格式化 DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = dateFormat.parse((String)value); return date; } catch (ParseException e) { e.printStackTrace(); } return null; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2.5.2 郵箱連結啟用使用者
郵箱相關內容:
郵件傳送的相關的概念: * 郵箱伺服器 :如果一臺電腦安裝了郵箱伺服器的軟體,這臺電腦稱為是郵箱伺服器. * 電子郵箱:其實就是郵箱伺服器上的一塊空間,通過電子郵箱賬號訪問這塊空間的資料. * 收發郵件的協議: * 發郵件:SMTP協議:SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協議,它是一組用於由源地址到目的地址傳送郵件的規則,由它來控制信件的中轉方式。 25預設埠號 * 收郵件:POP3協議:POP3,全名為“Post Office Protocol - Version 3”,即"郵局協議版本3"。是TCP/IP協議族中的一員。預設埠是110 * 收郵件:IMAP協議:IMAP(Internet Mail Access Protocol,Internet郵件訪問協議)以前稱作互動郵件訪問協議(Interactive Mail Access Protocol)。IMAP是斯坦福大學在1986年開發的一種郵件獲取協議。 * 收發郵件的過程:收郵件都要經過有郵件的POP3伺服器,傳送郵件都要經過SMTP伺服器,兩個郵箱間不是點對點通訊.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
java中傳送郵件:需要jar包:
mail.jar
/** * 郵箱工具類 */ public class MailUtils { public static void sendMail(String to, String code) { try { // 獲得連線: Properties props = new Properties(); Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("[email protected]", "somnus"); } }); // 構建郵件: Message message = new MimeMessage(session); message.setFrom(new InternetAddress("[email protected]")); // 設定收件人: // TO:收件人 CC:抄送 BCC:暗送,密送. message.addRecipient(RecipientType.TO, new InternetAddress(to)); // 主題: message.setSubject("來自商城的啟用郵件!"); // 正文: message.setContent( "<h1>來自購物天堂商城的啟用郵件:請點選下面連結啟用!</h1><h3><a href='http://localhost:8080/store_v2.0/UserServlet?method=active&code=" + code + "'>http://localhost:8080/store_v2.0/UserServlet?method=active&code=" + code + "</a></h3>", "text/html;charset=UTF-8"); // 傳送郵件: Transport.send(message); } catch (MessagingException e) { e.printStackTrace(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
2.5.3 使用者登入模組
登陸的非同步校驗及自動登陸和記住使用者名稱
登陸資訊的非同步校驗:點選登陸時進行Ajax請求校驗使用者名稱和密碼及驗證碼是否正確,返回校驗的標識。
自動登陸:使用者登陸成功後將使用者資訊儲存在session中,並將使用者名稱和密碼儲存到客戶端的Cookie中;再次訪問時,通過配置的過濾器攔截,先檢視session中含有使用者的資訊(瀏覽器未關閉,重新請求頁面),當session中含有使用者資訊則直接放行;當session中沒有使用者資訊時,檢查使用者請求帶過來的Cookie中是否有儲存的使用者資訊,如果沒有使用者資訊則直接放行;如果有使用者的資訊則查詢資料庫校驗Cookie中的使用者名稱和密碼是否正確(防止惡意更改資料),如果校驗成功,則將使用者的資訊儲存在session中並放行,如果校驗失敗(使用者資訊被篡改)直接放行;使用者點選安全退出時,需要將Cookie和session中的儲存的使用者的資訊進行清除。
記住使用者名稱:使用者登入成功後,將使用者名稱寫到客戶端的Cookie中並設定儲存時間,使用者訪問登陸頁面時,在跳轉之前,獲取使用者帶來的Cookie中的使用者名稱資訊,如果存在,則將使用者名稱取出儲存在request域中,在登陸的jsp頁面取出作為使用者名稱輸入框的value值即可;使用者取消記住使用者名稱時將使用者端的Cookie中的使用者名稱清除即可。
public class UserServlet extends BaseServlet { ... /** * 使用者登陸頁面 */ public String userLoginUI(HttpServletRequest request, HttpServletResponse response){ // 獲取客戶端請求中的Cookie 實現記住使用者名稱功能 Cookie[] cookies = request.getCookies(); // 查詢是否記住使用者名稱的Cookie Cookie findCookie = CookieUtils.findCookie(cookies, "remember"); // 當Cookie有記住使用者名稱時,將使用者名稱儲存在request域中 if(findCookie != null) { request.setAttribute("remember", findCookie.getValue()); } return "/jsp/login.jsp"; } /** * 使用者登陸的校驗 */ public String checkLogin(HttpServletRequest request, HttpServletResponse response){ // 獲取表單中的資料 String code = request.getParameter("code"); String username = request.getParameter("username"); String password = request.getParameter("password"); // 校驗表單中的資料 response.setContentType("text/html;charset=UTF-8"); try { if(code == null || "".equals(code) || username == null || "".equals(username) || password == null || "".equals(password)) { response.getWriter().println("{\"msg\":\"null\"}"); return null; } } catch (IOException e) { e.printStackTrace(); } // 獲取session中的驗證碼 String incode = (String) request.getSession().getAttribute("iconCode"); // 清除session中本次的驗證碼 request.getSession().removeAttribute("iconCode"); // 校驗驗證碼是否正確 if(!incode.equalsIgnoreCase(code)) { try { response.getWriter().println("{\"msg\":\"no\"}"); } catch (IOException e) { e.printStackTrace(); } return null; } // 封裝資料 User user = new User(); user.setUsername(username); user.setPassword(password); // 呼叫業務層進行查詢使用者是否存在 // UserService userService = new UserServiceImpl(); UserService userService = (UserService) BeanFactory.getBean("UserService"); try { User existUser = userService.checkUser(user); // 判斷使用者是否存在 if(existUser == null) { response.getWriter().println("{\"msg\":\"no\"}"); } else { // 使用者登陸成功 // 將使用者資訊儲存在session中 // 判斷是否勾選自動登陸,如果自動登陸勾選,則將使用者的登陸資訊儲存在cookie中 String autoLogin = request.getParameter("autoLogin"); if("true".equals(autoLogin)) { // 將使用者名稱和密碼以 username敏password形式儲存在Cookie中 String username_password = existUser.getUsername()+"敏"+existUser.getPassword(); // 將使用者名稱和密碼進行加密,將加密後的字串儲存在Cookie中 String encrypt = DesUtils.encrypt(username_password); Cookie cookie = new Cookie("autoLogin",encrypt); // 設定Cookie的有效路徑 cookie.setPath(request.getContextPath()); // 有效路徑是當前專案的路徑 // 設定Cookie的有效時間,7天 cookie.setMaxAge(60 * 60 * 24 * 7); // 將Cookie寫到客戶端 response.addCookie(cookie); } // 判斷是否勾選記住使用者名稱,勾選時,則將使用者名稱儲存在Cookie中 String remember = request.getParameter("remember"); if("true".equals(remember)) { Cookie rcookie = new Cookie("remember", existUser.getUsername()); // 設定Cookie的有效路徑 rcookie.setPath(request.getContextPath()); // 有效路徑是當前專案的路徑 // 設定Cookie的有效時間,7天 rcookie.setMaxAge(60 * 60 * 24 * 7); // 將Cookie寫到客戶端 response.addCookie(rcookie); } else { Cookie[] cookies = request.getCookies(); Cookie findCookie = CookieUtils.findCookie(cookies, "remember"); if(findCookie != null) { // 設定Cookie的有效路徑 findCookie.setPath(request.getContextPath()); // 有效路徑是當前專案的路徑 // 設定Cookie的時間為0 findCookie.setMaxAge(0); // 將Cookie寫到客戶端 response.addCookie(findCookie); } } // 將用登陸的資訊儲存在session中 request.getSession().setAttribute("existUser", existUser); response.getWriter().println("{\"msg\":\"ok\"}"); } return null; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 使用者安全退出的方法 * @Title: userLogOut * @Description: TODO(安全退出) * @param: @param request * @param: @param response * @param: @return * @return: String */ public String userLogOut(HttpServletRequest request, HttpServletResponse response){ // 1. 清除session中的資訊 request.getSession().removeAttribute("existUser"); // 2. 清除Cookie中儲存的使用者的資訊 Cookie[] cookies = request.getCookies(); Cookie findCookie = CookieUtils.findCookie(cookies, "autoLogin"); if(findCookie != null) { // 設定Cookie的有效路徑 findCookie.setPath(request.getContextPath()); // 有效路徑是當前專案的路徑 // 設定Cookie的時間為0 findCookie.setMaxAge(0); // 將Cookie寫到客戶端 response.addCookie(findCookie); } // 跳轉到主頁面 return "/index.jsp"; } ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
/** * 使用者自動登入的過濾器 */ public class AutoLoginFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest hsrequest = (HttpServletRequest) request; // 1.校驗session中是否含有使用者資訊 User existUser = (User) hsrequest.getSession().getAttribute("existUser"); // 1.1 session中含有使用者的登陸資訊,直接放行 if(existUser != null) { chain.doFilter(hsrequest, response); return; } // 1.2 session中不含有使用者的登陸資訊,則查詢Cookie中是否有使用者的登陸資訊 Cookie[] cookies = hsrequest.getCookies(); Cookie findCookie = CookieUtils.findCookie(cookies,"autoLogin"); // 2. Cookie中不含有使用者的資訊時,直接放行 if(findCookie == null) { chain.doFilter(hsrequest, response); return; } // 3. Cookie中含有使用者的資訊,則取出Cookie中的使用者名稱和密碼,到資料庫中進行校驗 String cookiValue = findCookie.getValue(); // 進行解密 try { String username_password = DesUtils.decrypt(cookiValue); User user = new User(); user.setUsername(username_password.split("敏")[0]); user.setPassword(username_password.split("敏")[1]); // 呼叫業務層進行查詢 // UserService userService = new UserServiceImpl(); UserService userService = (UserService) BeanFactory.getBean("UserService"); User checkUser = userService.checkUser(user); // 3.1 如果使用者資訊校驗不通過,直接放行 if(checkUser == null) { chain.doFilter(hsrequest, response); return; } // 3.2 如果使用者資訊校驗成功,則將使用者的資訊儲存在session域中,然後放行 hsrequest.getSession().setAttribute("existUser", checkUser); chain.doFilter(hsrequest, response); } catch (Exception e) { e.printStackTrace(); } } @Override public void destroy() { } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
<!-- 自動登入過濾器 web.xml檔案配置 --> <filter> <filter-name>AutoLoginFilter</filter-name> <filter-class>com.store.web.filter.AutoLoginFilter</filter-class> </filter> <filter-mapping> <filter-name>AutoLoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.6分類模組
使用快取技術實現分類資料的載入:首頁載入需要顯示分類資料,在首頁載入時使用Ajax非同步請求分類資料,由於分類資料是很少變動,可以將分類資料儲存在快取中,再次請求分類資料時可以不用從資料庫查詢,以提高效率。
# 常用的快取技術:常用的有EHCache,Memcached,Redis等快取技術,本次使用EHCache技術 * EHCache:Hibernate框架二級快取使用,使用時如果配置了overflowToDisk="true" 則需要將需要序列化的javabean實現序列化介面
- 1
- 2
- 3
- 4
EHCache的配置檔案:
<!--ehcache.xml配置檔案,放置在專案的src目錄下--> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 快取超出限制的數量時序列化到硬碟的路徑 --> <diskStore path="E:/develop/apache-tomcat-7.0.69/webapps/store_v2.0/ehcache" /> <!-- cache可以配置多個,通過配置的name屬性進行區分 --> <!-- name:代表緩衝的名稱 maxElementsInMemory:記憶體中能夠緩衝資料的條數 timeToLiveSeconds:緩衝的存活時間 overflowToDisk:當超出記憶體中快取數量時是否寫到本地硬碟 --> <!--categoryCache:分類資料的快取名稱--> <cache name="categoryCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
快取的使用:在分類模組的業務層進行使用
// 分類模組的Servlet public class CategoryServlet extends BaseServlet { private static final long serialVersionUID = 1L; /** * 查詢所有的分類資料 */ public String findAllCategory(HttpServletRequest request, HttpServletResponse response) { // 呼叫業務層進行查詢所有的分類資料 // CategoryService categoryService = new CategoryServiceImpl(); CategoryService categoryService = (CategoryService) BeanFactory.getBean("CategoryService"); try { List<Category> list = categoryService.findAllCategory(); // 將資料轉換為json格式,傳送給主頁面 JSONArray jsonArray = JSONArray.fromObject(list); // 將資料傳遞給主頁面 response.setContentType("text/html;charset=UTF-8"); response.getWriter().println(jsonArray.toString()); } catch (Exception e) { e.printStackTrace(); } return null; } } /** * 分類的業務層的實現類CategoryServiceImpl */ public class CategoryServiceImpl implements CategoryService { // 定義當快取中有分類資料時,是否重新查詢資料庫分類資料,當為true時不查詢資料庫資料, // 當為false時,表明後臺對分類資料進行修改,需要重新查詢分類資料 private static Boolean flag = true; @Override // 查詢所有的分類資料 public List<Category> findAllCategory() throws SQLException { // 使用快取技術優化EHCache // 1.請求查詢所有的分類資料時,先查詢緩衝中是否存在資料 // 讀取配置檔案,建立快取CacheManager物件 CacheManager cacheManager = CacheManager.create(this.getClass().getClassLoader().getResourceAsStream("ehcache.xml")); // 獲取指定名稱的快取 Cache cache = cacheManager.getCache("categoryCache"); // 獲取快取中儲存的指定名稱的快取資料 Element element = cache.get("list"); List<Category> list = null; if(element == null || !flag){ // 3.緩衝中沒有資料時,到資料庫中查詢資料,並將資料儲存在緩衝中 // 當指定快取為null時,查詢資料庫,並將查詢的記過儲存到快取中 // 呼叫dao層查詢資料 // CategoryDao categoryDao = new CategoryDaoImpl(); CategoryDao categoryDao = (CategoryDao) BeanFactory.getBean("CategoryDao"); list = categoryDao.findAll(); // 將查詢的資料儲存在緩衝中 cache.put(new Element("list", list)); } else { // 2.緩衝中存在資料則直接返回到前臺頁面 // 當快取中存在資料,則直接返回該資料 list = (List<Category>) element.getObjectValue(); } // 修改標誌位 flag = true; return list; } ... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
2.7 商品模組
商品瀏覽記錄和分類顯示商品
商品瀏覽記錄:使用者瀏覽商品時,需要通過Servlet查詢查詢商品資訊,則記錄瀏覽商品的id,將id以一定格式寫到客戶端的Cookie中,使用者訪問商品分類時,會帶著瀏覽的記錄的商品id,在返回頁面之前將Cookie中的瀏覽記錄的商品的id查詢出商品的具體資料,並將資料儲存在request域中,在jsp頁面中迴圈遍歷即可。
分類顯示商品:根據使用者請求訪問的商品分類,查詢所有的屬於該分類下所有的商品的記錄並進行分頁,將資料儲存到request域中,同時將瀏覽記錄中的商品查詢並儲存在request域中,到jsp頁面進行顯示。
/** * 商品模組的Servlet */ public class ProductServlet extends BaseServlet { private static final long serialVersionUID = 1L; // 根據商品的分類id查詢商品資料(分頁顯示) public String findPageByCid(HttpServletRequest request, HttpServletResponse response) { try { // 接收引數 // 獲取分類的id String cid = request.getParameter("cid"); // 獲取當前頁 String currPage = request.getParameter("currPage"); // 呼叫業務層查詢商品資料 // ProductService productService = new ProductServiceImpl(); ProductService productService = (ProductService) BeanFactory.getBean("ProductService"); PageBean<Product> pageBean = productService.findPageByCid(cid, Integer.parseInt(currPage)); // 將查詢的資料儲存在request域中 request.setAttribute("pageBean", pageBean); // 讀取Cookie中的瀏覽歷史資料 Cookie[] cookies = request.getCookies(); Cookie findCookie = CookieUtils.findCookie(cookies, "history"); if(findCookie != null) { String pid = findCookie.getValue(); String[] ids = pid.split("-"); List<Product> list = new LinkedList<Product>(); for (String id : ids) { Product product = productService.findByPid(id); list.add(product); } request.setAttribute("history", list); } } catch (Exception e) { e.printStackTrace(); } return "/jsp/product_list.jsp"; } // 根據商品id查詢商品資訊 public String findByPid(HttpServletRequest request, HttpServletResponse response) { // 獲取商品id String pid = request.getParameter("pid"); // 將瀏覽的商品的id儲存到Cookie中 Cookie[] cookies = request.getCookies(); Cookie findCookie = CookieUtils.findCookie(cookies, "history"); String history = null; if(findCookie == null) { // 沒有查詢到Cookie時,表示Cookie中沒有商品瀏覽記錄 history = pid; } else { // 查到Cookie表示瀏覽過商品,將當前瀏覽的商品新增到Cookie中 String ids = findCookie.getValue(); String[] split = ids.split("-"); LinkedList<String> list = new LinkedList<String>(Arrays.asList(split)); if(list.contains(pid)) { list.remove(pid); list.addFirst(pid); } else { if(list.size() >= 6) { list.removeLast(); list.addFirst(pid); } else { list.addFirst(pid); } } StringBuilder sb = new StringBuilder(); for (String id : list) { sb.append(id).append("-"); } history = sb.toString().substring(0, sb.length()-1); } // 將瀏覽記錄寫到客戶端Cookie中,儲存時間是7天 findCookie = new Cookie("history", history); findCookie.setPath(request.getContextPath()); findCookie.setMaxAge(60 * 60 * 24 * 7); response.addCookie(findCookie); try { // 呼叫業務層查詢資料 // ProductService productService = new ProductServiceImpl(); ProductService productService = (ProductService) BeanFactory.getBean("ProductService"); Product product = productService.findByPid(pid); // 將資料儲存在request域中 request.setAttribute("product", product); } catch (Exception e) { e.printStackTrace(); } return "/jsp/product_info.jsp"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
2.8 訂單模組
BeanUtils工具類能封Map集合的資料到javabean物件中,則查詢資料庫時,多表查詢的結果存放在Map集合中,也可以使用BeanUtils工具類進行封裝到javabean物件中。
// 訂單的實體類 public class Order { private String oid; // 訂單id private Date ordertime; // 訂單時間 private Double total; // 訂單總金額 private Integer state; // 訂單狀態,1:未付款 2:已付款但為發貨 3:已發貨 4:確認收貨 private String address; // 收貨地址 private String name; // 收貨人 private String telephone; // 收貨人聯絡方式 private User user; // 訂單所屬使用者 /** * 儲存該訂單中的所有的訂單項,方便在查詢使用者訂單時使用 */ private List<OrderItem> orderItems = new LinkedList<OrderItem>(); ...get/set方法 } // 訂單項實體類 public class OrderItem { private String itemid; // 訂單項id private Integer count; // 商品個數 private Double subtotal; // 金額小計 private Product product; // 訂單項中的商品 private Order order; // 訂單項所屬的訂單 ...get/set方法 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
@Override /** * 根據使用者的id查詢該使用者的所有訂單 */ public List<Order> findByUid(String uid, Integer begin, Integer pageSize) throws SQLException, IllegalAccessException, InvocationTargetException { QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource()); // 先查詢該使用者所有的訂單 String sql = "select * from orders where uid=? order by ordertime desc limit ?,?"; List<Order> order_list = queryRunner.query(sql, new BeanListHandler<Order>(Order.class), uid, begin, pageSize); // 查詢訂單對應的訂單項 for (Order order : order_list) { // 查詢該訂單下的訂單項對應所有商品資訊 sql = "select * from orderitem o,product p where o.pid=p.pid and oid=?"; List<Map<String, Object>> map_list = queryRunner.query(sql, new MapListHandler() , order.getOid()); for (Map<String, Object> map : map_list) { // 封裝訂單項資料 OrderItem item = new OrderItem(); BeanUtils.populate(item, map); item.setOrder(order); // 封裝訂單項中商品資料 Product product = new Product(); BeanUtils.populate(product, map); item.setProduct(product); // 將訂單項新增到訂單中 order.getOrderItems().add(item); } } return order_list; } @Override /** * 根據訂單id查詢訂單資訊 */ public Order findByOid(String oid) throws SQLException, IllegalAccessException, InvocationTargetException { QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource()); // 1.查詢訂單資訊 String sql = "select * from orders where oid = ?"; Order order = queryRunner.query(sql, new BeanHandler<Order>(Order.class), oid); // 2.根據訂單id查詢訂單項 sql = "select * from orderitem o,product p where o.pid=p.pid and o.oid=?"; List<Map<String, Object>> map = queryRunner.query(sql, new MapListHandler(), oid); for (Map<String, Object> map2 : map) { // 3.封裝商品資料 Product product = new Product(); BeanUtils.populate(product, map2); // 4.封裝訂單項資料 OrderItem orderItem = new OrderItem(); BeanUtils.populate(orderItem, map2); orderItem.setProduct(product); orderItem.setOrder(order); // 5.將訂單項新增到訂單中 order.getOrderItems().add(orderItem); } return order; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
線上支付的流程:通常有:直接使用銀行的介面(需要自己和銀行進行對接);使用第三方的提供的支付介面。
# 方式1:直接和銀行網銀進行對接 缺點:開發人員需要了解各個銀行的網銀介面,當銀行的網銀系統進行升級時,需要修改原始碼。 優點:銀行和自身賬號直接對接,資金流通快。 # 方式2:使用第三方提供的網銀介面 優點:開發人員只需和第三方支付公司進行對接,不需要了解各個銀行的網銀介面,網銀升級時,不需要進行程式碼修改,第三方公司負責和銀行對接。 缺點:資金需要通過第三方公司進行中轉,可能會對資金鍊造成影響。 # 使用第三方支付公司介面的流程: 1.網站的支付連結會先經過第三方公司的網站; 2.第三方公司的網站根據使用者選用的支付通道(選擇的銀行)跳轉到指定的銀行的網銀頁面; 3.銀行網銀處理完畢後會將結果轉給第三方公司; 4.第三方公司會將支付的結果轉到自身的網站。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
2.9 後臺管理
檔案上傳:使用fileupload
工具類實現商品圖片的上傳
檔案上傳工具類:fileUpload工具類 ,兩個jar包
commons-fileupload-1.2.1.jar'和
commons-io-1.4.jar`# 檔案上傳三要素: 1. form表單的提交方式必須是POST方式 2. form表單中必須有欄位為file型別的欄位 <input type="file" name="upload" /> 3. 表單的enctype屬性值必須是:enctype="multipart/form-data" # fileUpload上傳檔案的步驟: 1. 建立磁碟項工廠類,用於對上傳檔案進行配置 2. 通過工廠類獲得Servlet的上傳檔案的核心解析類 3. 通過核心類解析request物件,獲取所有欄位的集合,集合中的內容是分割線分成的每個部分 4. 遍歷集合中每個部分 * 如果是普通項:直接獲取屬性名稱和屬性值 * 如果是檔案項:通過輸入輸出流讀取檔案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12