Cookie與Session,書城訂單模組
尚硅谷JavaWeb筆記-09
Cookie
因為HTTP是無狀態(stateless)的,當我們想保存資訊就要用到Cookie
- 伺服器通知客戶端保存鍵值對的技術
- 白話:叫用戶保存住某個值
- 應用舉例:記住用戶名
- 由用戶的瀏覽器維護,視生命週期存在記憶體或硬碟中
- 單個最多4 KB
使用
很簡單就不多說了
- 在Java中是Cookie類
protected void doCookie(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 設定編碼
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
// 在Java造Cookie類物件
Cookie cookie1 = new Cookie("key1", "value1");
// 增,注意是response叫客戶端造cookie
response.addCookie(cookie1);
response.getWriter().write("cookie創建成功!");
// 取
Cookie[] cookies = request.getCookies();
// 取來的是所有cookies構成的數組
for (Cookie cookie2 : cookies) {
response.getWriter().write(cookie2.getName() + "=" + cookie2.getValue() + "\n");
// 可以做些if判斷拿出想要的
}
// 改
Cookie cookie3 = new Cookie("key1", "value3");
response.addCookie(cookie3); // 用同樣的"key1"去覆蓋"value1",變成"value3"
cookie3.setValue("value4"); // 或是用setValue
response.addCookie(cookie3); // 變成"value4"
// 控制生命,秒數
cookie3.setMaxAge(60);
// 刪
cookie3.setMaxAge(0);
// 生命跟隨瀏覽器
Cookie cookie5 = new Cookie("key5", "value5");
cookie5.setMaxAge(-1);
response.addCookie(cookie5);
// 設定路徑
Cookie cookie6 = new Cookie("key6", "value6");
cookie6.setPath("/abc");
cookie6.setMaxAge(-1);
request.getRequestDispatcher("http://localhost:8080/book_war_exploded/cookie.html");
}
編碼問題
- 由於是跟HTTP協議走的,value基本上只用英數字,不要用符號跟中文
- 想要中文可以用BASE64編碼之類的
設定路徑
- 起到一個過濾的效果,Cookie預設的Path是工程路徑
- 如果手動給一個Cookie指定Path,等於說用戶收到命令創建這個Cookie後,伺服器再想訪問這個Cookie,需要是符合這個路徑的請求,否則伺服器見不到該Cookie
- 類似說我在安親班造一個小孩,該小孩自帶的屬性是指定只有從某條路來的人可以訪問這個小孩
- 可以起到簡單的隔絕,防止接錯小孩
- 也能省資源,不然每次訪問都有一堆小孩傳來傳去
- 因為Cookie的傳遞是附加在每個HTTP請求中
- 類似說我在安親班造一個小孩,該小孩自帶的屬性是指定只有從某條路來的人可以訪問這個小孩
Session
- Session就是會話,是用來維護伺服器與用戶端之間的關聯
- 每一個用戶端有自己的一個Session
- Session由伺服器維護 (Cookie由客戶端的瀏覽器維護)
- 應用舉例:保存用戶登入後的訊息
使用
-
在Java中Session是一個接口(HttpSession)
-
getSession()
會判斷當前連線會話是否已經存在Session,若無則新造一個 -
每一個Session都有自己唯一的ID
// 增,注意是request造
HttpSession session = request.getSession();
// 判斷是否是新造的
boolean aNew = session.isNew();
// 獲取id
String id = session.getId();
// 生命週期
session.setMaxInactiveInterval(60);
// 獲取預設的最大生命,此值由web容器決定,可能自己在web.xml改
int maxInactiveInterval = session.getMaxInactiveInterval();
// 立刻殺死
session.invalidate();
在Session域存放資訊
request.getSession().setAttribute("key1", "value1");
request.getSession().getAttribute("key1");
與Cookie的關係
-
我們說Session是用來維持伺服器與客戶端之間的關聯,既然事情涉及到兩端,那肯定就不只一邊的事
-
已知Session由伺服器維護,存在於伺服器的記憶體中,我一個伺服器可能有無數的使用者,那怎樣知道某個Session連給哪個使用者?
-
一個Session會話是由客戶端發起,最初是客戶端發來的請求,所以是
request.getSession()
-
伺服器收到這個請求,會在伺服器的記憶體開闢一個Session的空間,這個空間有自己的
SessionID
-
同時,伺服器會要求客戶端創造一個Cookie,此Cookie的KEY固定是
JSESSIONID
,值就是對應的SessionID
,存在客戶端的瀏覽器 -
前面說過Cookie會附加在每個HTTP請求,因此會話就成立了,只要用戶端存在該Cookie就可以錨定到伺服器中的指定Session,直到某一方過期為止
書城項目
登出入
- 後端驗證登入成功同時將user物件保存到Session域,前端用sessionScope取出用戶資料
- 登出就刪掉session,轉發到首頁
驗證碼
- maven
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
- 在web.xml中可以設定
<servlet>
<servlet-name>Kaptcha</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
<!-- 定義驗證碼樣式 -->
<!-- 是否有邊框 -->
<init-param>
<param-name>kaptcha.border</param-name>
<!-- 沒有邊框 no -->
<param-value>yes</param-value>
</init-param>
<!-- 字體顏色 -->
<init-param>
<param-name>kaptcha.textproducer.font.color</param-name>
<param-value>red</param-value>
</init-param>
<!-- 圖片寬度 -->
<init-param>
<param-name>kaptcha.image.width</param-name>
<param-value>80</param-value>
</init-param>
<!-- 使用哪些字符生成驗證碼 -->
<init-param>
<param-name>kaptcha.textproducer.char.string</param-name>
<param-value>12456890</param-value>
</init-param>
<!-- 圖片高度 -->
<init-param>
<param-name>kaptcha.image.height</param-name>
<param-value>40</param-value>
</init-param>
<!-- 字體大小 -->
<init-param>
<param-name>kaptcha.textproducer.font.size</param-name>
<param-value>35</param-value>
</init-param>
<init-param>
<description>文字間隔</description>
<param-name>kaptcha.textproducer.char.space</param-name>
<param-value>3</param-value>
</init-param>
<!--去掉干擾線com.google.code.kaptcha.impl.NoNoise-->
<!--加干擾線com.google.code.kaptcha.impl.DefaultNoise-->
<init-param>
<description>干擾實現類</description>
<param-name>kaptcha.noise.impl</param-name>
<param-value>
com.google.code.kaptcha.impl.NoNoise
</param-value>
</init-param>
<!-- 幹擾線的顏色 -->
<init-param>
<param-name>kaptcha.noise.color</param-name>
<param-value>green</param-value>
</init-param>
<init-param>
<description>
圖片樣式:
水紋com.google.code.kaptcha.impl.WaterRipple
魚眼com.google.code.kaptcha.impl.FishEyeGimpy
陰影com.google.code.kaptcha.impl.ShadowGimpy
</description>
<param-name>kaptcha.obscurificator.impl</param-name>
<param-value>
com.google.code.kaptcha.impl.ShadowGimpy
</param-value>
</init-param>
<!-- 驗證碼字符個數 -->
<init-param>
<param-name>kaptcha.textproducer.char.length</param-name>
<param-value>4</param-value>
</init-param>
<!-- 字體 -->
<init-param>
<param-name>kaptcha.textproducer.font.names</param-name>
<param-value>Aria</param-value>
</init-param>
<init-param>
<description>
session中存放驗證碼的key鍵
</description>
<param-name>kaptcha.session.key</param-name>
<param-value>KAPTCHA_SESSION_KEY</param-value>
</init-param>
<init-param>
<description>
The date the kaptcha is generated is put into the
HttpSession. This is the key value for that item in the
session.
</description>
<param-name>kaptcha.session.date</param-name>
<param-value>KAPTCHA_SESSION_DATE</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Kaptcha</servlet-name>
<url-pattern>/captcha.jpg</url-pattern>
</servlet-mapping>
前端
圖片網址綁定上面的<url-pattern>/captcha.jpg</url-pattern>
<img id="code_img" src="${pageScope.basePath}captcha.jpg">
...
<script type="text/javascript">
// 頁面加載完成之後
$(function () {
// 綁定刷新驗證碼
$("#code_img").click(function () {
this.src = "${pageScope.basePath}captcha.jpg?d=" + new Date();
});
- 點擊刷新功能,用時間當URL的變數,讓每次圖片都重新請求
- 防止URL相同,瀏覽器把圖片快取起來不顯示新的
後端
- 前面發的請求會同時把KEY存在Session中,取出值:
request.getSession().getAttribute(KAPTCHA_SESSION_KEY);
- 在servlet中驗證表單內容的方法中,當成功取出值就把此Session刪掉,這樣一來如果用戶多次提交表單,又來到這個servlet的這個方法,第二次去取值就取不到,就會因為驗證失敗而走不到資料庫那步
- 如此就實現了以驗證碼阻止重複提交表單的問題
訂單模組
個人的踩坑紀錄
關於轉址
之前一直沒搞清楚url前面要不要寫/,這次好好釐清它
前端部分
jsp或html
ContextPath是會根據部屬改變的,例如tomcat預設部屬的首頁為
http://localhost:8080/book_war_exploded/
這個url對應的實體資源就是webapp/資料夾下
如果沒有指定,會自動拿根路徑的index作為首頁顯示
對於前端來說,如果沒有指定過<base href="">
<body>
<a href="TestServlet">GOTEST</a>
相當於當前網址+/TestServlet
可能包含工程路徑與網頁資源所在資料夾
這個頁面位於webapp/pages/
http://localhost:8080/book_war_exploded/pages/TestServlet
<a href="/TestServlet">GOTEST2</a>
相當於http://localhost:8080/TestServlet
即追本溯源到最頂,用這個的話切記注意部屬時設定的工程路徑
<a href="../TestServlet">GOTEST3</a>
IDE會認為你是想訪問一個檔案
但瀏覽器卻可以正確的拼接
http://localhost:8080/book_war_exploded/TestServlet
</body>
不管前端的js或後端,使用request.getContextPath()得到的就是工程路徑
/book_war_exploded
在前端用可以用js拚接出完整的路徑
<%
String basePath = request.getScheme()
+ "://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/";
pageContext.setAttribute("basePath", basePath);
%>
<base href="<%=basePath%>">
使用<%=basePath%>就等同於預設部屬的首頁,注意已經包含了最尾的/
<a href="<%=basePath%>TestServlet">測試跳轉</a>
- 使用base統一管理,最好是不要+ “/“結尾,讓用法跟後端統一
- 單獨用
/
要注意部屬時的設定
web.xml
<servlet-mapping>
<servlet-name>Kaptcha</servlet-name>
<url-pattern>/captcha.jpg</url-pattern>
= http://localhost:8080/book_war_exploded/captcha.jpg
</servlet-mapping>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/TestServlet</url-pattern>
= http://localhost:8080/book_war_exploded/TestServlet
</servlet-mapping>
- 綁定
<url-pattern>/</url-pattern>
強制要以/
開頭,否則直接報錯
後端部分
在後端使用轉發
response.sendRedirect("test.jsp");
request.getRequestDispatcher("test.jsp").forward(request, response);
不以`/`開頭就會被認為是相對路徑,即當下的url/
而通常webapp第一層只會放index,其他要訪問的頁面會分類在pages資料夾下,例如
response.sendRedirect("pages/abc.jsp");
request.getRequestDispatcher("/pages/abc.jsp").forward(request, response);
兩者區別在於
response.sendRedirect是兩個請求,已經轉發了,網址會變化
request.getRequestDispatcher網址不會變
如果如果在網址前加了/,寫成"/url",
對於response.sendRedirect 因為它是可以轉到外面去的,不寫齊全會出事,要手動加上request.getContextPath()
http://localhost:8080/
而request.getRequestDispatcher加了/比如("/pages/abc.jsp")
則表示Web資源的根路徑
小結
-
前端不以
/
開頭就會被認為是相對路徑,即當下的url -
後端都加上
/
- 並且
response.sendRedirect
先以request.getContextPath()
開頭
- 並且
-
為了方便統一,前端用base先拚出工程路徑(但不要包含最尾的/),這樣整個專案就都是用/打頭
- web.xml不加/根本無法啟動,現在都用註解或框架也不太用管
SQL問題
SQL語句
- 寫daoImpl的時候,忘記是從資料庫中取東西的時候才要加別名使其與pojo符合
- 我竟然在
insert into
的地方寫別名還除錯了半天,有夠蠢
Date類問題
cannot convert java.time.LocalDateTime to java.util.Date
把mysql-connector-java8.0.26換成8.0.21版,問題消失
因為26版會將datatime轉換成java.time.LocalDateTime
而21版轉換成java.util.Date
The server time zone value '����зǮɶ�' is unrecognized
21版連mysql 8不指定時間又會出錯
jdbc:mysql://localhost:3306/bookstore?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf-8&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
另外mysql預設時區要注意可能是UTC可以去資料庫看
兩邊都設定+8,就是GMT%2B8就不會出現時差問題
點擊事件
- 用id綁定,結果只有第一個生效,測試時靈時不靈還找半天原因
- 教訓:記住可能有多個物件時用
class
與.
綁定
Http503
- 具體報錯是
Servlet is currently unavailable
- 原因在我複製一個jsp頁面,IDEA它JSTL導入怪怪的,寫了一個
<c:if
它提示我沒導入,於是alt+enter修復,誰知道一修就壞了 - 來回折騰很久,Maven引了又引搞了半天,確定有這兩個
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.5</version>
</dependency>
- 最後發現index.jsp明明沒導入卻不報錯,很詭異
- 在最頂加上
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
就解決了
- 在最頂加上
- 教訓:不要輕易使用IDEA的複製跟移動文件功能,寧願造一個全新頁面然後全選複製貼上
上次修改於 2022-01-09