书城项目第一阶段

第一节 事件驱动补充

1、取消控件的默认行为

①控件默认行为

  • 点超链接会跳转页面
  • 点表单提交按钮会提交表单

本来控件的默认行为是天经地义就该如此的,但是如果我们希望点击之后根据我们判断的结果再看是否要跳转,此时默认行为无脑跳转的做法就不符合我们的预期了。

②取消方式

调用事件对象preventDefault()方法。

[1]超链接举例

HTML代码:

  1. <a id="anchor" href="http://www.baidu.com">超链接</a>

JavaScript代码:

  1. document.getElementById("anchor").onclick = function() {
  2. console.log("我点击了一个超链接");
  3. event.preventDefault();
  4. }

[2]表单提交按钮举例

HTML代码:

  1. <form action="http://www.baidu.com" method="post">
  2. <button id="submitBtn" type="submit">提交表单</button>
  3. </form>

JavaScript代码:

  1. document.getElementById("submitBtn").onclick = function() {
  2. console.log("我点击了一个表单提交按钮");
  3. event.preventDefault();
  4. }

2、阻止事件冒泡

image.png
图中的两个div,他们的HTML标签是:

  1. <div id="outterDiv">
  2. <div id="innerDiv"></div>
  3. </div>

点击里面的div同时也等于点击了外层的div,此时如果两个div上都绑定了单击响应函数那么就都会被触发:

  1. document.getElementById("outterDiv").onclick = function() {
  2. console.log("外层div的事件触发了");
  3. }
  4. document.getElementById("innerDiv").onclick = function() {
  5. console.log("内层div的事件触发了");
  6. }

所以事件冒泡就是一个事件会不断向父元素传递,直到window对象。
如果这不是我们想要的效果那么可以使用事件对象stopPropagation()函数阻止。

  1. document.getElementById("innerDiv").onclick = function() {
  2. console.log("内层div的事件触发了");
  3. event.stopPropagation();
  4. }

3、Vue事件修饰符

对于事件修饰符,Vue官网的描述是:

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节

①取消控件的默认行为

控件的默认行为指的是:

  • 点击超链接跳转页面
  • 点击表单提交按钮提交表单

实现这个需求使用的Vue事件修饰符是:.prevent

  1. <a href="http://www.baidu.com" @click.prevent="clickAnchor">超链接</a>
  2. <form action="http://www.baidu.com" method="post">
  3. <button type="submit" @click.prevent="clickSubmitBtn">提交表单</button>
  4. </form>

②取消事件冒泡

实现这个需求使用的Vue事件修饰符是:.stop

  1. <div id="outterDiv" @click="clickOutterDiv">
  2. <div id="innerDiv" @click.stop="clickInnerDiv"></div>
  3. </div>

第二节 正则表达式

1、从凤姐的择偶标准说起

image.png
本人对伴侣要求如下:

  • 第一、必须为北京大学或清华大学硕士毕业生。必须本科硕士连读,中途无跳级,不留级,不转校。在外参加工作后再回校读书者免。
  • 第二、必须为经济学专业毕业。非经济学专业毕业则必须精通经济学。或对经济学有浓厚的兴趣。
  • 第三、必须具备国际视野,但是无长期定居国外甚至移民的打算。
  • 第四、身高176—183左右。长得越帅越好。
  • 第五、无生育史。过往所有女友均无因自身而致的堕胎史。
  • 第六、东部户籍,即江、浙、沪三地户籍或黑龙江、广东、天津、山东、北京、吉林、辽宁等。
  • 东北三省和内蒙古等地户籍,西南地区即重庆、贵州、云南、西藏和湖南、湖北等地籍贯者不予考虑。
  • 第七、年龄25—28岁左右。即06届,07届,08届,09届毕业生。有一至两年的工作经验,06级毕业生需年龄在28岁左右,09级毕业生则需聪明过人。且具备丰富的社会实践经验。就职于国家机关,国有企事业单位者不愿考虑。但就职于中石油,中石化等世界顶尖型企业或银行者又比较喜欢。现自主创业者要商榷一番了。

    2、标准在手,世界我有

    ①模式验证

    使用标准衡量一位具体的男士,返回一个布尔值,从而知道这位男士是否满足自己的标准——相当于我们使用正则表达式验证一个字符串是否满足规则。比如验证一个字符串是否是一个身份证号。

    ②匹配读取

    对全中国的男士应用这个标准,返回一个数组,遍历这个数组,可以得到所有符合标准的男士——相当于我们使用正则表达式获取一段文本中匹配的子字符串。比如将一篇文章中的电子邮件地址读取出来。

    ③匹配替换

    对全中国的男士应用这个标准,把其中已婚的变成未婚,这样凤姐就有机会了——相当于我们使用正则表达式替换所有匹配的部分。比如将一段文字中的”HelloWorld”替换为”HelloJava”。

    花絮: 记者:封老师您好!由于您的名字『封捷』和『凤姐』谐音,同学们总是以此来调侃您,说您是尚硅谷『凤姐』,对此您有什么想说的吗? 封老师:太过分了!我咋能和人家比! 记者:呃……太意外了,您的意思是? 封老师:虽然过气了,但人家好歹也是网红呀!

3、正则表达式的概念

使用一段字符串定义的一个规则,用以检测某个字符串是否满足这个规则,或将目标字符串中满足规则的部分读取出来,又或者将目标字符串中满足标准的部分替换为其他字符串。所以正则表达式有三个主要用途:

  • 模式验证
  • 匹配读取
  • 匹配替换

image.png

4、正则表达式零起步

①创建正则表达式对象

[1]使用两个斜杠
  1. // 类似创建数组时可以使用[]、创建对象可以使用{}
  2. var reg = /a/;

[2]使用new关键字创建RegExp类型的对象
  1. // 类似创建数组可以new Array()、创建对象可以使用new Object()
  2. var reg = new RegExp("a");

②正则表达式的组成

正则表达式本身也是一个字符串,它由两种字符组成:

  • 普通字符,例如大、小写英文字母;数字等。
  • 元字符:被系统赋予特殊含义的字符。例如:^表示以某个字符串开始,$表示以某个字符串结束。

    ③正则表达式初体验

    [1]模式验证
    注意:这里是使用正则表达式对象调用方法。 ```javascript // 创建一个最简单的正则表达式对象 var reg = /o/;

// 创建一个字符串对象作为目标字符串 var str = ‘Hello World!’;

// 调用正则表达式对象的test()方法验证目标字符串是否满足我们指定的这个模式,返回结果true console.log(“/o/.test(‘Hello World!’)=”+reg.test(str));

  1. <a name="bOwl9"></a>
  2. ##### [2]匹配读取
  3. **注意**:这里是使用**字符串对象**来**调用**方法。
  4. ```javascript
  5. // 在目标字符串中查找匹配的字符,返回匹配结果组成的数组
  6. var resultArr = str.match(reg);
  7. // 数组长度为1
  8. console.log("resultArr.length="+resultArr.length);
  9. // 数组内容是o
  10. console.log("resultArr[0]="+resultArr[0]);

[3]替换

注意:这里是使用字符串对象调用方法。

  1. var newStr = str.replace(reg,'@');
  2. // 只有第一个o被替换了,说明我们这个正则表达式只能匹配第一个满足的字符串
  3. console.log("str.replace(reg)="+newStr);//Hell@ World!
  4. // 原字符串并没有变化,只是返回了一个新字符串
  5. console.log("str="+str);//str=Hello World!

④匹配方式

[1]全文查找

如果不使用g对正则表达式对象进行修饰,则使用正则表达式进行查找时,仅返回第一个匹配;使用g后,返回所有匹配。

  1. // 目标字符串
  2. var targetStr = 'Hello World!';
  3. // 没有使用全局匹配的正则表达式
  4. var reg = /[A-Z]/;
  5. // 获取全部匹配
  6. var resultArr = targetStr.match(reg);
  7. // 数组长度为1
  8. console.log("resultArr.length="+resultArr.length);
  9. // 遍历数组,发现只能得到'H'
  10. for(var i = 0; i < resultArr.length; i++){
  11. console.log("resultArr["+i+"]="+resultArr[i]);
  12. }

对比代码:

  1. // 目标字符串
  2. var targetStr = 'Hello World!';
  3. // 使用了全局匹配的正则表达式
  4. var reg = /[A-Z]/g;
  5. // 获取全部匹配
  6. var resultArr = targetStr.match(reg);
  7. // 数组长度为2
  8. console.log("resultArr.length="+resultArr.length);
  9. // 遍历数组,发现可以获取到“H”和“W”
  10. for(var i = 0; i < resultArr.length; i++){
  11. console.log("resultArr["+i+"]="+resultArr[i]);
  12. }

[2]忽略大小写
  1. //目标字符串
  2. var targetStr = 'Hello WORLD!';
  3. //没有使用忽略大小写的正则表达式
  4. var reg = /o/g;
  5. //获取全部匹配
  6. var resultArr = targetStr.match(reg);
  7. //数组长度为1
  8. console.log("resultArr.length="+resultArr.length);
  9. //遍历数组,仅得到'o'
  10. for(var i = 0; i < resultArr.length; i++){
  11. console.log("resultArr["+i+"]="+resultArr[i]);
  12. }

对比代码:

  1. //目标字符串
  2. var targetStr = 'Hello WORLD!';
  3. //使用了忽略大小写的正则表达式
  4. var reg = /o/gi;
  5. //获取全部匹配
  6. var resultArr = targetStr.match(reg);
  7. //数组长度为2
  8. console.log("resultArr.length="+resultArr.length);
  9. //遍历数组,得到'o'和'O'
  10. for(var i = 0; i < resultArr.length; i++){
  11. console.log("resultArr["+i+"]="+resultArr[i]);
  12. }

[3]多行查找

不使用多行查找模式,目标字符串中不管有没有换行符都会被当作一行。

  1. //目标字符串1
  2. var targetStr01 = 'Hello\nWorld!';
  3. //目标字符串2
  4. var targetStr02 = 'Hello';
  5. //匹配以'Hello'结尾的正则表达式,没有使用多行匹配
  6. var reg = /Hello$/;
  7. console.log(reg.test(targetStr01));//false
  8. console.log(reg.test(targetStr02));//true

对比代码:

  1. //目标字符串1
  2. var targetStr01 = 'Hello\nWorld!';
  3. //目标字符串2
  4. var targetStr02 = 'Hello';
  5. //匹配以'Hello'结尾的正则表达式,使用了多行匹配
  6. var reg = /Hello$/m;
  7. console.log(reg.test(targetStr01));//true
  8. console.log(reg.test(targetStr02));//true

5、元字符

①概念

在正则表达式中被赋予特殊含义的字符,不能被直接当做普通字符使用。如果要匹配元字符本身,需要对元字符进行转义,转义的方式是在元字符前面加上“\”,例如:^

②常用元字符

代码 说明
. 匹配除换行字符以外的任意字符。
\w 匹配字母或数字或下划线等价于[a-zA-Z0-9_]
\W 匹配任何非单词字符。等价于[^A-Za-z0-9_]
\s 匹配任意的空白符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v]。
\d 匹配数字。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]
\b 匹配单词的开始或结束
^ 匹配字符串的开始,但在[]中使用表示取反
$ 匹配字符串的结束

[1]例1
  1. var str = 'one two three four';
  2. // 匹配全部空格
  3. var reg = /\s/g;
  4. // 将空格替换为@
  5. var newStr = str.replace(reg,'@'); // one@two@three@four
  6. console.log("newStr="+newStr);

[2]例2
  1. var str = '今年是2014年';
  2. // 匹配至少一个数字
  3. var reg = /\d+/g;
  4. str = str.replace(reg,'abcd');
  5. console.log('str='+str); // 今年是abcd年

[3]例3
  1. var str01 = 'I love Java';
  2. var str02 = 'Java love me';
  3. // 匹配以Java开头
  4. var reg = /^Java/g;
  5. console.log('reg.test(str01)='+reg.test(str01)); // flase
  6. console.log("<br />");
  7. console.log('reg.test(str02)='+reg.test(str02)); // true

[4]例4
  1. var str01 = 'I love Java';
  2. var str02 = 'Java love me';
  3. // 匹配以Java结尾
  4. var reg = /Java$/g;
  5. console.log('reg.test(str01)='+reg.test(str01)); // true
  6. console.log("<br />");
  7. console.log('reg.test(str02)='+reg.test(str02)); // flase

6、字符集合

语法格式 示例 说明
[字符列表] 正则表达式:[abc]
含义:目标字符串包含abc中的任何一个字符
目标字符串:plain
是否匹配:是
原因:plain中的“a”在列表“abc”中
目标字符串中任何一个字符出现在字符列表中就算匹配。
[^字符列表] [^abc]
含义:目标字符串包含abc以外的任何一个字符
目标字符串:plain
是否匹配:是
原因:plain中包含“p”、“l”、“i”、“n”
匹配字符列表中未包含的任意字符。
[字符范围] 正则表达式:[a-z]
含义:所有小写英文字符组成的字符列表
正则表达式:[A-Z]
含义:所有大写英文字符组成的字符列表
匹配指定范围内的任意字符。
  1. var str01 = 'Hello World';
  2. var str02 = 'I am Tom';
  3. //匹配abc中的任何一个
  4. var reg = /[abc]/g;
  5. console.log('reg.test(str01)='+reg.test(str01));//flase
  6. console.log('reg.test(str02)='+reg.test(str02));//true

7、重复

代码 说明
* 重复零次或多次
+ 重复一次或多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或多次
{n,m} 重复n到m次
  1. console.log("/[a]{3}/.test('aa')="+/[a]{3}/g.test('aa')); // flase
  2. console.log("/[a]{3}/.test('aaa')="+/[a]{3}/g.test('aaa')); // true
  3. console.log("/[a]{3}/.test('aaaa')="+/[a]{3}/g.test('aaaa')); // true

8、在正则表达式中表达『或者』

使用符号:|

  1. // 目标字符串
  2. var str01 = 'Hello World!';
  3. var str02 = 'I love Java';
  4. // 匹配'World'或'Java'
  5. var reg = /World|Java/g;
  6. console.log("str01.match(reg)[0]="+str01.match(reg)[0]);//World
  7. console.log("str02.match(reg)[0]="+str02.match(reg)[0]);//Java

9、常用正则表达式

需求 正则表达式
用户名 /^[a-zA-Z][a-zA-Z\-0-9]{5,9}$/
密码 /^[a-zA-Z0-9_\-@\#\&\*]{6,12}$/
前后空格 /^\s+|\s+$/g
电子邮箱 /^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+[\.]{1})+[a-zA-Z]+$/

第三节 第一阶段要实现的功能

0、准备工作

创建目录后,把一整套现成的前端页面复制到新建的目录下,然后用HBuilderX打开这个目录。然后把vue.js文件复制到script目录下。
image.png

1、登录页面的表单验证

①规则设定

  • 用户名非空
  • 密码非空

    ②在login.html页面中加入Vue的环境

    1. </body>
    2. <script src="/bookstoreV01/static/script/vue.js" type="text/javascript" charset="utf-8"></script>
    3. </html>

    image.png

    ③思路

    image.png

    ④代码实现

    [1]HTML代码
    1. <form id="loginForm" action="login_success.html">
    2. <label>用户名称:</label>
    3. <input class="itxt" type="text" v-model:value="username" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username" />
    4. <br />
    5. <br />
    6. <label>用户密码:</label>
    7. <input class="itxt" type="password" v-model:value="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" id="password" />
    8. <br />
    9. <br />
    10. <button type="submit" id="sub_btn" @click="loginCheck">登录</button>
    11. </form>

    [2]Vue代码
    1. new Vue({
    2. "el":"#loginForm",
    3. "data":{
    4. "username":"",
    5. "password":""
    6. },
    7. "methods":{
    8. "loginCheck":function(){
    9. // 判断用户名或密码是否为空
    10. if(this.username == "" || this.password == "") {
    11. // 如果不满足验证条件,那么阻止表单提交
    12. event.preventDefault();
    13. }
    14. }
    15. }
    16. });

    2、注册页面的表单验证

    ①HTML代码

    1. <form id="registerForm" action="regist_success.html">
    2. <div class="form-item">
    3. <div>
    4. <label>用户名称:</label>
    5. <input v-model:value="username" type="text" placeholder="请输入用户名" />
    6. <span></span>
    7. </div>
    8. <span>{{usernameCheckMessage}}</span>
    9. </div>
    10. <div class="form-item">
    11. <div>
    12. <label>用户密码:</label>
    13. <input v-model:value="password" type="password" placeholder="请输入密码" />
    14. </div>
    15. <span class="errMess">密码的长度至少为8位</span>
    16. </div>
    17. <div class="form-item">
    18. <div>
    19. <label>确认密码:</label>
    20. <input v-model:value="passwordConfirm" type="password" placeholder="请输入确认密码" />
    21. </div>
    22. <span class="errMess">密码两次输入不一致</span>
    23. </div>
    24. <div class="form-item">
    25. <div>
    26. <label>用户邮箱:</label>
    27. <input v-model:value="email" type="text" placeholder="请输入邮箱" />
    28. </div>
    29. <span class="errMess">请输入正确的邮箱格式</span>
    30. </div>
    31. <div class="form-item">
    32. <div>
    33. <label>验证码:</label>
    34. <div class="verify">
    35. <input v-model:value="code" type="text" placeholder="" />
    36. <img src="../../static/img/code.bmp" alt="" />
    37. </div>
    38. </div>
    39. <span class="errMess">请输入正确的验证码</span>
    40. </div>
    41. <button type="submit" @click="registerCheck" class="btn">注册</button>
    42. </form>

    ②Vue代码

    1. new Vue({
    2. "el":"#registerForm",
    3. "data":{
    4. "username":"",
    5. "password":"",
    6. "passwordConfirm":"",
    7. "email":"",
    8. "code":"",
    9. "usernameCheckMessage":""
    10. },
    11. "watch":{
    12. "username":function(inputValue){
    13. var usernameRegExp = /^[A-Z,a-z,0-9,_]{5,8}$/;
    14. if (usernameRegExp.test(this.username)) {
    15. this.usernameCheckMessage = "";
    16. }else{
    17. this.usernameCheckMessage = "用户名不符合规则";
    18. }
    19. }
    20. },
    21. "methods":{
    22. "registerCheck":function(){
    23. // 1.检查用户名
    24. var usernameRegExp = /^[A-Z,a-z,0-9,_]{5,8}$/;
    25. if (!usernameRegExp.test(this.username)) {
    26. // 如果不满足条件,则阻止表单提交
    27. event.preventDefault();
    28. // 有任何一个条件不满足,后面就没必要检查了,所以函数可以停止执行
    29. return ;
    30. }
    31. // 2.检查密码
    32. var passwordRegExp = /^[A-Z,a-z,0-9,_]{5,8}$/;
    33. if (!passwordRegExp.test(this.password)) {
    34. // 如果不满足条件,则阻止表单提交
    35. event.preventDefault();
    36. // 有任何一个条件不满足,后面就没必要检查了,所以函数可以停止执行
    37. return ;
    38. }
    39. // 3.检查确认密码是否和密码一致
    40. if (this.password != this.passwordConfirm) {
    41. // 如果不满足条件,则阻止表单提交
    42. event.preventDefault();
    43. // 有任何一个条件不满足,后面就没必要检查了,所以函数可以停止执行
    44. return ;
    45. }
    46. // 4.检查电子邮件
    47. var emailRegExp = /^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+[\.]{1})+[a-zA-Z]+$/;
    48. if (!emailRegExp.test(this.email)) {
    49. // 如果不满足条件,则阻止表单提交
    50. event.preventDefault();
    51. // 有任何一个条件不满足,后面就没必要检查了,所以函数可以停止执行
    52. return ;
    53. }
    54. // 5.检查验证码
    55. var codeRegExp = /[A-Z,a-z,0-9]{5}/;
    56. if(!codeRegExp.test(this.code)) {
    57. // 如果不满足条件,则阻止表单提交
    58. event.preventDefault();
    59. // 有任何一个条件不满足,后面就没必要检查了,所以函数可以停止执行
    60. return ;
    61. }
    62. }
    63. }
    64. });

    书城项目第二阶段

    第一节 不带数据库的登录注册

    1、创建动态Web工程

    image.png

    2、把V1中的页面粘贴过来

    image.png

    3、使用base标签统一设置路径基准

    ①base标签的语法规则

  • base标签要写在head标签内

  • base标签必须写在所有其他有路径的标签的前面
  • base标签使用href属性设置路径的基准
  • base标签生效的机制是:最终的访问地址=base标签href属性设置的基准+具体标签内的路径
  • 对于想要参考base标签的具体标签,如果路径是以斜杠开头,那么它将不参考base标签

    ②base标签使用举例

    1. <head>
    2. <meta charset="UTF-8"/>
    3. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    4. <title>书城首页</title>
    5. <base href="/bookstore/"/>
    6. <link rel="stylesheet" href="static/css/minireset.css"/>
    7. <link rel="stylesheet" href="static/css/common.css"/>
    8. <link rel="stylesheet" href="static/css/iconfont.css"/>
    9. <link rel="stylesheet" href="static/css/index.css"/>
    10. <link rel="stylesheet" href="static/css/swiper.min.css"/>
    11. </head>

    4、基于base标签调整整个页面的路径

    ①base标签的代码

    在需要的页面把下面的base标签代码粘贴到head标签内、需要路径的标签前即可
    1. <base href="/bookstore/"/>

    ②对需要统一调整的路径执行替换

    Ctrl+r调出替换操作窗口
    image.png
    具体操作时请参考页面的实际情况进行替换。

    5、基本假设

    为了实现『不带数据库』的登录注册,我们需要假设:系统中目前已有用户:
用户名 密码
tom 123456

6、登录功能

①明确目标

在服务器端检查用户通过表单提交的用户名、密码是否正确。

  • 成功:跳转到login_success.html页面
  • 失败:返回错误消息

    ②思路

    友情提示:分析业务功能,捋清思路最好的办法就是画流程图
    image.png

    ③代码

    [1]创建LoginServlet
    image.png

    创建Packages时的规范: 公司或组织域名倒序.项目名.模块名.具体包名 或 公司或组织域名倒序.项目名.具体包名

下面是完整的Servlet配置信息:

  1. <servlet>
  2. <servlet-name>LoginServlet</servlet-name>
  3. <servlet-class>com.atguigu.bookstore.servlet.LoginServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>LoginServlet</servlet-name>
  7. <url-pattern>/LoginServlet</url-pattern>
  8. </servlet-mapping>

[2]完成doPost()方法中的代码
  1. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.声明两个变量,用于存储假设的用户名、密码
  3. String usernameSystem = "tom";
  4. String passwordSystem = "123456";
  5. // 2.获取请求参数中的用户名、密码
  6. String usernameForm = request.getParameter("username");
  7. String passwordForm = request.getParameter("password");
  8. // 3.执行判断
  9. if(usernameSystem.equals(usernameForm) && passwordSystem.equals(passwordForm)) {
  10. // 4.登录成功:重定向到登录成功页面
  11. response.sendRedirect(request.getContextPath() + "/pages/user/login_success.html");
  12. }else{
  13. // 5.登录失败
  14. response.setContentType("text/html;charset=UTF-8");
  15. response.getWriter().write("抱歉!用户名或密码不正确,请重新输入!");
  16. }
  17. }

[3]HTML页面设置表单提交地址
  1. <form id="loginForm" action="LoginServlet" method="post">
  2. <label>用户名称:</label>
  3. <input class="itxt" type="text" v-model:value="username" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username" />
  4. <br />
  5. <br />
  6. <label>用户密码:</label>
  7. <input class="itxt" type="password" v-model:value="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" id="password" />
  8. <br />
  9. <br />
  10. <button type="submit" id="sub_btn" @click="loginCheck">登录</button>
  11. </form>

④提示消息改进探索

以下代码仅供参考:

  1. // 5.登录失败
  2. // 返回提示消息方案一:过于简陋
  3. // response.setContentType("text/html;charset=UTF-8");
  4. // response.getWriter().write("抱歉!用户名或密码不正确,请重新输入!");
  5. // 返回提示消息方案二:没有提示消息,让用户非常困惑
  6. // request.getRequestDispatcher("/pages/user/login.html").forward(request, response);
  7. // 返回提示消息方案三:确实能在登录页面显示提示消息,但是实现的方式让我想骂人
  8. response.setContentType("text/html;charset=UTF-8");
  9. PrintWriter writer = response.getWriter();
  10. writer.write("<!DOCTYPE html>");
  11. writer.write("<html>");
  12. writer.write(" <head>");
  13. writer.write(" <base href='/bookstore/' />");

7、注册功能

①明确目标

用户提交注册表单后,检查用户名是否被占用

  • 没有被占用:注册成功
  • 已经被占用:注册失败

    ②思路

    image.png

    ③代码

    [1]创建RegisterServlet

    image.png
    完整配置信息:

    1. <servlet>
    2. <servlet-name>RegisterServlet</servlet-name>
    3. <servlet-class>com.atguigu.bookstore.servlet.RegisterServlet</servlet-class>
    4. </servlet>
    5. <servlet-mapping>
    6. <servlet-name>RegisterServlet</servlet-name>
    7. <url-pattern>/RegisterServlet</url-pattern>
    8. </servlet-mapping>

    [2]完成doPost()方法中的代码

    ```java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.声明变量保存系统内置的用户名 String usernameSystem = “jerry”;

    // 2.获取请求参数 String usernameForm = request.getParameter(“username”);

    // 3.比较 if (usernameSystem.equals(usernameForm)) {

    1. // 4.说明用户名已经被占用,需要提示错误消息
    2. response.setContentType("text/html;charset=UTF-8");
    3. PrintWriter writer = response.getWriter();
    4. writer.write("抱歉!用户名已经被占用,请重新输入!");

    }else{

    1. // 5.说明用户名可用,跳转到注册成功页面
    2. response.sendRedirect(request.getContextPath() + "/pages/user/regist_success.html");

    }

}

  1. <a name="Uqlc4"></a>
  2. ##### [3]HTML页面调整表单中的设置
  3. ```html
  4. <form id="registerForm" action="RegisterServlet" method="post">
  5. <div class="form-item">
  6. <div>
  7. <label>用户名称:</label>
  8. <input v-model:value="username" type="text" name="username" placeholder="请输入用户名" />
  9. <span></span>
  10. </div>
  11. <span>{{usernameCheckMessage}}</span>
  12. </div>
  13. <div class="form-item">
  14. <div>
  15. <label>用户密码:</label>
  16. <input v-model:value="password" type="password" name="password" placeholder="请输入密码" />
  17. </div>
  18. <span class="errMess">密码的长度至少为8位</span>
  19. </div>
  20. <div class="form-item">
  21. <div>
  22. <label>确认密码:</label>
  23. <input v-model:value="passwordConfirm" type="password" placeholder="请输入确认密码" />
  24. </div>
  25. <span class="errMess">密码两次输入不一致</span>
  26. </div>
  27. <div class="form-item">
  28. <div>
  29. <label>用户邮箱:</label>
  30. <input v-model:value="email" type="text" name="email" placeholder="请输入邮箱" />
  31. </div>
  32. <span class="errMess">请输入正确的邮箱格式</span>
  33. </div>
  34. <div class="form-item">
  35. <div>
  36. <label>验证码:</label>
  37. <div class="verify">
  38. <input v-model:value="code" type="text" name="code" placeholder="" />
  39. <img src="static/img/code.bmp" alt="" />
  40. </div>
  41. </div>
  42. <span class="errMess">请输入正确的验证码</span>
  43. </div>
  44. <button type="submit" @click="registerCheck" class="btn">注册</button>
  45. </form>

第二节 三层架构

1、三层架构划分

image.png

  • 表述层:负责处理浏览器请求、返回响应、页面调度
  • 业务逻辑层:负责处理业务逻辑,根据业务逻辑把持久化层从数据库查询出来的数据进行运算、组装,封装好后返回给表述层,也可以根据业务功能的需要调用持久化层把数据保存到数据库、修改数据库中的数据、删除数据库中的数据
  • 持久化层:根据上一层的调用对数据库中的数据执行增删改查的操作

    2、三层架构好处

    如果不做三层架构形式的拆分:
    image.png
    所有和当前业务功能需求相关的代码全部耦合在一起,如果其中有任何一个部分出现了问题,牵一发而动全身,导致其他无关代码也要进行相应的修改。这样的话代码会非常难以维护。
    所以为了提高开发效率,需要对代码进行模块化的拆分。整个项目模块化、组件化程度越高,越容易管理和维护,出现问题更容易排查。

    3、三层架构和模型的关系

    image.png
    模型对整个项目中三层架构的每一层都提供支持,具体体现是使用模型对象封装业务功能数据

    Java实体类有很多不同名称:

    • POJO:Plain old Java Object,传统的普通的Java对象
    • entity:实体类
    • bean或Java bean
    • domain:领域模型

4、模型开发的要求

①ORM

ORM:Object Relative Mapping对象关系映射
对象:Java对象
关系:关系型数据库
映射:Java对象和数据库表之间的对应关系

Java类 数据库表
属性 字段/列
对象 记录/行
属性按照驼峰式命名 字段名各个单词之间用下划线分开

②Java实体类的要求

  • 必须有一个无参构造器将来使用框架后,大量的对象都是框架通过反射来创建的。Class clazz = Class.forName(“全类名”);clazz.newInstance();
  • 通过getXxx()、setXxx()方法定义属性:getXxx()或setXxx()方法去掉get或set后,Xxx把首字母小写,得到的xxx就是属性名。 ```java public class User {

    private String safeUserName;

    public String getUserName(){

    1. return this.safeUserName;

    }

    public void setUserName(String userName){

    1. this.safeUserName = userName;

    }

}

  1. 在上面例子中,getXxx()、setXxx()方法定义的属性是userName,不是safeUserName
  2. <a name="o7ovt"></a>
  3. ## 第三节 建模
  4. <a name="NR3kL"></a>
  5. ### 1、创建数据库
  6. ```sql
  7. CREATE DATABASE bookstore210107 CHARACTER SET utf8;
  8. USE `bookstore210107`;

2、创建数据库表

物理建模的思路参考这里

  1. CREATE TABLE t_user(
  2. user_id INT PRIMARY KEY AUTO_INCREMENT,
  3. user_name CHAR(100),
  4. user_pwd CHAR(100),
  5. email CHAR(100)
  6. );

3、创建Java实体类

image.png
image.png

  1. public class User {
  2. private Integer userId;// user_id
  3. private String userName;// user_name
  4. private String userPwd;// user_pwd
  5. private String email;// email
  6. ……

第四节 持久化层

1、加入所需jar包

image.png
image.png
image.png

2、创建连接数据库的工具类

image.png

3、创建外部属性文件

image.png

  1. driverClassName=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://192.168.198.100:3306/bookstore210107
  3. username=root
  4. password=atguigu
  5. initialSize=10
  6. maxActive=20
  7. maxWait=10000

注意:这里是上课演示时使用的参数,大家操作时要改成自己的。

4、在JDBCUtils类中创建数据源对象

  1. private static DataSource dataSource;
  2. static {
  3. // 1.创建一个用于存储外部属性文件信息的Properties对象
  4. Properties properties = new Properties();
  5. // 2.使用当前类的类加载器加载外部属性文件:jdbc.properties
  6. InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
  7. try {
  8. // 3.将外部属性文件jdbc.properties中的数据加载到properties对象中
  9. properties.load(inputStream);
  10. // 4.创建数据源对象
  11. dataSource = DruidDataSourceFactory.createDataSource(properties);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }

5、声明工具方法操作数据库连接

  1. /**
  2. * 从数据源中获取数据库连接
  3. * @return 数据库连接对象
  4. */
  5. public static Connection getConnection() {
  6. Connection connection = null;
  7. try {
  8. connection = dataSource.getConnection();
  9. } catch (SQLException e) {
  10. e.printStackTrace();
  11. throw new RuntimeException(e);
  12. }
  13. return connection;
  14. }
  15. /**
  16. * 释放数据库连接
  17. * @param connection 要执行释放操作的连接对象
  18. */
  19. public static void releaseConnection(Connection connection) {
  20. if (connection != null) {
  21. try {
  22. connection.close();
  23. } catch (SQLException e) {
  24. e.printStackTrace();
  25. throw new RuntimeException(e);
  26. }
  27. }
  28. }

测试代码如下:

  1. public class BookstoreTest {
  2. @Test
  3. public void testConnection() {
  4. Connection connection = JDBCUtils.getConnection();
  5. System.out.println("connection = " + connection);
  6. }
  7. }

6、创建BaseDao

①DAO概念

DAO:Data Access Object数据访问对象
DAL:Data Access Layer数据访问层

②创建Java类

image.png

③编写通用方法

  1. /**
  2. * 各个具体Dao类的基类,泛型T对应具体实体类类型
  3. * @param <T>
  4. */
  5. public class BaseDao<T> {
  6. private QueryRunner queryRunner = new QueryRunner();
  7. /**
  8. * 通用的增删改方法
  9. * @param sql 要执行的SQL语句
  10. * @param param 为SQL语句准备好的参数
  11. * @return 受影响的行数
  12. */
  13. public int update(String sql, Object ... param) {
  14. int updatedRowCount = 0;
  15. Connection connection = JDBCUtils.getConnection();
  16. try {
  17. updatedRowCount = queryRunner.update(connection, sql, param);
  18. }
  19. // 为了让上层方法调用方便,将编译时异常捕获
  20. catch (SQLException e) {
  21. e.printStackTrace();
  22. // 为了不掩盖问题,将编译时异常封装为运行时异常抛出
  23. throw new RuntimeException(e);
  24. } finally {
  25. // 关闭数据库连接
  26. JDBCUtils.releaseConnection(connection);
  27. }
  28. return updatedRowCount;
  29. }
  30. /**
  31. * 查询单个对象
  32. * @param clazz 单个对象所对应的实体类类型
  33. * @param sql 查询单个对象所需要的SQL语句
  34. * @param param SQL语句的参数
  35. * @return 查询到的单个对象
  36. */
  37. public T getBean(Class<T> clazz, String sql, Object ... param) {
  38. Connection connection = JDBCUtils.getConnection();
  39. T t = null;
  40. try {
  41. t = queryRunner.query(connection, sql, new BeanHandler<>(clazz), param);
  42. } catch (SQLException e) {
  43. e.printStackTrace();
  44. throw new RuntimeException(e);
  45. } finally {
  46. // 关闭数据库连接
  47. JDBCUtils.releaseConnection(connection);
  48. }
  49. return t;
  50. }
  51. /**
  52. * 查询集合对象
  53. * @param clazz 集合中单个对象所对应的实体类类型
  54. * @param sql 查询集合所需要的SQL语句
  55. * @param param SQL语句的参数
  56. * @return 查询到的集合对象
  57. */
  58. public List<T> getBeanList(Class<T> clazz, String sql, Object ... param) {
  59. Connection connection = JDBCUtils.getConnection();
  60. List<T> list = null;
  61. try {
  62. list = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), param);
  63. } catch (SQLException e) {
  64. e.printStackTrace();
  65. throw new RuntimeException(e);
  66. } finally {
  67. // 关闭数据库连接
  68. JDBCUtils.releaseConnection(connection);
  69. }
  70. return list;
  71. }
  72. }

测试方法:

  1. @Test
  2. public void testUpdate() {
  3. BaseDao<Object> baseDao = new BaseDao<>();
  4. String sql = "insert into t_user(user_name,user_pwd) values(?,?)";
  5. int count = baseDao.update(sql, "罗志祥", "789456");
  6. System.out.println("count = " + count);
  7. }
  8. @Test
  9. public void testGetBean() {
  10. BaseDao<User> baseDao = new BaseDao<>();
  11. // user_id userId
  12. // user_name userName
  13. // user_pwd userPwd
  14. String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user where user_id=?";
  15. User user = baseDao.getBean(User.class, sql, 2);
  16. System.out.println("user = " + user);
  17. }
  18. @Test
  19. public void testGetBeanList() {
  20. BaseDao<User> baseDao = new BaseDao<>();
  21. String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user";
  22. List<User> userList = baseDao.getBeanList(User.class, sql);
  23. for (User user : userList) {
  24. System.out.println("user = " + user);
  25. }
  26. }

7、创建UserDao

①用户登录注册功能中的组件关系图

image.png

②声明UserDao接口

image.png

  1. public interface UserDao {
  2. /**
  3. * 根据用户名查询User对象
  4. * @param username 用户名
  5. * @return 查询到的User对象
  6. */
  7. User selectUserByName(String username);
  8. /**
  9. * 将User对象保存到数据库
  10. * @param user 要保存的User对象
  11. * @return 受影响的行数
  12. */
  13. int insertUser(User user);
  14. }

③声明UserDaoImpl实现类

  1. public class UserDaoImpl extends BaseDao<User> implements UserDao {
  2. @Override
  3. public User selectUserByName(String username) {
  4. String sql = "select user_id userId,user_name userName,user_pwd userPwd,email from t_user where user_name=?";
  5. return super.getBean(User.class, sql, username);
  6. }
  7. @Override
  8. public int insertUser(User user) {
  9. String sql = "insert into t_user(user_name,user_pwd,email) values(?,?,?)";
  10. return super.update(sql, user.getUserName(), user.getUserPwd(), user.getEmail());
  11. }
  12. }

④测试方法

  1. @Test
  2. public void testUserDaoGetUserByName() {
  3. UserDao userDao = new UserDaoImpl();
  4. User user = userDao.selectUserByName("陈冠希");
  5. System.out.println("user = " + user);
  6. }
  7. @Test
  8. public void testUserDaoSaveUser() {
  9. UserDao userDao = new UserDaoImpl();
  10. User user = new User(null, "陈冠希", "666666", "aaa@qq.com");
  11. int count = userDao.insertUser(user);
  12. System.out.println("count = " + count);
  13. }

第五节 完成带数据库的登录注册

1、密码加密

①加密方式介绍

  • 对称加密:在知道密文和加密算法的情况下,能够反推回明文
  • 非对称加密:

    • 加密:使用私钥加密
    • 解密:使用公钥解密

      ②加密算法:HASH

  • 特点1:不可逆

  • 特点2:加密后,密文长度固定
  • 特点3:输入数据不变,输出数据也保证不变;输入数据变化,输出数据一定变化

常见的HASH算法举例:

  • MD5
  • SHA1
  • SHA512
  • CRC32

    ③执行加密的工具方法

    ```java public class MD5Util {

    /**

    • 针对明文字符串执行MD5加密
    • @param source
    • @return */ public static String encode(String source) {

      // 1.判断明文字符串是否有效 if (source == null || “”.equals(source)) {

      1. throw new RuntimeException("用于加密的明文不可为空");

      }

      // 2.声明算法名称 String algorithm = “md5”;

      // 3.获取MessageDigest对象 MessageDigest messageDigest = null; try {

      1. messageDigest = MessageDigest.getInstance(algorithm);

      } catch (NoSuchAlgorithmException e) {

      1. e.printStackTrace();

      }

      // 4.获取明文字符串对应的字节数组 byte[] input = source.getBytes();

      // 5.执行加密 byte[] output = messageDigest.digest(input);

      // 6.创建BigInteger对象 int signum = 1; BigInteger bigInteger = new BigInteger(signum, output);

      // 7.按照16进制将bigInteger的值转换为字符串 int radix = 16; String encoded = bigInteger.toString(radix).toUpperCase();

      return encoded; }

}

  1. <a name="KGVII"></a>
  2. ### 2、注册功能
  3. <a name="HIaT5"></a>
  4. #### ①目标
  5. 检查用户名是否可用,如果用户名可用则保存User对象
  6. <a name="w2IoO"></a>
  7. #### ②思路
  8. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/26803611/1652806855025-a4ffa837-3b39-4e19-9ed2-85abfecdfec8.png#clientId=ub2b562d0-41b8-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ucfc1cb58&margin=%5Bobject%20Object%5D&name=image.png&originHeight=853&originWidth=789&originalType=url&ratio=1&rotation=0&showTitle=false&size=58905&status=done&style=none&taskId=uf084cbc4-dfbd-435c-8f81-6e76feb261c&title=)
  9. <a name="F8n9k"></a>
  10. #### ③代码
  11. <a name="hEx1n"></a>
  12. ##### [1]创建UserService
  13. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/26803611/1652806853278-8874adfc-4eb4-42ba-a1a1-5c09bf087673.png#clientId=ub2b562d0-41b8-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u6d196cb5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=133&originWidth=350&originalType=url&ratio=1&rotation=0&showTitle=false&size=4243&status=done&style=none&taskId=ucc23f19e-2ec3-4f48-8594-c3d78394a50&title=)
  14. ```java
  15. public interface UserService {
  16. void doRegister(User userForm);
  17. User doLogin(User userForm);
  18. }

开发中,接口设计和接口中方式定义的理念:

  • 方法的返回值应该对应这个方法本身的业务功能
    • 写操作:没有返回值
    • 读操作:有返回值,返回值就是查询的结果
  • 方法执行是否成功
    • 成功:不抛异常
    • 失败:抛异常

启发:
上层方法向下层方法布置任务:方法名、方法的参数
下层方法向上层方法反馈结果:返回值、是否抛异常

#[2]实现UserService接口

image.png
image.png

(1)UserDao声明为成员变量

说明:将来在Servlet中使用Service的时候,也是同样声明为成员变量,那么从Servlet、Service到Dao就都是『单实例,多线程』方式运行。

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao = new UserDaoImpl();
  3. @Override
  4. public void doRegister(User userForm) {
  5. }
  6. @Override
  7. public User doLogin(User userForm) {
  8. return null;
  9. }
  10. }

理由:

  • 创建对象的操作只执行一次
  • 对象在内存中只保存一份,不会过多占用内存空间

    (2)实现注册功能
    1. @Override
    2. public void doRegister(User userForm) {
    3. // 1.从userForm对象中获取用户通过表单提交的用户名
    4. String userName = userForm.getUserName();
    5. // 2.根据用户名调用UserDao方法查询对应的User对象
    6. User userDB = userDao.selectUserByName(userName);
    7. // 3.检查User对象是否为空
    8. if (userDB != null) {
    9. // 4.如果User对象不为空,则抛出异常,通知上层调用方法:用户名已经被占用
    10. throw new RuntimeException("用户名已经被占用");
    11. }
    12. // 5.对表单提交的密码执行MD5加密
    13. // ①取出表单的密码
    14. String userPwd = userForm.getUserPwd();
    15. // ②执行加密
    16. String encode = MD5Util.encode(userPwd);
    17. // ③将加密得到的密文字符串设置回userForm对象
    18. userForm.setUserPwd(encode);
    19. // 6.调用UserDao方法将userForm对象保存到数据库
    20. userDao.insertUser(userForm);
    21. }

    [3]修改RegisterServlet

    ```java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.从请求参数中获取数据封装为User对象 String username = request.getParameter(“username”); String password = request.getParameter(“password”); String email = request.getParameter(“email”);

    User userForm = new User(null, username, password, email);

    // 2.调用UserService的方法执行注册 try {

    1. userService.doRegister(userForm);
    2. // 3.如果没有抛出异常那么就跳转到注册成功的页面
    3. // 选择重定向的原因:跳转到regist_success.html页面后,用户刷新浏览器不会重复提交注册表单
    4. response.sendRedirect(request.getContextPath()+"/pages/user/regist_success.html");

    } catch (Exception e) {

    1. e.printStackTrace();
    2. // 4.如果抛出了异常
    3. response.setContentType("text/html;charset=UTF-8");
    4. response.getWriter().write("注册失败:" + e.getMessage());

    }

}

  1. <a name="is5SF"></a>
  2. ### 3、登录功能
  3. <a name="Sxudq"></a>
  4. #### ①关键点提示
  5. 对用户密码进行验证时,无法将密文解密为明文,只能将明文再次加密为密文,**『比较密文是否一致』**。
  6. <a name="zCBq9"></a>
  7. #### ②思路
  8. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/26803611/1652806855019-5a584506-d322-410e-a309-9cf1a9c5fa94.png#clientId=ub2b562d0-41b8-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u896a42a6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=909&originWidth=690&originalType=url&ratio=1&rotation=0&showTitle=false&size=51747&status=done&style=none&taskId=u836ad0ed-1da9-4d29-8ad9-6c8bdc0434c&title=)
  9. <a name="VX6Hl"></a>
  10. #### ③代码
  11. <a name="BYgkZ"></a>
  12. ##### [1]UserService接口的doLogin()
  13. ```java
  14. @Override
  15. public User doLogin(User userForm) {
  16. // 1.获取表单提交的用户名
  17. String userName = userForm.getUserName();
  18. // 2.根据用户名调用UserDao方法查询User对象
  19. User userDB = userDao.selectUserByName(userName);
  20. // 3.检查数据库查询的User对象是否为null
  21. if (userDB == null) {
  22. // 4.如果数据库查询的User对象为null,说明用户名不正确,抛出异常:登录失败
  23. throw new RuntimeException("用户名或密码不正确");
  24. }
  25. // 5.密码验证
  26. // ①获取表单提交的密码
  27. String userPwdForm = userForm.getUserPwd();
  28. // ②对表单提交的密码进行加密
  29. String encode = MD5Util.encode(userPwdForm);
  30. // ③获取数据库查询到的密码
  31. String userPwdDB = userDB.getUserPwd();
  32. // ④比较表单密码和数据库密码
  33. if (Objects.equals(encode, userPwdDB)) {
  34. // 6.如果密码验证成功,则将从数据库查询出来的User对象返回
  35. return userDB;
  36. }else{
  37. // 7.如果密码验证失败,说明密码不正确,抛出异常:登录失败
  38. throw new RuntimeException("用户名或密码不正确");
  39. }
  40. }

[2]LoginServlet的doPost()方法
  1. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数中获取用户名和密码
  3. String username = request.getParameter("username");
  4. String password = request.getParameter("password");
  5. // 2.封装为User对象
  6. User userForm = new User(null, username, password, null);
  7. // 3.调用UserService的方法执行登录验证
  8. try {
  9. User userDB = userService.doLogin(userForm);
  10. // 4.登录成功后跳转到登录成功页面
  11. response.sendRedirect(request.getContextPath() + "/pages/user/login_success.html");
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. // 5.登录失败则显示提示消息
  15. response.setContentType("text/html;charset=UTF-8");
  16. response.getWriter().write("登录失败:" + e.getMessage());
  17. }
  18. }

书城项目第三阶段

第一节 创建module,迁移代码

1、创建module

image.png
把index.jsp删除

2、加入jar包

attoparser-2.0.5.RELEASE.jar commons-dbutils-1.6.jar druid-1.1.9.jar hamcrest-core-1.3.jar javassist-3.20.0-GA.jar junit-4.12.jar log4j-1.2.15.jar mysql-connector-java-5.1.37-bin.jar ognl-3.1.26.jar slf4j-api-1.7.25.jar slf4j-log4j12-1.7.25.jar thymeleaf-3.0.12.RELEASE.jar unbescape-1.1.6.RELEASE.jar

3、从V02迁移代码

①src目录下的Java源代码

  • 在V02的module中的src目录上点右键
  • show in explorer在操作系统的窗口内打开
  • 在操作系统的窗口内复制package目录和jdbc.properties
  • 在操作系统的窗口内粘贴到V03的module中的src目录下
  • 在V03的src目录下,找到上一个版本的Servlet,全部删除
  • 创建两个子包
    • 存放Servlet基类:com.atguigu.bookstore.servlet.base
    • 存放Servlet子类:com.atguigu.bookstore.servlet.model
  • 从view-demo中,将两个基类迁移过来

    • 视图基类:ViewBaseServlet
    • 方法分发基类:ModelBaseServlet

      ②前端页面

  • 将V02中的pages目录整体复制到V03 module的WEB-INF目录

  • 将V02中的static目录整体复制到V03 module的web目录
  • 将V02中的index.html复制到V03 module的WEB-INF/pages目录下,将来通过Servlet访问

    4、显示首页

    ①配置web.xml

    1. <!-- 在上下文参数中配置视图前缀和视图后缀 -->
    2. <context-param>
    3. <param-name>view-prefix</param-name>
    4. <param-value>/WEB-INF/pages/</param-value>
    5. </context-param>
    6. <context-param>
    7. <param-name>view-suffix</param-name>
    8. <param-value>.html</param-value>
    9. </context-param>

    注意:这里需要将WEB-INF下的view改成pages,和当前项目环境的目录结构一致。

    ②创建PortalServlet

    注意:这个PortalServlet映射的地址是/index.html,这样才能保证访问首页时访问它。

    1. <servlet>
    2. <servlet-name>PortalServlet</servlet-name>
    3. <servlet-class>com.atguigu.bookstore.servlet.model.PortalServlet</servlet-class>
    4. </servlet>
    5. <servlet-mapping>
    6. <servlet-name>PortalServlet</servlet-name>
    7. <url-pattern>/index.html</url-pattern>
    8. </servlet-mapping>

    注意:PortalServlet服务于首页的显示,为了降低用户访问首页的门槛,不能附加任何请求参数,所以不能继承ModelBaseServlet,只能继承ViewBaseServlet。 ```java public class PortalServlet extends ViewBaseServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    1. // 将两种请求方式的处理逻辑合并到一个方法
    2. doPost(request, response);

    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    1. // 声明视图名称
    2. String viewName = "index";
    3. // 解析模板视图
    4. processTemplate(viewName, request, response);

    }

}

  1. <a name="IMYVu"></a>
  2. #### ③调整index.html
  3. - 加入Thymeleaf名称空间
  4. ```html
  5. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 修改base标签

    1. <base th:href="@{/}" href="/bookstore/"/>

    第二节 完成用户模块

    1、重构登录功能

    ①思路

    image.png

    ②实现:创建并组装组件

    [1]创建UserServlet

    web.xml中的配置:

    1. <servlet>
    2. <servlet-name>UserServlet</servlet-name>
    3. <servlet-class>com.atguigu.bookstore.servlet.model.UserServlet</servlet-class>
    4. </servlet>
    5. <servlet-mapping>
    6. <servlet-name>UserServlet</servlet-name>
    7. <url-pattern>/UserServlet</url-pattern>
    8. </servlet-mapping>

    Java代码:

    1. public class UserServlet extends ModelBaseServlet {
    2. protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    3. }
    4. protected void toLoginPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    5. }
    6. protected void toLoginSuccessPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    7. }
    8. }

    注意:记得修改UserServlet继承的类ModelBaseServlet。

    [2]把UserService组件组装到UserServlet中
    1. public class UserServlet extends ModelBaseServlet {
    2. private UserService userService = new UserServiceImpl();

    ③实现:前往登录页面

    [1]修改首页中登录超链接
    1. <a href="UserServlet?method=toLoginPage" class="login">登录</a>

    [2]完成UserServlet.toLoginPage()方法
    1. protected void toLoginPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    2. String viewName = "user/login";
    3. processTemplate(viewName, request, response);
    4. }

    [3]调整登录页面代码
  • 加入Thymeleaf名称空间

    1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 修改base标签

    1. <base th:href="@{/}" href="/bookstore/" />
  • 修改form标签action属性

    1. <form id="loginForm" action="UserServlet" method="post">
  • 增加method请求参数的表单隐藏域

    1. <input type="hidden" name="method" value="doLogin" />
  • 根据条件显示登录失败消息

    1. <p style="color: red;font-weight: bold;" th:if="${not #strings.isEmpty(message)}" th:text="${message}">这里根据条件显示登录失败消息</p>

    [4]回显表单中的用户名

    遇到问题:使用th:value=”${param.username}”确实实现了服务器端渲染,但是实际打开页面并没有看到。原因是页面渲染顺序:

  • 服务器端渲染

  • 服务器端将渲染结果作为响应数据返回给浏览器
  • 浏览器加载HTML文档
  • 读取到Vue代码后,执行Vue代码
  • Vue又进行了一次浏览器端渲染,覆盖了服务器端渲染的值

解决办法:将服务器端渲染的结果设置到Vue对象的data属性中。

  1. new Vue({
  2. "el":"#loginForm",
  3. "data":{
  4. "username":"[[${param.username}]]",
  5. "password":""
  6. },

④实现:前往登录成功页面

UserServlet.toLoginSuccessPage()

  1. protected void toLoginSuccessPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "user/login_success";
  3. processTemplate(viewName, request, response);
  4. }

login_success.html

  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. ……
  3. <base th:href="@{/}" href="/bookstore/"/>

⑤实现:完成登录操作

  1. protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数中获取用户名和密码
  3. String username = request.getParameter("username");
  4. String password = request.getParameter("password");
  5. // 2.封装为User对象
  6. User userForm = new User(null, username, password, null);
  7. // 3.调用UserService的方法执行登录验证
  8. try {
  9. User userDB = userService.doLogin(userForm);
  10. // 4.登录成功后跳转到登录成功页面
  11. response.sendRedirect(request.getContextPath() + "/UserServlet?method=toLoginSuccessPage");
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. // 5.登录失败则显示提示消息
  15. // ①将登录失败的提示消息存入请求域
  16. request.setAttribute("message", e.getMessage());
  17. // ②执行登录页面的模板渲染
  18. String viewName = "user/login";
  19. processTemplate(viewName, request, response);
  20. }
  21. }

2、重构注册功能

①思路

image.png

②实现:前往注册页面

[1]修改首页中注册超链接
  1. <a href="UserServlet?method=toRegisterPage" class="register">注册</a>

[2]完成UserServlet.toRegisterPage()方法
  1. protected void toRegisterPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "user/regist";
  3. processTemplate(viewName, request, response);
  4. }

[3]调整注册页面代码
  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. ……
  3. <base th:href="@{/}" href="/bookstore/"/>
  4. ……
  5. <form id="registerForm" action="UserServlet" method="post">
  6. <input type="hidden" name="method" value="doRegister" />
  7. ……
  8. <p style="color: red;font-weight: bold;" th:if="${not #strings.isEmpty(message)}" th:text="${message}">这里根据条件显示注册失败消息</p>
  1. new Vue({
  2. "el":"#registerForm",
  3. "data":{
  4. "username":"[[${param.username}]]",
  5. "password":"",
  6. "passwordConfirm":"",
  7. "email":"[[${param.email}]]",
  8. "code":"",
  9. "usernameCheckMessage":""
  10. }

③实现:前往注册成功页面

UserServlet:

  1. protected void toRegisterSuccessPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "user/regist_success";
  3. processTemplate(viewName, request, response);
  4. }

regist_success.html:

  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. ……
  3. <base th:href="@{/}" href="/bookstore/"/>

④实现:完成注册操作

  1. protected void doRegister(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数中获取数据封装为User对象
  3. String username = request.getParameter("username");
  4. String password = request.getParameter("password");
  5. String email = request.getParameter("email");
  6. User userForm = new User(null, username, password, email);
  7. // 2.调用UserService的方法执行注册
  8. try {
  9. userService.doRegister(userForm);
  10. // 3.如果没有抛出异常那么就跳转到注册成功的页面
  11. // 选择重定向的原因:跳转到regist_success.html页面后,用户刷新浏览器不会重复提交注册表单
  12. response.sendRedirect(request.getContextPath()+"/UserServlet?method=toRegisterSuccessPage");
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. // 4.如果抛出了异常
  16. request.setAttribute("message", e.getMessage());
  17. String viewName = "user/regist";
  18. processTemplate(viewName, request, response);
  19. }
  20. }

第三节 进入后台开发

1、概念辨析

image.png

2、访问后台首页

①思路

首页→后台系统超链接→AdminServlet.toPortalPage()→manager.html

②实现:创建AdminServlet

web.xml

  1. <servlet>
  2. <servlet-name>AdminServlet</servlet-name>
  3. <servlet-class>com.atguigu.bookstore.servlet.model.AdminServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>AdminServlet</servlet-name>
  7. <url-pattern>/AdminServlet</url-pattern>
  8. </servlet-mapping>

Java代码:

  1. public class AdminServlet extends ModelBaseServlet {
  2. protected void toPortalPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  3. String viewName = "manager/manager";
  4. processTemplate(viewName, request, response);
  5. }
  6. }

③实现:调整manager.html

  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. <head>
  3. <base th:href="@{/}" href="/bookstore/"/>

然后去除页面上的所有“../”。

④实现:抽取页面公共部分

[1]公共部分内容

三个超链接:

  1. <a href="./book_manager.html" class="order">图书管理</a>
  2. <a href="./order_manager.html" class="destory">订单管理</a>
  3. <a href="../../index.html" class="gohome">返回商城</a>

[2]抽取它们的理由

为了实现链接地址修改时:一处修改,处处生效

[3]创建包含代码片段的页面

image.png

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <!-- 使用th:fragment属性给代码片段命名 -->
  9. <div th:fragment="navigator">
  10. <a href="book_manager.html" class="order">图书管理</a>
  11. <a href="order_manager.html" class="destory">订单管理</a>
  12. <a href="index.html" class="gohome">返回商城</a>
  13. </div>
  14. </body>
  15. </html>

[4]在有需要的页面引入片段
  1. <div th:include="segment/admin-navigator :: navigator"></div>

第四节 后台图书CRUD

C:Create 增
R:Retrieve 查
U:Update 改
D:Delete 删

1、建模

①物理建模

点击这里查看SQL文件
注:上面链接建议点右键→目标另存为,直接打开会显示乱码

②逻辑建模

  1. package com.atguigu.bookstore.entity;
  2. public class Book {
  3. private Integer bookId;
  4. private String bookName;
  5. private String author;
  6. private Double price;
  7. private Integer sales;
  8. private Integer stock;
  9. private String imgPath;

2、创建并组装组件

①创建Servlet

注意:由于项目分成『前台』和『后台』,所以Servlet也分成两个:

  • 前台:BookPortalServlet
  • 后台:BookManagerServlet

    ②创建BookService

  • 接口:BookService

  • 实现类:BookServiceImpl

    ③创建BookDao

  • 接口:BookDao

  • 实现类:BookDaoImpl

    ④组装

  • 给BookManagerServlet组装BookService

  • 给BookService组装BookDao

    3、图书列表显示功能

    ①思路

    manager.html→图书管理超链接→BookManagerServlet→showBookList()→book_manager.html

    ②实现:修改图书管理超链接

    超链接所在文件位置:
    WEB-INF/pages/segment/admin-navigator.html

    1. <a href="BookManagerServlet?method=showBookList" class="order">图书管理</a>

    ③实现:BookManagerServlet.showBookList()

    ```java protected void showBookList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.调用Service方法查询图书列表 List bookList = bookService.getBookList();

    // 2.将图书列表数据存入请求域 request.setAttribute(“bookList”, bookList);

    // 3.执行视图模板渲染 String viewName = “manager/book_manager”;

    processTemplate(viewName, request, response);

}

  1. <a name="mOnGV"></a>
  2. #### ④实现:BookService.getBookList()
  3. ```java
  4. @Override
  5. public List<Book> getBookList() {
  6. return bookDao.selectBookList();
  7. }

⑤实现:BookDao.selectBookList()

  1. @Override
  2. public List<Book> selectBookList() {
  3. String sql = "select book_id bookId, book_name bookName, author, price, sales, stock, img_path imgPath from t_book order by book_Id desc";
  4. return getBeanList(Book.class, sql);
  5. }

⑥实现:调整book_manager.html

  • Thymeleaf名称空间
  • base标签
  • 路径中的../和./
  • 包含进来的代码片段

    ⑦实现:在book_manager.html中迭代显示图书列表

    1. <tbody th:if="${#lists.isEmpty(bookList)}">
    2. <tr>
    3. <td colspan="7">抱歉,没有查询到您要的数据!</td>
    4. </tr>
    5. </tbody>
    6. <tbody th:if="${not #lists.isEmpty(bookList)}">
    7. <tr th:each="book : ${bookList}">
    8. <td>
    9. <img th:src="${book.imgPath}" src="static/uploads/huozhe.jpg" alt=""/>
    10. </td>
    11. <td th:text="${book.bookName}">活着</td>
    12. <td th:text="${book.price}">100.00</td>
    13. <td th:text="${book.author}">余华</td>
    14. <td th:text="${book.sales}">200</td>
    15. <td th:text="${book.stock}">400</td>
    16. <td>
    17. <a href="book_edit.html">修改</a><a href="" class="del">删除</a>
    18. </td>
    19. </tr>
    20. </tbody>

    4、图书删除功能

    ①思路

    book_manager.html→删除超链接→BookManagerServlet.removeBook()→重定向显示列表功能

    ②实现:删除超链接

    1. <a th:href="@{/BookManagerServlet(method=removeBook,bookId=${book.bookId})}" class="del">删除</a>

    ③实现:BookManagerServlet.removeBook()

    ```java protected void removeBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.从请求参数中获取bookId String bookId = request.getParameter(“bookId”);

    // 2.调用Service方法执行删除 bookService.removeBook(bookId);

    // 3.重定向到显示列表功能 response.sendRedirect(request.getContextPath() + “/BookManagerServlet?method=showBookList”);

}

  1. <a name="DUCaB"></a>
  2. #### ④实现:BookService.removeBook()
  3. ```java
  4. @Override
  5. public void removeBook(String bookId) {
  6. bookDao.deleteBook(bookId);
  7. }

⑤实现:BookDao.deleteBook()

  1. @Override
  2. public void deleteBook(String bookId) {
  3. String sql = "delete from t_book where book_id=?";
  4. update(sql, bookId);
  5. }

5、新增图书功能

①思路

book_manager.html→添加图书超链接→BookManagerServlet.toAddPage()→book_add.html
book_add.html→提交表单→BookManagerServlet.saveBook()→重定向显示列表功能

②实现:添加图书超链接

  1. <a href="BookManagerServlet?method=toAddPage">添加图书</a>

③实现:BookManagerServlet.toAddPage()

  1. protected void toAddPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "book_add";
  3. processTemplate(viewName, request, response);
  4. }

④实现:book_add.html

由book_edit.html复制出来,然后调整表单标签:

  1. <form action="BookManagerServlet" method="post">
  2. <input type="hidden" name="method" value="saveBook" />
  3. <input type="text" name="bookName" placeholder="请输入名称"/>
  4. <input type="number" name="price" placeholder="请输入价格"/>
  5. <input type="text" name="author" placeholder="请输入作者"/>
  6. <input type="number" name="sales" placeholder="请输入销量"/>
  7. <input type="number" name="stock" placeholder="请输入库存"/>

⑤实现:BookManagerServlet.saveBook()

  1. protected void saveBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.获取请求参数
  3. String bookName = request.getParameter("bookName");
  4. String price = request.getParameter("price");
  5. String author = request.getParameter("author");
  6. String sales = request.getParameter("sales");
  7. String stock = request.getParameter("stock");
  8. // 2.封装对象
  9. // ※imgPath按说应该通过文件上传的方式提供,但是现在这个技术还没学
  10. // 所以暂时使用一个固定值
  11. String imgPath = "static/uploads/mi.jpg";
  12. Book book = new Book(null, bookName, author, Double.parseDouble(price), Integer.parseInt(sales), Integer.parseInt(stock), imgPath);
  13. // 3.调用Service方法执行保存
  14. bookService.saveBook(book);
  15. // 4.重定向到显示列表页面
  16. response.sendRedirect(request.getContextPath() + "/BookManagerServlet?method=showBookList");
  17. }

⑥实现:BookService.saveBook()

  1. @Override
  2. public void saveBook(Book book) {
  3. bookDao.insertBook(book);
  4. }

⑦实现:BookDao.insertBook()

  1. @Override
  2. public void insertBook(Book book) {
  3. String sql = "insert into `t_book`(`book_name`,`author`,`price`,`sales`,`stock`,`img_path`) values (?,?,?,?,?,?)";
  4. update(sql, book.getBookName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
  5. }

6、修改图书功能

①思路

book_manager.html→修改图书超链接→BookManagerServlet.toEditPage()→book_edit.html(表单回显)
book_edit.html→提交表单→BookManagerServlet.updateBook()→重定向显示列表功能

②实现:修改图书超链接

  1. <a th:href="@{/BookManagerServlet(method=toEditPage,bookId=${book.bookId})}">删除</a>

③实现:BookManagerServlet.toEditPage()

  1. protected void toEditPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数中获取bookId
  3. String bookId = request.getParameter("bookId");
  4. // 2.根据bookId查询对应的Book对象
  5. Book book = bookService.getBookById(bookId);
  6. // 3.把Book对象存入请求域
  7. request.setAttribute("book", book);
  8. // 4.渲染视图
  9. String viewName = "manager/book_edit";
  10. processTemplate(viewName, request, response);
  11. }

④实现:BookService.getBookById()

  1. @Override
  2. public Book getBookById(String bookId) {
  3. return bookDao.selectBookByPrimaryKey(bookId);
  4. }

⑤实现:BookDao.selectBookByPrimaryKey()

  1. @Override
  2. public Book selectBookByPrimaryKey(String bookId) {
  3. String sql = "select book_id bookId, book_name bookName, author, price, sales, stock, img_path imgPath from t_book where book_id=?";
  4. return getBean(Book.class, sql, bookId);
  5. }

⑥实现:book_edit.html(表单回显)

  1. <form action="BookManagerServlet" method="post">
  2. <input type="hidden" name="method" value="updateBook" />
  3. <input type="text" name="bookName" th:value="${book.bookName}" placeholder="请输入名称"/>
  4. <input type="number" name="price" th:value="${book.price}" placeholder="请输入价格"/>
  5. <input type="text" name="author" th:value="${book.author}" placeholder="请输入作者"/>
  6. <input type="number" name="sales" th:value="${book.sales}" placeholder="请输入销量"/>
  7. <input type="number" name="stock" th:value="${book.stock}" placeholder="请输入库存"/>
  8. <button class="btn">更新</button>

⑦实现:BookManagerServlet.updateBook()

  1. protected void updateBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.获取请求参数
  3. String bookId = request.getParameter("bookId");
  4. String bookName = request.getParameter("bookName");
  5. String price = request.getParameter("price");
  6. String author = request.getParameter("author");
  7. String sales = request.getParameter("sales");
  8. String stock = request.getParameter("stock");
  9. // 2.封装对象
  10. Book book = new Book(Integer.parseInt(bookId), bookName, author, Double.parseDouble(price), Integer.parseInt(sales), Integer.parseInt(stock), null);
  11. // 3.调用Service方法执行更新
  12. bookService.updateBook(book);
  13. // 4.渲染视图
  14. response.sendRedirect(request.getContextPath() + "/BookManagerServlet?method=showBookList");
  15. }

⑧实现:BookService.updateBook()

  1. @Override
  2. public void updateBook(Book book) {
  3. bookDao.updateBook(book);
  4. }

⑨实现:BookDao.updateBook()

注意:这里不修改imgPath字段

  1. @Override
  2. public void updateBook(Book book) {
  3. String sql = "update t_book set book_name=?, author=?, price=?, sales=?, stock=? where book_id=?";
  4. update(sql, book.getBookName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getBookId());
  5. }

第五节 前台图书展示

1、思路

index.html→PortalServlet.doPost()→把图书列表数据查询出来→渲染视图→页面迭代显示图书数据

2、实现:PortalServlet.doPost()

  1. public class PortalServlet extends ViewBaseServlet {
  2. private BookService bookService = new BookServiceImpl();
  3. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  4. // 将两种请求方式的处理逻辑合并到一个方法
  5. doPost(request, response);
  6. }
  7. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  8. // 查询图书列表数据
  9. List<Book> bookList = bookService.getBookList();
  10. // 将图书列表数据存入请求域
  11. request.setAttribute("bookList", bookList);
  12. // 声明视图名称
  13. String viewName = "index";
  14. // 解析模板视图
  15. processTemplate(viewName, request, response);
  16. }
  17. }

3、实现:页面迭代显示图书数据

页面文件:index.html

  1. <div class="list-content" th:if="${#lists.isEmpty(bookList)}">
  2. 抱歉,本商城现在没有上架任何商品
  3. </div>
  4. <div class="list-content" th:if="${not #lists.isEmpty(bookList)}">
  5. <div class="list-item" th:each="book : ${bookList}">
  6. <img th:src="${book.imgPath}" src="static/uploads/huozhe.jpg" alt="">
  7. <p>书名:<span th:text="${book.bookName}">活着</span></p>
  8. <p>作者:<span th:text="${book.author}">余华</span></p>
  9. <p>价格:¥<span th:text="${book.price}">66.6</span></p>
  10. <p>销量:<span th:text="${book.sales}">230</span></p>
  11. <p>库存:<span th:text="${book.stock}">1000</span></p>
  12. <button>加入购物车</button>
  13. </div>
  14. </div>

书城项目第四阶段

第一节 保持登录状态

1、创建module

  • 迁移src目录下的Java代码
  • 迁移web目录下的static目录
  • 迁移web/WEB-INF目录下的lib目录和pages目录
  • 将lib目录下的jar包添加到运行时环境
  • 将旧的web.xml中的配置复制到新module的web.xml中

    2、将登录成功的User对象存入会话域

    image.png ```java protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.从请求参数中获取用户名和密码 String username = request.getParameter(“username”); String password = request.getParameter(“password”);

    // 2.封装为User对象 User userForm = new User(null, username, password, null);

    // 3.调用UserService的方法执行登录验证 try {

    1. User userDB = userService.doLogin(userForm);
    2. // ※保持登录状态,将User对象存入会话域
    3. HttpSession session = request.getSession();
    4. // 注意:不要放错,这里要使用从数据库查询得到的User对象
    5. session.setAttribute("user", userDB);
    6. // 4.登录成功后跳转到登录成功页面
    7. response.sendRedirect(request.getContextPath() + "/UserServlet?method=toLoginSuccessPage");

    } catch (Exception e) {

    1. e.printStackTrace();
    2. // 5.登录失败则显示提示消息
    3. // ①将登录失败的提示消息存入请求域
    4. request.setAttribute("message", e.getMessage());
    5. // ②执行登录页面的模板渲染
    6. String viewName = "user/login";
    7. processTemplate(viewName, request, response);

    }

}

  1. <a name="sVFb3"></a>
  2. ### 3、修改欢迎信息
  3. <a name="N8RRo"></a>
  4. #### ①登录成功页面
  5. ```html
  6. <span>欢迎<span class="um_span" th:text="${session.user.username}">张总</span>光临尚硅谷书城</span>

②首页

  1. <div class="topbar-right" th:if="${session.user == null}">
  2. <a href="UserServlet?method=toLoginPage" class="login">登录</a>
  3. <a href="UserServlet?method=toRegisterPage" class="register">注册</a>
  4. <a href="pages/cart/cart.html" class="cart iconfont icon-gouwuche">
  5. 购物车
  6. <div class="cart-num">3</div>
  7. </a>
  8. <a href="AdminServlet?method=toPortalPage" class="admin">后台管理</a>
  9. </div>
  10. <!-- 登录后风格-->
  11. <div class="topbar-right" th:if="${session.user != null}">
  12. <span>欢迎你<b th:text="${session.user.userName}">张总</b></span>
  13. <a href="#" class="register">注销</a>
  14. <a href="pages/cart/cart.jsp" class="cart iconfont icon-gouwuche">
  15. 购物车
  16. <div class="cart-num">3</div>
  17. </a>
  18. <a href="pages/manager/book_manager.html" class="admin">后台管理</a>
  19. </div>

4、退出登录功能

①页面超链接

  1. <a href="UserServlet?method=logout">注销</a>

②UserServlet.logout()

  1. protected void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. HttpSession session = request.getSession();
  3. // 退出登录方案一:从会话域移除User对象
  4. // session.removeAttribute("user");
  5. // 退出登录方案二:强制Session对象失效
  6. session.invalidate();
  7. processTemplate("index", request, response);
  8. }

第二节 验证码

1、目标

通过让用户填写验证码并在服务器端检查,防止浏览器端使用程序恶意访问。

2、思路

image.png

3、操作

①导入jar包

kaptcha-2.3.2.jar

②配置KaptchaServlet

jar包中已经写好了Servlet的Java类,我们只需要在web.xml中配置这个Servlet即可。

  1. <servlet>
  2. <servlet-name>KaptchaServlet</servlet-name>
  3. <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>KaptchaServlet</servlet-name>
  7. <url-pattern>/KaptchaServlet</url-pattern>
  8. </servlet-mapping>

③通过页面访问测试

http://localhost:8080/bookstore/KaptchaServlet

④在注册页面显示验证码图片

image.png

  1. <img src="KaptchaServlet" alt="" />

⑤调整验证码图片的显示效果

[1]去掉边框

KaptchaServlet会在初始化时读取init-param,而它能够识别的init-param在下面类中:

com.google.code.kaptcha.util.Config

web.xml中具体配置如下:

  1. <servlet>
  2. <servlet-name>KaptchaServlet</servlet-name>
  3. <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
  4. <!-- 通过配置初始化参数影响KaptchaServlet的工作方式 -->
  5. <!-- 可以使用的配置项参考com.google.code.kaptcha.util.Config类 -->
  6. <!-- 配置kaptcha.border的值为false取消图片边框 -->
  7. <init-param>
  8. <param-name>kaptcha.border</param-name>
  9. <param-value>no</param-value>
  10. </init-param>
  11. </servlet>
  12. <servlet-mapping>
  13. <servlet-name>KaptchaServlet</servlet-name>
  14. <url-pattern>/KaptchaServlet</url-pattern>
  15. </servlet-mapping>

开发过程中的工程化细节: no、false、none等等单词从含义上来说都表示『没有边框』这个意思,但是这里必须使用no。 参考的依据是下面的源码:

  1. public boolean getBoolean(String paramName, String paramValue, boolean defaultValue) {
  2. boolean booleanValue;
  3. if (!"yes".equals(paramValue) && !"".equals(paramValue) && paramValue != null) {
  4. if (!"no".equals(paramValue)) {
  5. throw new ConfigException(paramName, paramValue, "Value must be either yes or no.");
  6. }
  7. booleanValue = false;
  8. } else {
  9. booleanValue = defaultValue;
  10. }
  11. return booleanValue;
  12. }

[2]设置图片大小
  1. <img style="width: 150px; height: 40px;" src="KaptchaServlet" alt="" />

⑥点击图片刷新

[1]目的

验证码图片都是经过刻意扭曲、添加了干扰、角度偏转,故意增加了识别的难度。所以必须允许用户在看不出来的时候点击图片刷新,生成新的图片重新辨认。

[2]实现的代码

修改图片的img标签:

  1. <img @click="refreshCodeImage" style="width: 150px; height: 40px;" src="KaptchaServlet" alt="" />

Vue代码:将refreshCodeImage()单击响应函数声明到注册表单验证功能的Vue对象的methods属性中

  1. ,
  2. "refreshCodeImage":function () {
  3. // 通过event事件对象的target属性获取当前正在点击的img标签
  4. var imgEle = event.target;
  5. // 设置img标签的src属性
  6. imgEle.src = "KaptchaServlet?random=" + Math.random();
  7. }

⑦执行注册前检查验证码

image.png

[1]确认KaptchaServlet将验证码存入Session域时使用的属性名

image.png
通过查看源码,找到验证码存入Session域时使用的属性名是:

KAPTCHA_SESSION_KEY

[2]在执行注册的方法中添加新的代码
  1. protected void doRegister(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // ※检查验证码
  3. // 1.从请求参数中获取用户提交的验证码
  4. String codeForm = request.getParameter("code");
  5. // 2.获取Session域中保存的验证码
  6. HttpSession session = request.getSession();
  7. String codeSystem = (String) session.getAttribute("KAPTCHA_SESSION_KEY");
  8. // 3.将表单验证码和系统验证码进行比较
  9. if (Objects.equals(codeForm, codeSystem)) {
  10. // 4.如果比较后发现二者一致,则将用过的验证码从Session域移除
  11. session.removeAttribute("KAPTCHA_SESSION_KEY");
  12. }else{
  13. // 5.如果比较后发现二者不一致,则返回注册的表单页面显示提示信息
  14. request.setAttribute("message", "验证码不正确,请重新填写");
  15. String viewName = "user/regist";
  16. processTemplate(viewName, request, response);
  17. // 6.停止执行当前方法
  18. return ;
  19. }
  20. // 后续是原来的代码……

第三节 购物车

1、功能清单

  • 添加购物车
  • 显示购物车信息
  • 修改购物车中具体商品的数量
  • 删除购物车中某个具体商品
  • 清空购物车

    2、创建购物车模型

    image.png

    ①购物车详情类:CartItem

    image.png

    1. public class CartItem {
    2. private String bookId;
    3. private String bookName;
    4. private String imgPath;
    5. private Double price;
    6. private Integer count;
    7. private Double amount;
    8. // 获取金额时需要计算得到
    9. public Double getAmount() {
    10. return this.count * this.price;
    11. }

    ②购物车类:Cart

    1. public class Cart {
    2. private Map<String, CartItem> cartItemMap = new HashMap<>();
    3. // 添加购物车
    4. public void addCartItem(Book book) {
    5. // 1.获取当前Book对象的id值
    6. String bookId = book.getBookId() + "";
    7. // 2.根据id值到Map中检查是否已存在
    8. if (cartItemMap.containsKey(bookId)) {
    9. // 3.如果存在,则给原有的CartItem增加数量
    10. CartItem cartItem = cartItemMap.get(bookId);
    11. int newCount = cartItem.getCount() + 1;
    12. cartItem.setCount(newCount);
    13. } else {
    14. // 4.如果不存在,则创建新的CartItem对象
    15. CartItem cartItem = new CartItem();
    16. cartItem.setBookId(bookId);
    17. cartItem.setBookName(book.getBookName());
    18. cartItem.setCount(1);
    19. cartItem.setImgPath(book.getImgPath());
    20. cartItem.setPrice(book.getPrice());
    21. // 5.将新的CartItem对象存入cartItemMap
    22. cartItemMap.put(bookId, cartItem);
    23. }
    24. }
    25. // 从购物车中删除CartItem
    26. public void removeCartItem(String bookId) {
    27. cartItemMap.remove(bookId);
    28. }
    29. // 把某个CartItem的数量修改为指定值
    30. public void updateItemCount(String bookId, Integer newCount) {
    31. // 根据bookId从Map中获取对应的CartItem对象
    32. CartItem cartItem = cartItemMap.get(bookId);
    33. // 设置新的数量值
    34. cartItem.setCount(newCount);
    35. }
    36. // 把某个CartItem的数量+1
    37. public void itemCountIncrease(String bookId) {
    38. CartItem cartItem = cartItemMap.get(bookId);
    39. cartItem.setCount(cartItem.getCount() + 1);
    40. }
    41. // 把某个CartItem的数量-1
    42. public void itemCountDecrease(String bookId) {
    43. CartItem cartItem = cartItemMap.get(bookId);
    44. cartItem.setCount(cartItem.getCount() - 1);
    45. if (cartItem.getCount() == 0) {
    46. removeCartItem(bookId);
    47. }
    48. }
    49. public Map<String, CartItem> getCartItemMap() {
    50. return cartItemMap;
    51. }
    52. // 计算总数量
    53. public Integer getTotalCount() {
    54. // 1.声明一个变量用于存储累加结果
    55. Integer sum = 0;
    56. // 2.遍历Map集合
    57. Set<String> keySet = cartItemMap.keySet();
    58. for (String key : keySet) {
    59. CartItem cartItem = cartItemMap.get(key);
    60. Integer count = cartItem.getCount();
    61. sum = sum + count;
    62. }
    63. // 3.返回累加结果
    64. return sum;
    65. }
    66. // 计算总金额
    67. public Double getTotalAmount() {
    68. // 1.声明一个变量用于存储累加结果
    69. Double sum = 0.0;
    70. // 2.遍历Map集合
    71. Set<String> keySet = cartItemMap.keySet();
    72. for (String key : keySet) {
    73. CartItem cartItem = cartItemMap.get(key);
    74. Double amount = cartItem.getAmount();
    75. sum = sum + amount;
    76. }
    77. // 3.返回累加结果
    78. return sum;
    79. }
    80. }

    3、添加购物车功能

    ①目标

    在首页点击『加入购物车』将该条图书加入『购物车』。

    ②思路

    首页→加入购物车→CartServlet.addCart()→执行添加操作→回到首页

    ③代码实现

    [1]创建CartServlet

    image.png ```java public class CartServlet extends ModelBaseServlet {

    private BookService bookService = new BookServiceImpl();

    protected void addCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    1. // 1.从请求参数中获取bookId
    2. String bookId = request.getParameter("bookId");
    3. // 2.根据bookId查询图书数据
    4. Book book = bookService.getBookById(bookId);
    5. // 3.获取Session对象
    6. HttpSession session = request.getSession();
    7. // 4.尝试从Session域获取购物车对象
    8. Cart cart = (Cart) session.getAttribute("cart");
    9. // 5.判断Cart对象是否存在
    10. if (cart == null) {
    11. // 6.如果不存在,则创建新的Cart对象
    12. cart = new Cart();
    13. // 7.将新创建的Cart对象存入Session域
    14. session.setAttribute("cart", cart);
    15. }
    16. // 8.添加购物车
    17. cart.addCartItem(book);
    18. // 9.回首页
    19. response.sendRedirect(request.getContextPath() + "/index.html");

    }

}

  1. <a name="GOucd"></a>
  2. ##### [2]index.html页面
  3. 购物车数量显示:
  4. ```html
  5. <!-- 登录后风格-->
  6. <div class="topbar-right" th:if="${session.user != null}">
  7. <span>欢迎你<b th:text="${session.user.userName}">张总</b></span>
  8. <a href="#" class="register">注销</a>
  9. <a href="pages/cart/cart.jsp" class="cart iconfont icon-gouwuche">
  10. 购物车
  11. <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
  12. </a>
  13. <a href="pages/manager/book_manager.html" class="admin">后台管理</a>
  14. </div>

加入购物车:

  1. <div class="list-content" th:if="${not #lists.isEmpty(bookList)}">
  2. <div class="list-item" th:each="book : ${bookList}">
  3. <img th:src="${book.imgPath}" src="static/uploads/huozhe.jpg" alt="">
  4. <p>书名:<span th:text="${book.bookName}">活着</span></p>
  5. <p>作者:<span th:text="${book.author}">余华</span></p>
  6. <p>价格:¥<span th:text="${book.price}">66.6</span></p>
  7. <p>销量:<span th:text="${book.sales}">230</span></p>
  8. <p>库存:<span th:text="${book.stock}">1000</span></p>
  9. <!--<button>加入购物车</button>-->
  10. <a th:href="@{/CartServlet(method=addCart,bookId=${book.bookId})}">加入购物车</a>
  11. </div>
  12. </div>

④CSS样式

image.png

  1. .books-list .list-content .list-item a {
  2. display: block;
  3. width: 80px;
  4. height: 30px;
  5. border: none;
  6. line-height: 30px;
  7. background-color: #39987c;
  8. margin-top: 5px;
  9. outline: none;
  10. color: #fff;
  11. cursor:pointer;
  12. font-size:12px;
  13. text-align:center;
  14. }

如果修改完成后页面效果没有改变,可以使用Ctrl+F5强制刷新。

4、显示购物车页面

①目标

把购物车信息在专门的页面显示出来

②思路

首页→购物车超链接→CartServlet.showCart()→cart.html

③代码实现

[1]购物车超链接

登录状态和未登录状态

  1. <a href="CartServlet?method=showCart" class="cart iconfont icon-gouwuche">购物车</a>

[2]CartServlet
  1. protected void showCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "cart/cart";
  3. processTemplate(viewName, request, response);
  4. }

[3]cart.html
  1. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2. <head>
  3. <base th:href="@{/}"/>
  4. ……
  5. <div class="header-right" th:include="segment/welcome :: welcome-page"></div>
  6. ……
  7. <tbody th:if="${session.cart == null || session.cart.cartItemMap.size() == 0}">
  8. <tr>
  9. <td colspan="6">购物车还是空空的,赶紧去添加吧!</td>
  10. </tr>
  11. </tbody>
  12. <tbody th:if="${session.cart != null && session.cart.cartItemMap.size() > 0}">
  13. <tr th:each="cartItemEntry : ${session.cart.cartItemMap}">
  14. <td>
  15. <img th:src="${cartItemEntry.value.imgPath}" src="static/uploads/huozhe.jpg" alt=""/>
  16. </td>
  17. <td th:text="${cartItemEntry.value.bookName}">活着</td>
  18. <td>
  19. <span class="count">-</span>
  20. <input class="count-num" type="text" th:value="${cartItemEntry.value.count}" value="1"/>
  21. <span class="count">+</span>
  22. </td>
  23. <td th:text="${cartItemEntry.value.price}">36.8</td>
  24. <td th:text="${cartItemEntry.value.amount}">36.8</td>
  25. <td><a href="">删除</a></td>
  26. </tr>
  27. </tbody>
  28. </table>
  29. <div class="footer" th:if="${session.cart != null && session.cart.cartItemMap.size() > 0}">
  30. <div class="footer-left">
  31. <a href="#" class="clear-cart">清空购物车</a>
  32. <a href="index.html">继续购物</a>
  33. </div>
  34. <div class="footer-right">
  35. <div><span th:text="${session.cart.totalCount}">3</span>件商品</div>
  36. <div class="total-price">总金额<span th:text="${session.cart.totalAmount}">99.9</span></div>
  37. <a class="pay" href="checkout.html">去结账</a>
  38. </div>
  39. </div>

5、清空购物车

①目标

当用户确定点击清空购物车,将Session域中的Cart对象移除。

②思路

cart.html→清空购物车超链接→绑定单击响应函数→confirm()确认→确定→CartServlet.clearCart()→从Session域移除Cart对象

③代码实现

[1]清空购物车超链接
  1. <a href="CartServlet?method=clearCart" class="clear-cart">清空购物车</a>

[2]绑定单击响应函数

(1)引入Vue
  1. <script src="static/script/vue.js" type="text/javascript" charset="utf-8"></script>

(2)编写Vue代码
  1. new Vue({
  2. "el":"#appCart",
  3. "methods":{
  4. "clearCart":function () {
  5. // 1.使用confirm()弹框向用户确认是否要清空
  6. var confirmResult = confirm("您真的要清空购物车吗?");
  7. // 2.如果用户点击了取消
  8. if (!confirmResult) {
  9. // 3.调用事件对象的方法阻止超链接跳转页面
  10. event.preventDefault();
  11. }
  12. }
  13. }
  14. });

[3]CartServlet.clearCart()
  1. protected void clearCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.通过request对象获取Session对象
  3. HttpSession session = request.getSession();
  4. // 2.将Session域的Cart对象移除
  5. session.removeAttribute("cart");
  6. // 3.渲染视图
  7. String viewName = "cart/cart";
  8. processTemplate(viewName, request, response);
  9. }

6、减号

①目标

  • 在大于1的数值基础上-1:执行-1的逻辑
  • 在1的基础上-1:执行删除item的逻辑

    ②思路

    减号span绑定单击响应函数→获取文本框中的数据:当前数量→检查数量是否大于1

  • 数量大于1:发送请求访问CartServlet.decrease()方法→回到cart.html

  • 数量等于1:confirm确认→发送请求访问CartServlet.removeItem()方法→回到cart.html

    ③后端代码

    CartServlet.decrease() ```java protected void decrease(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.通过请求参数获取bookId String bookId = request.getParameter(“bookId”);

    // 2.通过request对象获取Session对象 HttpSession session = request.getSession();

    // 3.从Session域获取购物车对象 Cart cart = (Cart) session.getAttribute(“cart”);

    // ※考虑到如果Session超时会导致购物车数据丢失,所以先判断购物车数据是否为空 if (cart != null) {

    1. // 4.从购物车对象中根据bookId执行-1操作
    2. cart.itemCountDecrease(bookId);

    }

    // 5.回到购物车页面 String viewName = “cart/cart”;

    processTemplate(viewName, request, response);

}

  1. <a name="O80p6"></a>
  2. #### ④前端代码
  3. HTML代码:
  4. ```html
  5. <td>
  6. <input type="hidden" name="bookId" th:value="${cartItemEntry.value.bookId}" />
  7. <span @click="itemDecrease" class="count">-</span>
  8. ……
  9. </td>

Vue代码:

  1. ,
  2. "itemDecrease":function () {
  3. // 0.不管执行两种逻辑中的哪一种,都需要将当前item的bookId发送到Servlet
  4. // event:事件对象
  5. // event.target:当前事件操作的控件(HTML元素)
  6. // previousSibling:上一个兄弟节点。当前span标签的上一个兄弟节点是空格,所以要再找上一个兄弟
  7. var bookId = event.target.previousSibling.previousSibling.value;
  8. console.log("bookId="+bookId);
  9. // 1.获取文本框中的value属性值:当前item的数量
  10. // event:事件对象
  11. // event.target:当前事件操作的控件(HTML元素)
  12. // nextSibling:下一个兄弟节点。当前span标签的下一个兄弟节点是空格,所以要再找下一个兄弟
  13. // value:获取input标签的value属性值
  14. var currentCount = event.target.nextSibling.nextSibling.value;
  15. console.log("currentCount="+currentCount);
  16. // 2.判断currentCount是否大于1
  17. if (currentCount > 1) {
  18. // 3.应用-1逻辑
  19. console.log("currentCount > 1");
  20. window.location.href = "CartServlet?method=decrease&bookId="+bookId;
  21. } else {
  22. // 通过DOM树形结构获取图书名称数据
  23. // event:事件对象
  24. // event.target:当前事件操作的控件(HTML元素)
  25. // parentNode:访问父节点
  26. // parentNode.getElementsByTagName():在父节点范围内根据标签名称查找元素,返回数组
  27. // innerHTML:返回标签内部HTML代码
  28. var bookName = event.target.parentNode.parentNode.getElementsByTagName("td")[1].innerHTML;
  29. // 4.应用删除item的逻辑
  30. var confirmResult = confirm("你真的要从购物车中删除『"+bookName+"』这条记录吗?");
  31. if (confirmResult) {
  32. window.location.href = "CartServlet?method=removeItem&bookId="+bookId;
  33. }
  34. }
  35. }

7、删除

①目标

点击删除超链接后,把对应的CartItem从Cart中删除

②思路

cart.html→删除超链接→CartServlet.removeItem()→回到cart.html

③代码实现

[1]后端代码

CartServlet.removeItem()

  1. protected void removeItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数获取bookId
  3. String bookId = request.getParameter("bookId");
  4. // 2.通过request对象获取Session对象
  5. HttpSession session = request.getSession();
  6. // 3.从Session域获取购物车对象
  7. Cart cart = (Cart) session.getAttribute("cart");
  8. // ※考虑到如果Session超时会导致购物车数据丢失,所以先判断购物车数据是否为空
  9. if (cart != null) {
  10. // 4.执行删除item操作
  11. cart.removeCartItem(bookId);
  12. }
  13. // 5.回到购物车页面
  14. String viewName = "cart/cart";
  15. processTemplate(viewName, request, response);
  16. }

[2]前端代码

HTML代码:

  1. <a @click="removeConfirm" th:href="@{/CartServlet(method=removeItem,bookId=${cartItemEntry.value.bookId})}">删除</a>

Vue代码:

  1. ,
  2. "removeConfirm":function () {
  3. // 通过DOM树形结构获取图书名称数据
  4. // event:事件对象
  5. // event.target:当前事件操作的控件(HTML元素)
  6. // parentNode:访问父节点
  7. // parentNode.getElementsByTagName():在父节点范围内根据标签名称查找元素,返回数组
  8. // innerHTML:返回标签内部HTML代码
  9. var bookName = event.target.parentNode.parentNode.getElementsByTagName("td")[1].innerHTML;
  10. console.log("bookName=" + bookName);
  11. if(!confirm("你真的要从购物车中删除『"+bookName+"』这条记录吗?")) {
  12. event.preventDefault();
  13. }
  14. }

8、文本框修改

①目标

用户在文本框输入新数据后,根据用户输入在Session中的Cart中修改CartItem中的count

②思路

cart.html→前端数据校验→CartServlet.changeCount()→回到cart.html

③代码实现

[1]后端代码

CartServlet.changeCount()

  1. protected void changeCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数获取bookId和newCount
  3. String bookId = request.getParameter("bookId");
  4. Integer newCount = Integer.parseInt(request.getParameter("newCount"));
  5. // 2.通过request对象获取Session对象
  6. HttpSession session = request.getSession();
  7. // 3.从Session域获取购物车对象
  8. Cart cart = (Cart) session.getAttribute("cart");
  9. // ※考虑到如果Session超时会导致购物车数据丢失,所以先判断购物车数据是否为空
  10. if (cart != null) {
  11. // 4.执行修改数量操作
  12. cart.updateItemCount(bookId, newCount);
  13. }
  14. // 5.回到购物车页面
  15. String viewName = "cart/cart";
  16. processTemplate(viewName, request, response);
  17. }

[2]前端代码

HTML代码:

  1. <td>
  2. <input type="hidden" name="bookId" th:value="${cartItemEntry.value.bookId}" />
  3. ……
  4. <input @change="itemCountChange" class="count-num" type="text" th:value="${cartItemEntry.value.count}" value="1"/>
  5. ……
  6. </td>

Vue代码:

  1. ,
  2. "itemCountChange":function () {
  3. // 1.获取文本框中输入的数据
  4. var newCount = event.target.value;
  5. // 6.排除小数或零或负数的情况
  6. var regExp = /^[0-9]+$/;
  7. if (!regExp.test(newCount) || newCount == 0) {
  8. // 7.如果用户输入的数据不是数字,给出提示
  9. alert("请输入正整数!");
  10. // 8.将文本框恢复为默认值
  11. event.target.value = event.target.defaultValue;
  12. // 9.当前函数停止执行
  13. return ;
  14. }
  15. // 10.获取bookId值
  16. var bookId = event.target.parentNode.getElementsByTagName("input")[0].value;
  17. console.log("bookId="+bookId);
  18. // 11.发送请求执行修改
  19. window.location.href = "CartServlet?method=changeCount&bookId="+bookId+"&newCount="+newCount;
  20. }

9、加号

①目标

告诉Servlet将Session域中Cart对象里面对应的CartItem执行count+1操作

②思路

加号span绑定单击响应函数→获取文本框中的数据:当前数量→CartServlet.increase()→回到cart.html

③代码实现

[1]后端代码

CartServlet.increase()

  1. protected void increase(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.通过请求参数获取bookId
  3. String bookId = request.getParameter("bookId");
  4. // 2.通过request对象获取Session对象
  5. HttpSession session = request.getSession();
  6. // 3.从Session域获取购物车对象
  7. Cart cart = (Cart) session.getAttribute("cart");
  8. // ※考虑到如果Session超时会导致购物车数据丢失,所以先判断购物车数据是否为空
  9. if (cart != null) {
  10. // 4.从购物车对象中根据bookId执行-1操作
  11. cart.itemCountIncrease(bookId);
  12. }
  13. // 5.回到购物车页面
  14. String viewName = "cart/cart";
  15. processTemplate(viewName, request, response);
  16. }

[2]前端代码

HTML代码:

  1. <td>
  2. <input type="hidden" name="bookId" th:value="${cartItemEntry.value.bookId}" />
  3. ……
  4. <span @click="itemIncrease" class="count">+</span>
  5. </td>

Vue代码:

  1. ,
  2. "itemIncrease":function () {
  3. // 1.获取bookId值
  4. var bookId = event.target.parentNode.getElementsByTagName("input")[0].value;
  5. // 2.发送请求执行+1
  6. window.location.href = "CartServlet?method=increase&bookId="+bookId;
  7. }

10、Double数据运算过程中精度调整

①问题现象

image.png

②解决方案

  • 使用BigDecimal类型来进行Double类型数据运算
  • 创建BigDecimal类型对象时将Double类型的数据转换为字符串

Cart类:

  1. // 计算总金额
  2. public Double getTotalAmount() {
  3. // 1.声明一个变量用于存储累加结果
  4. BigDecimal sum = new BigDecimal("0.0");
  5. // 2.遍历Map集合
  6. Set<String> keySet = cartItemMap.keySet();
  7. for (String key : keySet) {
  8. CartItem cartItem = cartItemMap.get(key);
  9. Double amount = cartItem.getAmount();
  10. sum = sum.add(new BigDecimal(amount + ""));
  11. }
  12. // 3.返回累加结果
  13. return sum.doubleValue();
  14. }

CartItem类:

  1. // 获取金额时需要计算得到
  2. public Double getAmount() {
  3. BigDecimal bigDecimalCount = new BigDecimal(this.count + "");
  4. BigDecimal bigDecimalPrice = new BigDecimal(this.price + "");
  5. BigDecimal multiReuslt = bigDecimalCount.multiply(bigDecimalPrice);
  6. return multiReuslt.doubleValue();
  7. }

书城项目第五阶段

第一节 登录检查

1、目标

把项目中需要保护的功能保护起来,没有登录不允许访问。但是我们不考虑后台登录检查,仅完成前台登录检查。

  • 购物车功能
  • 订单功能

    2、思路

    image.png

    3、代码实现

    ①拦截受保护资源的请求

    购物车资源地址:/protected/CartServlet
    订单资源地址:/protected/OrderServlet
    Filter拦截的地址:/protected/*

    ②对访问购物车资源的地址进行修改

    [1]首页加入购物车

    1. <a th:href="@{/protected/CartServlet(method=addCart,bookId=${book.bookId})}">加入购物车</a>

    [2]首页显示购物车
    1. <a href="protected/CartServlet?method=showCart" class="cart iconfont icon-gouwuche">购物车</a>

    [3]cart.html清空购物车
    1. <a @click="clearCart" href="protected/CartServlet?method=clearCart" class="clear-cart">清空购物车</a>

    [4]cart.html删除超链接
    1. <a @click="removeConfirm" th:href="@{/protected/CartServlet(method=removeItem,bookId=${cartItemEntry.value.bookId})}">删除</a>

    [5]cart.html中Vue代码

    凡是涉及到window.location.href都需要修改:

    1. window.location.href = "protected/CartServlet?method=decrease&bookId="+bookId;
    2. ……

    ③web.xml中修改CartServlet的url-pattern

    1. <servlet>
    2. <servlet-name>CartServlet</servlet-name>
    3. <servlet-class>com.atguigu.bookstore.servlet.model.CartServlet</servlet-class>
    4. </servlet>
    5. <servlet-mapping>
    6. <servlet-name>CartServlet</servlet-name>
    7. <url-pattern>/protected/CartServlet</url-pattern>
    8. </servlet-mapping>

    ④创建执行登录检查的Filter

    [1]Filter类

    image.png ```java /**

    • 判断当前请求是否已登录的Filter
    • 由于在web.xml的配置中,这个Filter只拦截/protected开头的路径,
    • 所以,凡是被这个Filter拦截的请求都要求必须登录 */ public class LoginFilter implements Filter { public void destroy() {} public void init(FilterConfig config) throws ServletException {}

      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

      1. // 1.将request对象的类型转换为HttpServletRequest
      2. HttpServletRequest httpRequest = (HttpServletRequest) request;
      3. // 2.通过httpRequest对象获取HttpSession对象
      4. HttpSession session = httpRequest.getSession();
      5. // 3.尝试从Session域获取User对象
      6. User user = (User) session.getAttribute("user");
      7. // 4.判断User对象是否存在
      8. if (user == null) {
      9. // 5.转发到登录页面,提示用户登录后再操作
      10. request.setAttribute("message", "请登录后再操作");
      11. request.getRequestDispatcher("/UserServlet?method=toLoginPage").forward(request, response);
      12. }else{
      13. // 6.如果User对象存在,说明用户已登录,直接放行
      14. chain.doFilter(request, response);
      15. }

      }

}

  1. <a name="eC1p9"></a>
  2. ##### [2]注册Filter类
  3. ```xml
  4. <filter>
  5. <filter-name>LoginFilter</filter-name>
  6. <filter-class>com.atguigu.bookstore.filter.LoginFilter</filter-class>
  7. </filter>
  8. <filter-mapping>
  9. <filter-name>LoginFilter</filter-name>
  10. <url-pattern>/protected/*</url-pattern>
  11. </filter-mapping>

第二节 结账

1、创建订单模型

①物理建模

[1]t_order表
  1. CREATE TABLE t_order(
  2. order_id INT PRIMARY KEY AUTO_INCREMENT,
  3. order_sequence VARCHAR(200),
  4. create_time VARCHAR(100),
  5. total_count INT,
  6. total_amount DOUBLE,
  7. order_status INT,
  8. user_id INT
  9. );
字段名 字段作用
order_id 主键
order_sequence 订单号
create_time 订单创建时间
total_count 订单的总数量
total_amount 订单的总金额
order_status 订单的状态
user_id 下单的用户的id
  • 虽然order_sequence也是一个不重复的数值,但是不使用它作为主键。数据库表的主键要使用没有业务功能的字段来担任。
  • 订单的状态
    • 待支付(书城项目中暂不考虑)
    • 已支付,待发货:0
    • 已发货:1
    • 确认收货:2
    • 发起退款或退货(书城项目中暂不考虑)
  • 用户id
    • 从逻辑和表结构的角度来说,这其实是一个外键。
    • 但是开发过程中建议先不要加外键约束:因为开发过程中数据尚不完整,加了外键约束开发过程中使用测试数据非常不方便,建议项目预发布时添加外键约束测试。
      [2]t_order_item表
      1. CREATE TABLE t_order_item(
      2. item_id INT PRIMARY KEY AUTO_INCREMENT,
      3. book_name VARCHAR(20),
      4. price DOUBLE,
      5. img_path VARCHAR(50),
      6. item_count INT,
      7. item_amount DOUBLE,
      8. order_id VARCHAR(20)
      9. );
      | 字段名称 | 字段作用 | | —- | —- | | item_id | 主键 | | book_name | 书名 | | price | 单价 | | item_count | 当前订单项的数量 | | item_amount | 当前订单项的金额 | | order_id | 当前订单项关联的订单表的主键 |

说明:book_name、author、price这三个字段其实属于t_book表,我们把它们加入到t_order_item表中,其实并不符合数据库设计三大范式。这里做不符合规范的操作的原因是:将这几个字段加入当前表就不必在显示数据时和t_book表做关联查询,提高查询的效率,这是一种变通的做法。

②逻辑模型

[1]Order类
  1. public class Order {
  2. private Integer orderId;
  3. private String orderSequence;
  4. private String createTime;
  5. private Integer totalCount;
  6. private Double totalAmount;
  7. private Integer orderStatus = 0;
  8. private Integer userId;

[2]OrdrItem类
  1. public class OrderItem {
  2. private Integer itemId;
  3. private String bookName;
  4. private Double price;
  5. private String imgPath;
  6. private Integer itemCount;
  7. private Double itemAmount;
  8. private Integer orderId;

2、创建组件

①持久化层

image.png

②业务逻辑层

image.png

③表述层

image.png

3、结账功能

①具体操作清单

  • 创建订单对象
  • 给订单对象填充数据
    • 生成订单号
    • 生成订单的时间
    • 从购物车迁移总数量和总金额
    • 从已登录的User对象中获取userId并设置到订单对象中
  • 将订单对象保存到数据库中
  • 获取订单对象在数据库中自增主键的值
  • 根据购物车中的CartItem集合逐个创建OrderItem对象
    • 每个OrderItem对象对应的orderId属性都使用前面获取的订单数据的自增主键的值
  • 把OrderItem对象的集合保存到数据库
  • 每一个item对应的图书增加销量
  • 每一个item对应的图书减少库存
  • 清空购物车

    ②思路

    image.png

    ③代码实现

    [1]购物车页面结账超链接

    cart.html

    1. <a class="pay" href="protected/OrderClientServlet?method=checkout">去结账</a>

    [2]OrderClientServlet.checkout()
    1. protected void checkout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    2. // 1.获取HttpSession对象
    3. HttpSession session = request.getSession();
    4. // 2.获取购物车对象
    5. Cart cart = (Cart) session.getAttribute("cart");
    6. if (cart == null) {
    7. String viewName = "cart/cart";
    8. processTemplate(viewName, request, response);
    9. return ;
    10. }
    11. // 3.获取已登录的用户对象
    12. User user = (User) session.getAttribute("user");
    13. // 4.调用Service方法执行结账的业务逻辑
    14. String orderSequence = orderService.checkout(cart, user);
    15. // 5.清空购物车
    16. session.removeAttribute("cart");
    17. // 6.将订单号存入请求域
    18. request.setAttribute("orderSequence", orderSequence);
    19. // 7.将页面跳转到下单成功页面
    20. String viewName = "cart/checkout";
    21. processTemplate(viewName, request, response);
    22. }

    [3]OrderService.checkout()
    1. @Override
    2. public String checkout(Cart cart, User user) {
    3. // 从User对象中获取userId
    4. Integer userId = user.getUserId();
    5. // 创建订单对象
    6. Order order = new Order();
    7. // 给订单对象填充数据
    8. // 生成订单号=系统时间戳
    9. String orderSequence = System.currentTimeMillis() + "_" + userId;
    10. // 生成订单的时间
    11. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    12. String createTime = simpleDateFormat.format(new Date());
    13. // 从购物车迁移总数量和总金额
    14. Integer totalCount = cart.getTotalCount();
    15. Double totalAmount = cart.getTotalAmount();
    16. order.setOrderSequence(orderSequence);
    17. order.setCreateTime(createTime);
    18. order.setTotalCount(totalCount);
    19. order.setTotalAmount(totalAmount);
    20. // 将订单对象保存到数据库中
    21. // ※说明:这里对insertOrder()方法的要求是获取自增的主键并将自增主键的值设置到Order对象的orderId属性中
    22. orderDao.insertOrder(order);
    23. // 获取订单对象在数据库中自增主键的值
    24. Integer orderId = order.getOrderId();
    25. // 根据购物车中的CartItem集合逐个创建OrderItem对象
    26. Map<String, CartItem> cartItemMap = cart.getCartItemMap();
    27. Collection<CartItem> cartItems = cartItemMap.values();
    28. List<CartItem> cartItemList = new ArrayList<>(cartItems);
    29. // 为了便于批量保存OrderItem,创建Object[][]
    30. // 二维数组第一维:SQL语句的数量
    31. // 二维数组第二维:SQL语句中参数的数量
    32. Object[][] saveOrderItemParamArr = new Object[cartItems.size()][6];
    33. // 为了便于批量更新Book,创建Object[][]
    34. Object[][] updateBookParamArr = new Object[cartItems.size()][3];
    35. for (int i = 0;i < cartItemList.size(); i++) {
    36. CartItem cartItem = cartItemList.get(i);
    37. // 为保存OrderItem创建Object[]
    38. Object[] orderItemParam = new Object[6];
    39. // book_name,price,img_path,item_count,item_amount,order_id
    40. orderItemParam[0] = cartItem.getBookName();
    41. orderItemParam[1] = cartItem.getPrice();
    42. orderItemParam[2] = cartItem.getImgPath();
    43. orderItemParam[3] = cartItem.getCount();
    44. orderItemParam[4] = cartItem.getAmount();
    45. orderItemParam[5] = orderId;
    46. // 将一维数组存入二维数组中
    47. saveOrderItemParamArr[i] = orderItemParam;
    48. // 创建数组用于保存更新Book数据的信息
    49. String[] bookUpdateInfoArr = new String[3];
    50. // 增加的销量
    51. bookUpdateInfoArr[0] = cartItem.getCount() + "";
    52. // 减少的库存
    53. bookUpdateInfoArr[1] = cartItem.getCount() + "";
    54. // bookId
    55. bookUpdateInfoArr[2] = cartItem.getBookId();
    56. // 将数组存入List集合
    57. updateBookParamArr[i] = bookUpdateInfoArr;
    58. }
    59. // 把OrderItem对象的集合保存到数据库:批量操作
    60. orderItemDao.insertOrderItemArr(saveOrderItemParamArr);
    61. // 使用bookUpdateInfoList对图书数据的表执行批量更新操作
    62. bookDao.updateBookByParamArr(updateBookParamArr);
    63. // 返回订单号
    64. return orderSequence;
    65. }

    [4]orderDao.insertOrder(order)

    ```java @Override public void insertOrder(Order order) {

    // ※DBUtils没有封装获取自增主键的方法,需要我们使用原生的JDBC来完成 // 1.获取数据库连接 Connection connection = JDBCUtils.getConnection();

    // 2.创建PreparedStatement对象 String sql = “INSERT INTO t_order(order_sequence,create_time,total_count,total_amount,order_status,user_id) VALUES(?,?,?,?,?,?)”;

    try {

    1. // ①创建PreparedStatement对象,指明需要自增的主键
    2. PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    3. // ②给PreparedStatement对象设置SQL语句的参数
    4. preparedStatement.setString(1, order.getOrderSequence());
    5. preparedStatement.setString(2, order.getCreateTime());
    6. preparedStatement.setInt(3, order.getTotalCount());
    7. preparedStatement.setDouble(4, order.getTotalAmount());
    8. preparedStatement.setInt(5, order.getOrderStatus());
    9. preparedStatement.setInt(6, order.getUserId());
    10. // ③执行更新
    11. preparedStatement.executeUpdate();
    12. // ④获取封装了自增主键的结果集
    13. ResultSet generatedKeysResultSet = preparedStatement.getGeneratedKeys();
    14. // ⑤解析结果集
    15. if (generatedKeysResultSet.next()) {
    16. int orderId = generatedKeysResultSet.getInt(1);
    17. order.setOrderId(orderId);
    18. }

    } catch (SQLException e) {

    1. e.printStackTrace();
    2. throw new RuntimeException(e);

    } finally {

    1. JDBCUtils.releaseConnection(connection);

    }

}

  1. <a name="tLslB"></a>
  2. ##### [5]BaseDao.batchUpdate()
  3. ```java
  4. /**
  5. * 通用的批量增删改方法
  6. * @param sql
  7. * @param params 执行批量操作的二维数组
  8. * 每一条SQL语句的参数是一维数组
  9. * 多条SQL语句的参数就是二维数组
  10. * @return 每一条SQL语句返回的受影响的行数
  11. */
  12. public int[] batchUpdate(String sql, Object[][] params) {
  13. Connection connection = JDBCUtils.getConnection();
  14. int[] rowCountArr = null;
  15. try {
  16. rowCountArr = queryRunner.batch(connection, sql, params);
  17. } catch (SQLException e) {
  18. e.printStackTrace();
  19. throw new RuntimeException(e);
  20. } finally {
  21. JDBCUtils.releaseConnection(connection);
  22. }
  23. return rowCountArr;
  24. }

[6]orderItemDao.insertOrderItemArr(saveOrderItemParamArr)
  1. @Override
  2. public void insertOrderItemArr(Object[][] saveOrderItemParamArr) {
  3. String sql = "INSERT INTO t_order_item(book_name,price,img_path,item_count,item_amount,order_id) VALUES(?,?,?,?,?,?)";
  4. super.batchUpdate(sql, saveOrderItemParamArr);
  5. }

[7]bookDao.updateBookByParamArr(updateBookParamArr)
  1. @Override
  2. public void updateBookByParamArr(Object[][] updateBookParamArr) {
  3. String sql = "update t_book set sales=sales+?,stock=stock-? where book_id=?";
  4. super.batchUpdate(sql, updateBookParamArr);
  5. }

第三节 结账过程中使用事务(重要)

1、事务回顾

①ACID属性

  • A:原子性 事务中包含的数据库操作缺一不可,整个事务是不可再分的。
  • C:一致性 事务执行之前,数据库中的数据整体是正确的;事务执行之后,数据库中的数据整体仍然是正确的。
    • 事务执行成功:提交(commit)
    • 事务执行失败:回滚(rollback)
  • I:隔离性 数据库系统同时执行很多事务时,各个事务之间基于不同隔离级别能够在一定程度上做到互不干扰。简单说就是:事务在并发执行过程中彼此隔离。
  • D:持久性 事务一旦提交,就永久保存到数据库中,不可撤销。

    ②隔离级别

    [1]并发问题
    | 并发问题 | 问题描述 | | —- | —- | | 脏读 | 当前事务读取了其他事务尚未提交的修改
    如果那个事务回滚,那么当前事务读取到的修改就是错误的数据 | | 不可重复读 | 当前事务读取同一个数据,第一次和第二次不一致 | | 幻读 | 当前事务在执行过程中,数据库表增减或减少了一些记录,感觉像是出现了幻觉 |

[2]隔离级别
隔离级别 描述 能解决的并发问题
读未提交 允许当前事务读取其他事务尚未提交的修改 啥问题也解决不了
读已提交 允许当前事务读取其他事务已经提交的修改 脏读
可重复读 当前事务执行时锁定当前记录,不允许其他事务操作 脏读、不可重复读
串行化 当前事务执行时锁定当前表,不允许其他事务操作 脏读、不可重复读、幻读

2、JDBC事务控制

①同一个数据库连接

image.png

②关闭事务的自动提交

  1. connection.setAutoCommit(false);

③提交事务

  1. connection.commit();

④回滚事务

  1. connection.rollBack();

⑤事务整体的代码块

  1. try{
  2. // 关闭事务的自动提交
  3. connection.setAutoCommit(false);
  4. // 事务中包含的所有数据库操作
  5. // 提交事务
  6. connection.commit();
  7. } catch(Excetion e) {
  8. // 回滚事务
  9. connection.rollBack();
  10. } finally {
  11. // 释放数据库连接
  12. connection.close();
  13. }

3、将事务对接到书城项目中

①三层架构中事务要对接的位置

从逻辑上来说,一个事务对应一个业务方法(Service层的一个方法)。
image.png

②假想

每一个Service方法内部,都套用了事务操作所需要的try…catch…finally块。

③假想代码的缺陷

  • 会出现大量的冗余代码:我们希望能够抽取出来,只写一次
  • 对核心业务功能是一种干扰:我们希望能够在编写业务逻辑代码时专注于业务本身,而不必为辅助性质的套路代码分心
  • 将持久化层对数据库的操作写入业务逻辑层,是对业务逻辑层的一种污染,导致持久化层和业务逻辑层耦合在一起

    ④事务代码抽取

  • 只要是Filter拦截到的请求都会从doFilter()方法经过

  • chain.doFilter(req, resp);可以包裹住将来要执行的所有方法
  • 事务操作的try…catch…finally块只要把chain.doFilter(req, resp)包住,就能够包住将来要执行的所有方法 ```java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

    try{

    1. // 关闭事务的自动提交
    2. connection.setAutoCommit(false);
    3. // 『事务中包含的所有数据库操作』就在chain.doFilter(req, resp);将来要调用的方法中
    4. // 所以用事务的try...catch...finally块包住chain.doFilter(req, resp);
    5. // 就能让所有事务方法都『享受』到事务功能的『服务』。
    6. // 所谓框架其实就是把常用的『套路代码』抽取出来,为大家服务,我们享受框架服务提高开发效率。
    7. chain.doFilter(req, resp);
    8. // 提交事务
    9. connection.commit();

    }catch(Excetion e){

    1. // 回滚事务
    2. connection.rollBack();

    }finally{

    1. // 释放数据库连接
    2. connection.close();

    }

}

  1. <a name="Q6uie"></a>
  2. #### ⑤在Filter中获取数据库连接
  3. ```java
  4. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  5. try{
  6. // 获取数据库连接
  7. Connection connection = JDBCUtils.getConnection();
  8. // 关闭事务的自动提交
  9. connection.setAutoCommit(false);
  10. // 『事务中包含的所有数据库操作』就在chain.doFilter(req, resp);将来要调用的方法中
  11. // 所以用事务的try...catch...finally块包住chain.doFilter(req, resp);
  12. // 就能让所有事务方法都『享受』到事务功能的『服务』。
  13. // 所谓框架其实就是把常用的『套路代码』抽取出来,为大家服务,我们享受框架服务提高开发效率。
  14. chain.doFilter(req, resp);
  15. // 提交事务
  16. connection.commit();
  17. }catch(Excetion e){
  18. // 回滚事务
  19. connection.rollBack();
  20. }finally{
  21. // 释放数据库连接
  22. connection.close();
  23. }
  24. }

⑥保证所有数据库操作使用同一个连接

『重要发现』:在书城项目中所有执行SQL语句的代码都是通过JDBCUtils.getConnection()方法获取数据库连接。所以我们可以通过重构JDBCUtils.getConnection()方法实现:所有数据库操作使用同一个连接。
image.png

[1]从数据源中只拿出一个

为了保证各个需要Connection对象的地方使用的都是同一个对象,我们从数据源中只获取一个Connection。不是说整个项目只用一个Connection,而是说调用JDBCUtils.getConnection()方法时,只使用一个。所以落实到代码上就是:每次调用getConnection()方法时先检查是否已经拿过了,拿过就给旧的,没拿过给新的。

[2]公共区域

为了保证各个方法中需要Connection对象时都能拿到同一个对象,需要做到:将唯一的对象存入一个大家都能接触到的地方。
image.png
结论:使用线程本地化技术实现Connection对象从上到下传递。

⑦线程本地化

[1]确认同一个线程

在从Filter、Servlet、Service一直到Dao运行的过程中,我们始终都没有做类似new Thread().start()这样开启新线程的操作,所以整个过程在同一个线程中。

[2]一条小河

image.png

[3]一个线程

image.png

[4]代码

java.lang.ThreadLocal的set()方法:

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

java.lang.TheadLocal的get()方法:

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

所以TheadLocal的基本原理是:它在内部维护了一个Map,需要存入数据时,就以this为键,要存入的数据为值,存入Map。需要取出数据时,就以this为键,从Map中取出数据。

[5]结论

如果我们需要将数据在整个项目中按照从上到下的方式传递,但是又没法通过方法的参数来实现,这时使用线程本地化技术是一个非常好的选择。

⑧异常向上抛出的线路

image.png
上图中标记颜色的位置都是有try…catch块的代码,需要逐个检查一下,catch块捕获的异常是否转换为运行时异常又再次抛出。
如果没有抛出,异常就不会传递到Filter中,TransactionFilter就会认为代码执行过程中没有发生问题,从而提交事务,但是实际上应该回滚。下面是一个例子:

  1. /**
  2. * 通用的批量增删改方法
  3. * @param sql
  4. * @param params 执行批量操作的二维数组
  5. * 每一条SQL语句的参数是一维数组
  6. * 多条SQL语句的参数就是二维数组
  7. * @return 每一条SQL语句返回的受影响的行数
  8. */
  9. public int[] batchUpdate(String sql, Object[][] params) {
  10. Connection connection = JDBCUtils.getConnection();
  11. int[] rowCountArr = null;
  12. try {
  13. rowCountArr = queryRunner.batch(connection, sql, params);
  14. } catch (SQLException e) {
  15. e.printStackTrace();
  16. throw new RuntimeException(e);
  17. } finally {
  18. JDBCUtils.releaseConnection(connection);
  19. }
  20. return rowCountArr;
  21. }

4、代码实现

①重构JDBCUtils类

  • 要点1:将ThreadLocal对象声明为静态成员变量
  • 要点2:重构获取数据库连接的方法
  • 要点3:重构释放数据库连接的方法

    1. /**
    2. * 功能1:创建数据源对象
    3. * 功能2:获取数据库连接并绑定到当前线程上
    4. * 功能3:释放数据库连接并从当前线程移除
    5. */
    6. public class JDBCUtils {
    7. // 将数据源对象设置为静态属性,保证大对象的单一实例
    8. private static DataSource dataSource;
    9. // 将ThreadLocal对象设置为静态成员变量,保证以此为键时从Map中取值能够取到同一个值
    10. private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    11. static {
    12. // 1.创建一个用于存储外部属性文件信息的Properties对象
    13. Properties properties = new Properties();
    14. // 2.使用当前类的类加载器加载外部属性文件:jdbc.properties
    15. InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
    16. try {
    17. // 3.将外部属性文件jdbc.properties中的数据加载到properties对象中
    18. properties.load(inputStream);
    19. // 4.创建数据源对象
    20. dataSource = DruidDataSourceFactory.createDataSource(properties);
    21. } catch (Exception e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. /**
    26. * 从数据源中获取数据库连接
    27. * @return 数据库连接对象
    28. */
    29. public static Connection getConnection() {
    30. // 1.尝试从当前线程获取Connection对象
    31. Connection connection = threadLocal.get();
    32. if (connection == null) {
    33. try {
    34. // 2.如果从当前线程上没有获取到Connection对象那么从数据源获取
    35. connection = dataSource.getConnection();
    36. // 3.将Connection对象绑定到当前线程
    37. threadLocal.set(connection);
    38. } catch (SQLException e) {
    39. e.printStackTrace();
    40. throw new RuntimeException(e);
    41. }
    42. }
    43. // 4.返回Connection对象
    44. return connection;
    45. }
    46. /**
    47. * 释放数据库连接
    48. * @param connection 要执行释放操作的连接对象
    49. */
    50. public static void releaseConnection(Connection connection) {
    51. if (connection != null) {
    52. try {
    53. connection.close();
    54. // 将Connection对象从当前线程移除
    55. threadLocal.remove();
    56. } catch (SQLException e) {
    57. e.printStackTrace();
    58. throw new RuntimeException(e);
    59. }
    60. }
    61. }
    62. }

    ②重构BaseDao

  • 要点:去除释放数据库连接的操作(转移到TransactionFilter中) ```java /**

    • 各个具体Dao类的基类,泛型T对应具体实体类类型
    • @param */ public class BaseDao {

      private QueryRunner queryRunner = new QueryRunner();

      /**

      • 通用的批量增删改方法
      • @param sql
      • @param params 执行批量操作的二维数组
      • 每一条SQL语句的参数是一维数组
      • 多条SQL语句的参数就是二维数组
      • @return 每一条SQL语句返回的受影响的行数 */ public int[] batchUpdate(String sql, Object[][] params) {

        Connection connection = JDBCUtils.getConnection();

        int[] rowCountArr = null;

        try {

        1. rowCountArr = queryRunner.batch(connection, sql, params);

        } catch (SQLException e) {

        1. e.printStackTrace();
        2. throw new RuntimeException(e);

        }/* finally {

        1. JDBCUtils.releaseConnection(connection);

        }*/

        return rowCountArr;

      }

      /**

      • 通用的增删改方法
      • @param sql 要执行的SQL语句
      • @param param 为SQL语句准备好的参数
      • @return 受影响的行数 */ public int update(String sql, Object … param) {

        int updatedRowCount = 0;

        Connection connection = JDBCUtils.getConnection();

        try {

        1. updatedRowCount = queryRunner.update(connection, sql, param);

        } // 为了让上层方法调用方便,将编译时异常捕获 catch (SQLException e) {

        1. e.printStackTrace();
        2. // 为了不掩盖问题,将编译时异常封装为运行时异常抛出
        3. throw new RuntimeException(e);

        }/* finally {

        1. // 关闭数据库连接
        2. JDBCUtils.releaseConnection(connection);

        }*/

        return updatedRowCount;

      }

      /**

      • 查询单个对象
      • @param clazz 单个对象所对应的实体类类型
      • @param sql 查询单个对象所需要的SQL语句
      • @param param SQL语句的参数
      • @return 查询到的单个对象 */ public T getBean(Class clazz, String sql, Object … param) {

        Connection connection = JDBCUtils.getConnection();

        T t = null;

        try {

        1. t = queryRunner.query(connection, sql, new BeanHandler<>(clazz), param);

        } catch (SQLException e) {

        1. e.printStackTrace();
        2. throw new RuntimeException(e);

        }/* finally {

        1. // 关闭数据库连接
        2. JDBCUtils.releaseConnection(connection);

        }*/

        return t; }

      /**

      • 查询集合对象
      • @param clazz 集合中单个对象所对应的实体类类型
      • @param sql 查询集合所需要的SQL语句
      • @param param SQL语句的参数
      • @return 查询到的集合对象 */ public List getBeanList(Class clazz, String sql, Object … param) {

        Connection connection = JDBCUtils.getConnection();

        List list = null;

        try {

        1. list = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), param);

        } catch (SQLException e) {

        1. e.printStackTrace();
        2. throw new RuntimeException(e);

        }/* finally {

        1. // 关闭数据库连接
        2. JDBCUtils.releaseConnection(connection);

        }*/

        return list; }

}

  1. **注意**:OrderDaoinsertOrder()方法也要去掉关闭数据库连接的操作。
  2. ```java
  3. @Override
  4. public void insertOrder(Order order) {
  5. // ※DBUtils没有封装获取自增主键的方法,需要我们使用原生的JDBC来完成
  6. // 1.获取数据库连接
  7. Connection connection = JDBCUtils.getConnection();
  8. // 2.创建PreparedStatement对象
  9. String sql = "INS222ERT INTO t_order(order_sequence,create_time,total_count,total_amount,order_status,user_id) VALUES(?,?,?,?,?,?)";
  10. try {
  11. // ①创建PreparedStatement对象,指明需要自增的主键
  12. PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
  13. // ②给PreparedStatement对象设置SQL语句的参数
  14. preparedStatement.setString(1, order.getOrderSequence());
  15. preparedStatement.setString(2, order.getCreateTime());
  16. preparedStatement.setInt(3, order.getTotalCount());
  17. preparedStatement.setDouble(4, order.getTotalAmount());
  18. preparedStatement.setInt(5, order.getOrderStatus());
  19. preparedStatement.setInt(6, order.getUserId());
  20. // ③执行更新
  21. preparedStatement.executeUpdate();
  22. // ④获取封装了自增主键的结果集
  23. ResultSet generatedKeysResultSet = preparedStatement.getGeneratedKeys();
  24. // ⑤解析结果集
  25. if (generatedKeysResultSet.next()) {
  26. int orderId = generatedKeysResultSet.getInt(1);
  27. order.setOrderId(orderId);
  28. }
  29. } catch (SQLException e) {
  30. e.printStackTrace();
  31. throw new RuntimeException(e);
  32. } /*finally {
  33. JDBCUtils.releaseConnection(connection);
  34. }*/
  35. }

③创建一个用于显示通用错误信息的页面

[1]创建页面

这个页面可以从login_success.html复制过来
image.png

[2]创建Servlet跳转到页面

image.png

  1. protected void showSystemError(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. String viewName = "error";
  3. processTemplate(viewName, request, response);
  4. }

④创建TransactionFilter

image.png

  1. <filter>
  2. <filter-name>TransactionFilter</filter-name>
  3. <filter-class>com.atguigu.bookstore.filter.TransactionFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>TransactionFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

Java代码如下:

  1. public class TransactionFilter implements Filter {
  2. private static final Set<String> PUBLIC_STATIC_RESOURCE_EXT_NAME_SET = new HashSet<>();
  3. static {
  4. PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.add(".png");
  5. PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.add(".css");
  6. PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.add(".js");
  7. PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.add(".jpg");
  8. PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.add(".gif");
  9. }
  10. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  11. // 排除掉静态资源,它们和数据库操作没有关系
  12. // 1.给请求和响应对象转换类型
  13. HttpServletRequest request = (HttpServletRequest) req;
  14. HttpServletResponse response = (HttpServletResponse) resp;
  15. // 2.获取当前请求的ServletPath
  16. String servletPath = request.getServletPath();
  17. // 3.检查servletPath中是否包含“.”
  18. if (servletPath.contains(".")) {
  19. int index = servletPath.lastIndexOf(".");
  20. String extensionName = servletPath.substring(index);
  21. if (PUBLIC_STATIC_RESOURCE_EXT_NAME_SET.contains(extensionName)) {
  22. chain.doFilter(request, response);
  23. return ;
  24. }
  25. }
  26. // 执行事务操作
  27. // 1.获取数据库连接
  28. Connection connection = JDBCUtils.getConnection();
  29. // 2.使用try...catch...finally块管理事务
  30. try{
  31. // 3.关闭事务的自动提交
  32. connection.setAutoCommit(false);
  33. // 4.尝试执行目标代码
  34. chain.doFilter(request, response);
  35. // 5.如果上面的操作没有抛出异常
  36. connection.commit();
  37. }catch (Exception e){
  38. // 6.如果上面的操作抛出了异常
  39. try {
  40. connection.rollback();
  41. } catch (SQLException e1) {
  42. e1.printStackTrace();
  43. }
  44. // 7.捕获到异常后,跳转到专门的页面显示提示消息
  45. String message = e.getMessage();
  46. request.setAttribute("error", message);
  47. request.getRequestDispatcher("/ErrorServlet?method=showSystemError").forward(request, response);
  48. }finally {
  49. // 8.不管前面操作是成功还是失败,到这里都要释放数据库连接
  50. JDBCUtils.releaseConnection(connection);
  51. }
  52. }
  53. public void init(FilterConfig config) throws ServletException {}
  54. public void destroy() {}
  55. }

第四节 后台查看订单列表&发货

第五节 前台查看订单列表&确认收货

书城项目第六阶段

第一节 注册页面用户名唯一性检查优化

1、准备工作

  • 创建module
  • 迁移代码

    2、加入Ajax开发环境

    ①前端所需axios库

    image.png

    ②后端所需Gson库

    image.png

    3、封装AjaxCommonsResult

    ①模型的作用

    在整个项目中,凡是涉及到给Ajax请求返回响应,我们都封装到AjaxCommonsResult类型中。

    ②模型的代码

    1. public class AjaxCommonResult<T> {
    2. public static final String SUCCESS = "SUCCESS";
    3. public static final String FAILED = "FAILED";
    4. private String result;
    5. private String message;
    6. private T data;

    各个属性的含义:

属性名 含义
SUCCESS 代表服务器端处理请求成功
FAILED 代表服务器端处理请求失败
result 服务器端处理请求的结果,取值在SUCCESS和FAILED二者中选择一个
message 失败消息
data 针对查询操作返回的数据

③模型的好处

  • 作为整个团队开发过程中,前后端交互时使用的统一的数据格式
  • 有利于团队成员之间的协助,提高开发效率

    4、功能实现

    ①定位功能的位置

    在用户输入用户名之后,立即检查这个用户名是否可用。

    ②思路

    [1]给用户名输入框绑定的事件类型

    结论:不能在针对username设定的watch中发送Ajax请求。
    原因:服务器端响应的速度跟不上用户输入的速度,而且服务器端异步返回响应数据,无法保证和用户输入的顺序完全一致。此时有下面几点问题:

  • 给服务器增加不必要的压力

  • 用户输入的数据在输入过程中是不断发生变化的
  • 响应数据和输入顺序不对应,会发生错乱

解决办法:绑定的事件类型使用『值改变』事件。

[2]流程图

image.png

③代码实现

[0]在当前页面引入axios库文件
  1. <script src="static/script/axios.js" type="text/javascript" charset="utf-8"></script>

[1]给用户名输入框绑定值改变事件
  1. <input v-model:value="username" @change="usernameUniqueCheck" type="text" name="username" placeholder="请输入用户名" />

[2]JavaScript代码
  1. var registerApp = new Vue({
  2. "el":"#registerForm",
  3. "data":{
  4. "username":"[[${param.username}]]",
  5. "password":"",
  6. "passwordConfirm":"",
  7. "email":"[[${param.email}]]",
  8. "code":"",
  9. "usernameCheckMessage":""
  10. },
  11. "watch":{……},
  12. "methods":{
  13. ……,
  14. ……,
  15. "usernameUniqueCheck":function () {
  16. // 获取用户在文本框中输入的数据
  17. var username = this.username;
  18. // 发送Ajax请求执行检查
  19. axios({
  20. "method":"post",
  21. "url":"UserServlet",
  22. "params":{
  23. "method":"checkUsernameUnique",
  24. "username":username
  25. }
  26. }).then(function (response) {
  27. // 1.从响应数据中获取请求处理结果
  28. var result = response.data.result;
  29. // 2.判断result的值
  30. if (result == "SUCCESS") {
  31. // 3.用户名可用
  32. // 注意:现在我们在then()的回调函数中,this已经不再指向Vue对象了
  33. // 所以,我们通过Vue对象的变量名来访问Vue对象
  34. registerApp.usernameCheckMessage = "用户名可用";
  35. } else {
  36. // 4.用户名不可用
  37. registerApp.usernameCheckMessage = response.data.message;
  38. }
  39. }).catch(function (error) {
  40. console.log(error);
  41. });
  42. }
  43. }
  44. });

[3]UserServlet
  1. protected void checkUsernameUnique(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. AjaxCommonResult<String> ajaxResult = null;
  3. // 1.从请求参数中获取用户名
  4. String username = request.getParameter("username");
  5. try {
  6. // 2.调用Service方法检查用户名是否被占用
  7. userService.checkUsernameUnique(username);
  8. // 3.按照检测成功的结果创建AjaxCommonResult对象
  9. ajaxResult = new AjaxCommonResult<>(AjaxCommonResult.SUCCESS, null, null);
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. // 4.按照检测失败的结果创建AjaxCommonResult对象
  13. ajaxResult = new AjaxCommonResult<>(AjaxCommonResult.FAILED, e.getMessage(), null);
  14. }
  15. // 5.根据ajaxResult对象返回响应数据
  16. // ①创建Gson对象
  17. Gson gson = new Gson();
  18. // ②执行JSON数据转换
  19. String json = gson.toJson(ajaxResult);
  20. // ③设置响应体内容类型
  21. response.setContentType("application/json;charset=UTF-8");
  22. response.getWriter().write(json);
  23. }

[4]UserService
  1. @Override
  2. public void checkUsernameUnique(String username) {
  3. User user = userDao.selectUserByName(username);
  4. if (user != null) {
  5. throw new RuntimeException("用户名已经被占用");
  6. }
  7. }

第二节 加入购物车

1、思路

image.png

2、代码实现

①加入layer弹层组件

image.png

  1. <script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
  2. <script type="text/javascript" src="static/layer/layer.js"></script>

②顶层bar绑定Vue对象

Thymeleaf在服务器端渲染的过程中将购物车总数量计算得到,通过表达式设置写入JavaScript代码,作为Vue对象的初始值。然后由Vue对象通过v-show判断是否显示数量标签。

[1]在HTML标签上标记id

由于要考虑是否登录的情况,所以id加到了两种情况外层的div

  1. <div id="topBarApp" class="w">
  2. <div class="topbar-left">
  3. <i>送至:</i>
  4. <i>北京</i>
  5. <i class="iconfont icon-ai-arrow-down"></i>
  6. </div>
  7. <div class="topbar-right" th:if="${session.user == null}">
  8. <a href="UserServlet?method=toLoginPage" class="login">登录</a>
  9. <a href="UserServlet?method=toRegisterPage" class="register">注册</a>
  10. <a href="protected/CartServlet?method=showCart" class="cart iconfont icon-gouwuche">购物车</a>
  11. <a href="AdminServlet?method=toPortalPage" class="admin">后台管理</a>
  12. </div>
  13. <!-- 登录后风格-->
  14. <div class="topbar-right" th:if="${session.user != null}">
  15. <span>欢迎你<b th:text="${session.user.userName}">张总</b></span>
  16. <a href="#" class="register">注销</a>
  17. <a href="protected/CartServlet?method=showCart" class="cart iconfont icon-gouwuche">
  18. 购物车
  19. <div class="cart-num" v-show="totalCount > 0">{{totalCount}}</div>
  20. </a>
  21. <a href="pages/manager/book_manager.html" class="admin">后台管理</a>
  22. </div>
  23. </div>

[2]创建Vue对象
  1. // topBarApp对象的totalCount属性的初始值是Thymeleaf在服务器端运算出来用表达式设置的
  2. var topBarApp = new Vue({
  3. "el": "#topBarApp",
  4. "data": {
  5. "totalCount": [[${(session.cart == null)?"0":session.cart.totalCount}]]
  6. }
  7. });

③图书列表div绑定Vue对象

[1]在HTML标签上标记id

目的是为了便于创建Vue对象

  1. <div id="bookListApp" class="list-content" th:if="${not #lists.isEmpty(bookList)}">
  2. <div class="list-item" th:each="book : ${bookList}">
  3. <img th:src="${book.imgPath}" src="static/uploads/huozhe.jpg" alt="">
  4. <p>书名:<span th:text="${book.bookName}">活着</span></p>
  5. <p>作者:<span th:text="${book.author}">余华</span></p>
  6. <p>价格:¥<span th:text="${book.price}">66.6</span></p>
  7. <p>销量:<span th:text="${book.sales}">230</span></p>
  8. <p>库存:<span th:text="${book.stock}">1000</span></p>
  9. <!--<button>加入购物车</button>-->
  10. <a th:href="@{/protected/CartServlet(method=addCart,bookId=${book.bookId})}">加入购物车</a>
  11. </div>
  12. </div>

[2]在首页引入Vue和axios库文件
  1. <script src="static/script/vue.js" type="text/javascript" charset="utf-8"></script>
  2. <script src="static/script/axios.js" type="text/javascript" charset="utf-8"></script>

[3]创建Vue对象
  1. <script type="text/javascript">
  2. new Vue({
  3. "el":"#bookListApp"
  4. });
  5. </script>

[4]绑定单击响应函数

给加入购物车按钮绑定单击响应函数

  1. <button @click="addToCart">加入购物车</button>

Vue代码:

  1. new Vue({
  2. "el":"#bookListApp",
  3. "methods":{
  4. "addToCart":function () {
  5. }
  6. }
  7. });

[5]将bookId设置到按钮中

为了便于在按钮的单击响应函数中得到bookId的值

  1. <button th:id="${book.bookId}" @click="addToCart">加入购物车</button>

[6]在单击响应函数中发送Ajax请求
  1. new Vue({
  2. "el":"#bookListApp",
  3. "methods":{
  4. "addToCart":function () {
  5. // event:事件对象
  6. // event.target:当前事件操作的对象
  7. // event.target.id:前事件操作的对象的id属性的值
  8. var bookId = event.target.id;
  9. axios({
  10. "method":"post",
  11. "url":"protected/CartServlet",
  12. "params":{
  13. "method":"addCart",
  14. "bookId":bookId
  15. }
  16. }).then(function (response) {
  17. var result = response.data.result;
  18. if (result == "SUCCESS") {
  19. // 给出提示:加入购物车成功
  20. layer.msg("加入购物车成功");
  21. // 从响应数据中获取购物车总数量
  22. // response.data其实就是AjaxCommonResult对象的JSON格式
  23. // response.data.data就是访问AjaxCommonResult对象的data属性
  24. var totalCount = response.data.data;
  25. // 修改页头位置购物车的总数量
  26. topBarApp.totalCount = totalCount;
  27. }else {
  28. // 给出提示:response.data.message
  29. layer.msg(response.data.message);
  30. }
  31. }).catch(function (error) {
  32. console.log(error);
  33. });
  34. }
  35. }
  36. });

④后端代码

CartServlet

  1. protected void addCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1.从请求参数中获取bookId
  3. String bookId = request.getParameter("bookId");
  4. // 2.根据bookId查询图书数据
  5. Book book = bookService.getBookById(bookId);
  6. // 3.获取Session对象
  7. HttpSession session = request.getSession();
  8. // 4.尝试从Session域获取购物车对象
  9. Cart cart = (Cart) session.getAttribute("cart");
  10. // 5.判断Cart对象是否存在
  11. if (cart == null) {
  12. // 6.如果不存在,则创建新的Cart对象
  13. cart = new Cart();
  14. // 7.将新创建的Cart对象存入Session域
  15. session.setAttribute("cart", cart);
  16. }
  17. // 8.添加购物车
  18. cart.addCartItem(book);
  19. // 9.给Ajax返回JSON格式响应
  20. // ①创建AjaxCommonResult对象
  21. AjaxCommonResult<Integer> result = new AjaxCommonResult<>(AjaxCommonResult.SUCCESS, null, cart.getTotalCount());
  22. // ②创建Gson对象
  23. Gson gson = new Gson();
  24. // ③将AjaxCommonResult对象转换为JSON字符串
  25. String json = gson.toJson(result);
  26. // ④设置响应体的内容类型
  27. response.setContentType("application/json;charset=UTF-8");
  28. // ⑤返回响应
  29. response.getWriter().write(json);
  30. }

第三节 显示购物车数据

1、思路

image.png

2、代码实现

①CartServlet增加getCartJSON()方法

[1]Cart模型的局限性

image.png
目前的Cart对象转换为JSON后,没有totalCount、totalAmount这样的属性,Map结构也不如LIst遍历方便。

[2]调整方式

把前端页面需要的属性,存入Map中即可。

[3]方法代码
  1. protected void getCartJSON(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. AjaxCommonResult<Map<String, Object>> result = null;
  3. // 1.获取Session对象
  4. HttpSession session = request.getSession();
  5. // 2.尝试获取购物车对象
  6. Cart cart = (Cart) session.getAttribute("cart");
  7. // 3.检查cart对象是否为空
  8. if (cart == null) {
  9. result = new AjaxCommonResult<>(AjaxCommonResult.FAILED, null, null);
  10. } else {
  11. Map<String, Object> cartJSONMap = new HashMap<>();
  12. cartJSONMap.put("totalCount", cart.getTotalCount());
  13. cartJSONMap.put("totalAmount", cart.getTotalAmount());
  14. cartJSONMap.put("cartItemList", cart.getCartItemMap().values());
  15. result = new AjaxCommonResult<Map<String, Object>>(AjaxCommonResult.SUCCESS, null, cartJSONMap);
  16. }
  17. // 4.将AjaxCommonResult对象转换为JSON作为响应返回
  18. Gson gson = new Gson();
  19. String json = gson.toJson(result);
  20. response.setContentType("application/json;charset=UTF-8");
  21. response.getWriter().write(json);
  22. }

②前端代码

[1]去除Thymeleaf痕迹

将cart.html页面中,由Thymeleaf渲染数据的部分去掉。

[2]使用Vue对象初步接管页面渲染
  1. new Vue({
  2. "el":"#appCart",
  3. "data":{
  4. "cart":"empty"
  5. },

HTML标签:

  1. <tbody v-if="cart == 'empty'">
  2. <tr>
  3. <td colspan="6">购物车还是空空的,赶紧去添加吧!</td>
  4. </tr>
  5. </tbody>

[3]在mounted生命周期环境发Ajax请求

记得加入axios库:

  1. <script src="static/script/axios.js" type="text/javascript" charset="utf-8"></script>
  1. var cartApp = new Vue({
  2. "el":"#appCart",
  3. "data":{
  4. "cart":"empty"
  5. },
  6. "mounted":function () {
  7. axios({
  8. "method":"post",
  9. "url":"protected/CartServlet",
  10. "params":{
  11. "method":"getCartJSON"
  12. }
  13. }).then(function (response) {
  14. // 1.从响应数据中获取请求处理结果
  15. var result = response.data.result;
  16. // 2.检查结果是成功还是失败
  17. if (result == "SUCCESS") {
  18. // 3.获取购物车数据并赋值给Vue对象
  19. cartApp.cart = response.data.data;
  20. console.log(cartApp.cart);
  21. }
  22. }).catch(function (error) {
  23. console.log(error);
  24. });
  25. },
  26. ……

[4]完成Vue页面渲染
  1. <div id="appCart" class="w">
  2. <table>
  3. <thead>
  4. <tr>
  5. <th>图片</th>
  6. <th>商品名称</th>
  7. <th>数量</th>
  8. <th>单价</th>
  9. <th>金额</th>
  10. <th>操作</th>
  11. </tr>
  12. </thead>
  13. <tbody v-if="cart == 'empty'">
  14. <tr>
  15. <td colspan="6">购物车还是空空的,赶紧去添加吧!</td>
  16. </tr>
  17. </tbody>
  18. <tbody v-if="cart != 'empty'">
  19. <tr v-for="cartItem in cart.cartItemList">
  20. <td>
  21. <img :src="cartItem.imgPath" alt=""/>
  22. </td>
  23. <td>{{cartItem.bookName}}</td>
  24. <td>
  25. <input type="hidden" name="bookId" :value="cartItem.bookId" />
  26. <span @click="itemDecrease" class="count">-</span>
  27. <input @change="itemCountChange" class="count-num" type="text" :value="cartItem.count"/>
  28. <span @click="itemIncrease" class="count">+</span>
  29. </td>
  30. <td>{{cartItem.price}}</td>
  31. <td>{{cartItem.amount}}</td>
  32. <td><a @click="removeConfirm" href="protected/CartServlet">删除</a></td>
  33. </tr>
  34. </tbody>
  35. </table>
  36. <div class="footer">
  37. <div class="footer-left">
  38. <a @click="clearCart" href="protected/CartServlet?method=clearCart" class="clear-cart">清空购物车</a>
  39. <a href="index.html">继续购物</a>
  40. </div>
  41. <div class="footer-right">
  42. <div>共<span>{{cart.totalCount}}</span>件商品</div>
  43. <div class="total-price">总金额{{cart.totalAmount}}元</div>
  44. <a class="pay" href="protected/OrderClientServlet?method=checkout">去结账</a>
  45. </div>
  46. </div>
  47. </div>