小案例

案例:黑马旅游注册

目标

使用CSS样式排版出如图所示的效果

效果

在这里插入图片描述

  1. 表格有四行,第一行和第四行跨2列,第一列占30%的宽度,第一行是标题th,第四行是放按钮。使用图片按钮
  2. table居中,宽300px,高180px; 边框solid 1px gray
  3. td的文字对齐居中,字体大小14px
  4. table添加背景,不平铺,图片背景的宽度和高度与table的宽和高一样。
  5. 用户名和密码文本框使用类样式,也可以使用其它选择器。宽150px,高32px,边框用实线,圆角5px,1个宽度,黑色
  6. 使用伪类样式,当鼠标移动到文本框上的时候,变成虚线橙色边框。得到焦点,背景色变成浅黄色
  7. 文本框前面有头像,密码框前面有键盘,左内边距35

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>黑马旅游注册</title>
    <style>
        /*table居中,宽300px,高180px; 边框solid 1px gray*/
        table {
            width: 300px;
            height: 180px;
            margin: auto;
            border: solid 1px gray;
            border-radius: 10px;
            /*table添加背景,不平铺,图片背景的宽度和高度与table的宽和高一样。*/
            background-image: url("img/bg1.jpg");
            background-size: 300px 180px;
        }

        /*td的文字对齐居中,字体大小14px*/
        td {
            text-align: center;
            font-size: 14px;
        }


        /*用户名和密码文本框使用ID样式,也可以使用其它选择器。宽150px,高32px,边框用实线,圆角5px,1个宽度,黑色*/
        #user,#password {
            width: 150px;
            height: 32px;
            border: solid 1px black;
            border-radius: 5px;
        }

        /*使用伪类样式,当鼠标移动到文本框上的时候,变成虚线橙色边框。得到焦点,背景色变成浅黄色*/
        #user:hover,#password:hover {
            border: dashed orange 1px;
        }

        #user:focus,#password:focus {
            background-color: lightyellow;
        }

        /*文本框前面有头像,密码框前面有键盘,左内边距35*/
        #user {
            background-image: url("img/head.png");
            background-repeat: no-repeat;
            /*左内边距为32*/
            padding-left: 32px;
        }

        #password {
            background-image: url("img/keyboard.png");
            background-repeat: no-repeat;
            /*左内边距为32*/
            padding-left: 32px;
        }
    </style>
</head>
<body>
<table>
    <tr>
        <!--跨2列-->
        <th colspan="2">黑马旅游注册</th>
    </tr>
    <tr>
        <td>用户名:</td>
        <td>
            <input type="text" id="user">
        </td>
    </tr>
    <tr>
        <td>密码:</td>
        <td>
            <input type="password" id="password">
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="image" src="img/regbtn.jpg">
        </td>
    </tr>
</table>
</body>
</html>

案例:轮播图

目标

  1. 函数的应用
  2. 定时函数的使用

方法说明

方法描述
document.getElementById(“id”)作用:通过id获取网页上唯一的元素(标签)
参数:ID的值
window.setInterval(“函数名()”,时间)
window.setInterval(函数名,时间)
作用:每隔一段时间调用一次指定的函数
参数1:要调用的函数名
参数2:隔多久调用,单位是毫秒

需求

每过3秒中切换一张图片的效果,一共5张图片,当显示到最后1张的时候,再次显示第1张。

步骤

  1. 创建HTML页面,页面有一个img标签,id为pic,宽度600。
  2. body的背景色为黑色,内容居中。
  3. 五张图片的名字依次是0~4.jpg,放在项目的img文件夹下,图片一开始的src为第0张图片。
  4. 编写函数:changePicture(),使用setInterval()函数,每过3秒调用一次。
  5. 定义全局变量:num=1。
  6. 在changePicture()方法中,设置图片的src属性为img/num.jpg。
  7. 判断num是否等于5,如果等于5,则num=0;否则num++。

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>轮播图案例</title>
    <style>
        body {
            background-color: black;
            text-align: center;
        }
    </style>
</head>
<body>
    <img src="img/0.jpg" width="800" id="game">
</body>
<script type="text/javascript">
    //过2秒调用一次下面的图片
    window.setInterval("changePic()", 2000);

    let num = 1;

    /*
    创建一个函数:换图片
    技术点:修改img元素的src属性即可
     */
    function changePic() {
        //通过id得到一个元素,注:这个方法必须在网页已经加载完元素之后执行。要注意这个代码出现的位置
        let imgObj = document.getElementById("game");
        //alert(imgObj);
        //修改图片元素的src属性:对象名.属性名
        imgObj.src = "img/" + num + ".jpg";
        //文件名数字加1
        num++;
        //如果等于5,设置为0
        if (num == 5) {
            num = 0;
        }
    }

</script>

</html>

效果

在这里插入图片描述

案例:倒计时页面跳转

目标

window对象与location对象的综合案例应用

window中计时器有关的方法

window中的方法作用
setTimeout(函数名, 间隔毫秒数)过一段时间后调用一次指定的函数,单位是毫秒,只调用一次。
返回值是一个整数类型,这就是计时器
clearTimeout(计时器)清除上面的计时器,参数的计时器就是上面方法的返回值
setInterval(函数名, 间隔毫秒数)每隔一段时间调用一次指定的函数,单位是毫秒,不停的调用。
返回值是一个整数类型,这就是计时器
clearInterval(计时器)清除上面的计时器,参数的计时器就是上面方法的返回值

需求

页面上显示一个倒计时5秒的数字,到了5秒以后跳转到另一个页面

效果

在这里插入图片描述

分析

  1. 在页面上创建一个span用于放置变化的数字。
  2. 定义一个全局变量为5,每过1秒调用1次自定义refresh()函数
  3. 编写refresh()函数,修改span中的数字
  4. 判断变量是否为0,如果是0则跳转到新的页面
  5. 否则变量减1。并修改span中的数字

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跳转</title>
</head>
<body>
<span id="counter">5</span>秒以后跳转
<script type="text/javascript">
    /*
    分析:
    1.用到倒计时:window.setInterval()
    2.页面跳转:location.href
    3.修改标签中文字使用属性:innerText
    */
    let count = 5;

    setInterval("countDown()", 1000);  //每过1秒调用一次

    //调用的函数
    function countDown() {
        //1.将数字减1
        count--;
        //2.更新span中数字
        document.getElementById("counter").innerText = count;
        //3.判断数字是否为0
        if (count == 0) {
            //4.如果为0就进行页面跳转
            location.href = "http://www.itcast.cn";
        }
    }

</script>
</body>
</html>

案例:会动的时钟

目标

​ 页面上有两个按钮,一个开始按钮,一个暂停按钮。点开始按钮时间开始走动,点暂停按钮,时间不动。再点开始按钮,时间继续走动。

在这里插入图片描述

步骤

  1. 在页面上创建一个h1标签,用于显示时钟,设置颜色和大小。
  2. 一开始暂停按钮不可用,设置disabled属性,disabled=true表示不可用。
  3. 创建全局的变量,用于保存计时器
  4. 为了防止多次点开始按钮出现bug,点开始按钮以后开始按钮不可用,暂停按钮可用。点暂停按钮以后,暂停按钮不可用,开始按钮可用。设置disabled=true。
  5. 点开始按钮,在方法内部每过1秒中调用匿名函数,在匿名函数内部得到现在的时间,并将得到的时间显示在h1标签内部。
  6. 暂停的按钮在方法内部清除clearInterval()的计时器。

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>会动的时钟</title>
</head>
<body>
<input type="button" value="开始" id="btnBegin">
<!--disabled就元素不可用-->
<input type="button" value="暂停" id="btnPause" disabled="disabled">
<hr/>

<h1 id="clock" style="color: darkgreen"></h1>

<script type="text/javascript">

    let timer;  //计时器

    //开始按钮的点击事件
    document.getElementById("btnBegin").onclick = function() {
        timer = setInterval("showTime()", 1000);  //每过1秒调用1次
        //修改disabled的属性为true,表示不可用,false表示可用
        //让开始按钮不可用,让暂停按钮可用
        document.getElementById("btnBegin").disabled = true;
        document.getElementById("btnPause").disabled = false;
    }

    //暂停按钮的点击事件
    document.getElementById("btnPause").onclick=function () {
        document.getElementById("btnBegin").disabled = false;  //开始按钮可用
        document.getElementById("btnPause").disabled = true;   //暂停按钮不可用
        //让计时器失效
        clearInterval(timer);   //计时器是setInterval()的返回值
    }

    /**
     * 分析:
     * 每过1秒钟输出现在的时间
     * 不能使用document.write方法
     * 应该放在一个标签内部,修改标签内部的内容,使用innerText或innerHTML
     */
    function showTime() {
        //得到现在的时间
        let now = new Date().toLocaleString();
        //在有些浏览器上,会覆盖整个网页,导致这些脚本都没有了
        //document.write(now);
        //在h1中显示时间
        document.getElementById("clock").innerText = now;
    }
</script>
</body>
</html>

案例:省市级联

案例需求

​ 页面上有两个下拉列表框,页面加载的时候访问数据库,得到所有的省份显示在第1个列表框中。当选择不同的省份的时候,加载这个省份下所有的城市显示在第二个下拉列表中。

案例效果

在这里插入图片描述

实现步骤

  1. 创建两个下拉列表,一个显示省份,一个显示城市
  2. 注册网页加载事件:在网页加载完毕只会显示省份信息
  3. 给省份下拉列表注册值改变事件:在省份发生变化时更新城市下拉列表信息

示例代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>省市联动</title>
</head>
<body>
<select id="province">
    <option>--请选择省份--</option>
</select>
<select id="city">
    <option>--请选择城市--</option>
</select>
</body>

<script>
    // 省份数据
    var provinces= ["广东省","湖南省","广西省"];
    // 城市数据,数据是一个二维数组,一维中每个元素是一个数组
    var cities = [["深圳","广州","东莞"],["长沙市","郴州市","湘潭市"],["南宁市","桂林市","柳州市"]];

    /*
    在页面加载完毕以后读取所有的省份,并且填充到#province中
    分析:动态创建option对象,并且设置 <option value="0">广东省</option> 这个value就是它对应的城市数组索引
     */
    window.onload = function () {
        //1.得到省份这个select对象
        let selectProvince = document.getElementById("province");
        //遍历省份这个数组
        for (let i = 0; i < provinces.length; i++) {
            let province = provinces[i];  //i=0  province=广东省
            //2.创建option对象
            let optionElement = document.createElement("option");
            //3.设置value和文本
            optionElement.value = i;
            optionElement.innerText = province;
            //4.添加到select的最后一个子元素: 父元素.appendChild(子元素)
            selectProvince.appendChild(optionElement);
        }
    }

    /**
     * 省份下拉列表的改变事件
     */
    document.getElementById("province").onchange = function () {
        //alert(this.value);  //value就是它的索引
        var arr = cities[this.value];  //得到相应城市的数组
        //得到城市下拉select对象
        let city = document.getElementById("city");  //city下拉列表对象
        //修改city中innerHTML
        city.innerHTML = "<option>--请选择城市--</option>";  //覆盖原来的HTML子元素
        //向城市下拉列表添加option
        //1. 遍历城市数组
        for (let i = 0; i < arr.length; i++) {
            let arrElement = arr[i];  //每个城市字符串
            //2. 创建option对象 <option>广州市</option>
            let optionElement = document.createElement("option");
            //3. 设置文本
            optionElement.innerText = arrElement;
            //4. 添加到select的最后一个子元素: 父元素.appendChild(子元素)
            city.appendChild(optionElement);
        }
    }
</script>
</html>

案例:校验注册表单

目标

在这里插入图片描述

onsubmit事件说明

 onsubmit事件,如果return false就可以阻止表单提交

案例需求

用户注册,需要进行如下验证,请在JS中使用正则表达式进行验证。

  1. 用户名:只能由英文字母和数字组成,长度为4~16个字符,并且以英文字母开头
  2. 密码: 大小写字母和数字6-20个字符
  3. 确认密码:两次密码要相同
  4. 电子邮箱: 符合邮箱地址的格式 /^\w+@\w+(.[a-zA-Z]{2,3}){1,2}$/
  5. 手机号:/^1[3456789]\d{9}$/
  6. 生日:生日的年份在1900~2009之间,生日格式为1980-5-12或1988-05-04的形式,/^((19\d{2})|(200\d))-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2]\d|3[0-1])$/

案例分析

  1. 创建正则表达式
  2. 得到文本框中输入的值
  3. 如果不匹配,在后面的span中显示错误信息,返回false
  4. 如果匹配,在后面的span中显示一个打勾图片,返回true
  5. 写一个验证表单中所有的项的方法,所有的方法都返回true,这个方法才返回true.

代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>验证注册页面</title>
    <style type="text/css">
        body {
            margin: 0;
            padding: 0;
            font-size: 12px;
            line-height: 20px;
        }

        .main {
            width: 525px;
            margin-left: auto;
            margin-right: auto;
        }

        .hr_1 {
            font-size: 14px;
            font-weight: bold;
            color: #3275c3;
            height: 35px;
            border-bottom-width: 2px;
            border-bottom-style: solid;
            border-bottom-color: #3275c3;
            vertical-align: bottom;
            padding-left: 12px;
        }

        .left {
            text-align: right;
            width: 80px;
            height: 25px;
            padding-right: 5px;
        }

        .center {
            width: 280px;
        }

        .in {
            width: 130px;
            height: 16px;
            border: solid 1px #79abea;
        }

        .red {
            color: #cc0000;
            font-weight: bold;
        }

        div {
            color: #F00;
        }

        span {
            color: red;
        }
    </style>
    <script type="text/javascript">
        /**
         * 校验所有的表单项
         */
        function checkAll() {
            return checkUser() && checkMobile();
        }

        /**
         * 检查用户名是否正确
         * 由英文字母和数字组成,长度为4~16个字符,并且以英文字母开头
         */
        function checkUser() {
            //1.得到文本框的值
            let value = document.getElementById("user").value;
            //2.创建正则表达式
            let reg = /^[a-zA-Z][a-zA-Z0-9]{3,15}$/;
            //3.比较正则表达式与文本框的值是否匹配
            if (reg.test(value)) {
                //4.如果匹配就通过,在后面显示打勾的图片,返回true
                document.getElementById("userInfo").innerHTML = "<img src='img/gou.png' width='15'/>";
                return true;
            } else {
                //5.如果不匹配,在后面显示错误信息,返回false
                document.getElementById("userInfo").innerHTML = "用户名格式有误";
                return false;
            }
        }

        /**
         * 判断手机号
         */
        function checkMobile() {
            //1.得到文本框的值
            let value = document.getElementById("mobile").value;
            //2.创建正则表达式
            let reg = /^1[3456789]\d{9}$/;
            //3.比较正则表达式与文本框的值是否匹配
            if (reg.test(value)) {
                //4.如果匹配就通过,在后面显示打勾的图片,返回true
                document.getElementById("mobileInfo").innerHTML = "<img src='img/gou.png' width='15'/>";
                return true;
            } else {
                //5.如果不匹配,在后面显示错误信息,返回false
                document.getElementById("mobileInfo").innerHTML = "手机格式有误";
                return false;
            }
        }
    </script>
</head>

<body>
<!--  onsubmit事件,如果return false就可以阻止表单提交-->
<form action="server" method="post" id="myform" onsubmit="return checkAll()">
    <table class="main" border="0" cellspacing="0" cellpadding="0">
        <tr>
            <td><img src="img/logo.jpg" alt="logo"/><img src="img/banner.jpg" alt="banner"/></td>
        </tr>
        <tr>
            <td class="hr_1">新用户注册</td>
        </tr>
        <tr>
            <td style="height:10px;"></td>
        </tr>
        <tr>
            <td>
                <table width="100%" border="0" cellspacing="0" cellpadding="0">
                    <tr>
                        <!-- 不能为空, 输入长度必须介于 5 和 10 之间 -->
                        <td class="left">用户名:</td>
                        <td class="center">
                            <!--文本框失去焦点进行验证-->
                            <input id="user" name="user" type="text" class="in" onblur="checkUser()"/>
                            <span id="userInfo"></span>
                        </td>
                    </tr>
                    <tr>
                        <!-- 不能为空, 输入长度大于6个字符 -->
                        <td class="left">密码:</td>
                        <td class="center">
                            <input id="pwd" name="pwd" type="password" class="in"/>
                        </td>
                    </tr>
                    <tr>
                        <!-- 不能为空, 与密码相同 -->
                        <td class="left">确认密码:</td>
                        <td class="center">
                            <input id="repwd" name="repwd" type="password" class="in"/>
                        </td>
                    </tr>
                    <tr>
                        <!-- 不能为空, 邮箱格式要正确 -->
                        <td class="left">电子邮箱:</td>
                        <td class="center">
                            <input id="email" name="email" type="text" class="in"/>
                        </td>
                    </tr>
                    <tr>
                        <!-- 不能为空, 使用正则表达式自定义校验规则,1开头,11位全是数字 -->
                        <td class="left">手机号码:</td>
                        <td class="center">
                            <input id="mobile" name="mobile" type="text" class="in" onblur="checkMobile()"/>
                            <span id="mobileInfo"></span>
                        </td>
                    </tr>
                    <tr>
                        <!-- 不能为空, 要正确的日期格式 -->
                        <td class="left">生日:</td>
                        <td class="center">
                            <input id="birth" name="birth" type="text" class="in"/>
                        </td>
                    </tr>
                    <tr>
                        <td class="left">&nbsp;</td>
                        <td class="center">
                            <input name="" type="image" src="img/register.jpg"/>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

案例:表格隔行换色与全选全不选

目标

  1. 实现隔行变色的效果
  2. 实现全选全不选的效果

效果

在这里插入图片描述

步骤

隔行变色
  1. 页面加载完毕,得到所有的tr。
  2. 使用基本过滤选择器,除了第0行,设置偶数行背景色为lightgreen
  3. 使用基本过滤选择器,除了第0行,设置奇数行背景色为lightyellow
全选全不选
  1. 给最上面的id=all的复选框添加点击事件
  2. 使用属性选择器选中所有item=name的复选框,设置它的checked属性与id=all的复选框的checked属性相同。

代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>隔行换色/全选全不选</title>
    <style type="text/css">
        table {
            border-collapse: collapse;
        }

        tr {
            height: 35px;
            text-align: center;
        }

        a:link {
            text-decoration: none;
        }
    </style>
    <script type="text/javascript" src="js/jquery-3.3.1.js"></script>
    <script type="text/javascript">
        $(function () {
            //隔行变色,除了第1行之外,even表示偶数行,剩下的行中偶数行
            $("tr:gt(0):even").css("background-color", "lightgreen");
            $("tr:gt(0):odd").css("background-color", "lightyellow");

            //全选,全不选
            $("#all").click(function () {  //最上面的复选框点击事件
                //得到all的值是true或是false
                //选中所有下面的checkbox
                $("input[name=item]").prop("checked", $("#all").prop("checked"));    //设置boolean类型的属性
            });
        })
    </script>
</head>
<body>
<table id="tab1" border="1" width="700" align="center">
    <tr style="background-color: #999999;">
        <td><input type="checkbox" id="all"></td>
        <td>分类ID</td>
        <td>分类名称</td>
        <td>分类描述</td>
        <td>操作</td>
    </tr>
    <tr>
        <td><input type="checkbox" name="item"></td>
        <td>1</td>
        <td>手机数码</td>
        <td>手机数码类商品</td>
        <td><a href="">修改</a>|<a href="">删除</a></td>
    </tr>
    <tr>
        <td><input type="checkbox" name="item"></td>
        <td>2</td>
        <td>电脑办公</td>
        <td>电脑办公类商品</td>
        <td><a href="">修改</a>|<a href="">删除</a></td>
    </tr>
    <tr>
        <td><input type="checkbox" name="item"></td>
        <td>3</td>
        <td>鞋靴箱包</td>
        <td>鞋靴箱包类商品</td>
        <td><a href="">修改</a>|<a href="">删除</a></td>
    </tr>
    <tr>
        <td><input type="checkbox" name="item"></td>
        <td>4</td>
        <td>家居饰品</td>
        <td>家居饰品类商品</td>
        <td><a href="">修改</a>|<a href="">删除</a></td>
    </tr>
</table>
</body>
</html>

小结

  1. 选中大于0的tr的偶数行的选择器怎么写?

    tr:gt(0):even
    
  2. 设置某个复选框选中的方法是什么?

    jq对象.prop("checked",true)
    

案例:实现购物车

目标

使用今天学习的内容制作一个购物车案例

需求

  1. 实现添加商品,如果商品名为空,提示:商品名不能为空。如果不为空,则添加成功一行,清空文本框的内容,图片使用固定一张。
  2. 实现删除一行商品的功能,删除前弹出确认对话框。

效果

在这里插入图片描述

代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>购物车的实现</title>
    <style type="text/css">
        table {
            width: 400px;
            border-collapse: collapse;
            margin: auto;
        }

        td,th {
            text-align: center;
            height: 30px;
        }

        .container {
            width: 400px;
            margin: auto;
            text-align: right;
        }

        img {
            width: 100px;
            height: 100px;
        }

        th {
            background-color: lightgray;
        }

        tr:hover {
            background-color: lightyellow;
        }
    </style>
    <script type="text/javascript" src="js/jquery-3.3.1.js" ></script>
    <script type="text/javascript">
        //添加到购物车的点击事件
        function addRow() {
            //1.获取文本框中内容
            let val = $("#pname").val().trim();   //去掉前后的空格
            if (val == "") {  //注:不同于Java
                alert("商品名字不能为空");
                //让文本框获得焦点
                $("#pname").focus();
                return;
            }
            //2.创建tr
            let tr = "<tr>" +
                "<td><img src=\"img/girl.jpg\" /></td>" +
                "<td>" + val + "</td>" +
                "<td><input type=\"button\" value=\"删除\" οnclick=\"deleteRow(this)\"></td>" +
                "</tr>";
            //3.创建好的tr的放到父元素tbody中
            $("#cart").append(tr);
            //4.清空文本框内容
            $("#pname").val("");
        }

        //删除一行
        function deleteRow(obj) {   //obj其实就是一个按钮对象,这是JS对象,转成JQ对象
            if (confirm("真的要从购物车中移除这件商品吗?")) {
                //按钮的父元素->td  的父元素 -> tr  删除tr即可
                $(obj).parent().parent().remove();   //自己删除自己
            }
        }
    </script>
</head>

<body>
<div class="container">
    <table border="1">
        <tbody id="cart">
        <tr>
            <th>
                图片
            </th>
            <th>
                商品名
            </th>
            <th>
                操作
            </th>
        </tr>
        <tr>
            <td><img src="img/sx.jpg" /></td>
            <td>
                三星Note7
            </td>
            <td>
                <!--this表示当前按钮-->
                <input type="button" value="删除" onclick="deleteRow(this)">
            </td>
        </tr>
        </tbody>
    </table>
    <br />
    商品名:
    <input type="text" id="pname" value="" />&nbsp;
    <input type="button" value="添加到购物车" onclick="addRow()" />
</div>
</body>
</html>

小结

1. 创建tr
2. 添加到tbody中
3. 删除元素remove()
4. 获取父元素:parent()

案例:原生的AJAX判断用户名是否存在

目标

​ 了解原生的ajax如何去实现这个案例

需求

​ 用户注册时输入一个用户名,失去焦点以后,通过ajax后台判断用户名是否存在。服务器先不访问数据库,直接判断用户名,如果用户名为newboy,则表示用户已经存在,否则用户名可以注册使用。

效果

在这里插入图片描述

服务器端

准备数据文件 users.json

[
"Newboy",
"Jack",
"Rose",
"Tom",
"Lina"
]

客户端

步骤
  1. 文本框失去焦点,得到文本框中的姓名
  2. 创建 XMLHttpRequest 请求对象
  3. 设置请求的 URL
  4. 调用 open 方法,设置提交给服务器的请求方式和访问地址
  5. 调用 send 方法发送请求
  6. 设置请求对象的 onreadystatechange 事件,即"准备状态改变"事件。
    a) 当 readyState 等于 4,并且服务器 status 响应码为 200 则表示响应成功
    b) 通过 responseText 得到响应的字符串,服务器返回的是一个字符串数组。
    c) 转成 JSON 数组,再与文本框中输出的值进行比较,如果存在就设置为 true,否则设置为 false。
    d) 如果是 true,则表示用户存在,在后面的 span 显示"用户名已经存在"
    e) 否则表示不存在,在后面的 span 中显示"恭喜你,可以注册"。
代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户是否存在</title>
</head>
<body>
用户名:<input type="text" id="user"> <span id="info"></span>
<script type="text/javascript">
    /*用户注册时输入一个用户名,失去焦点以后,通过ajax后台判断用户名是否存在。
    服务器先不访问数据库,直接判断用户名,如果用户名为newboy,则表示用户已经存在,否则用户名可以注册使用。 */

    //用户名的改变事件
    document.getElementById("user").onchange = function () {
        //创建XMLHttpRequest对象
        let request = new XMLHttpRequest();
        //打开服务器的连接,参数:GET或POST,服务器地址,true
        request.open("GET", "json/users.json", true);
        //发送请求给服务器
        request.send();
        //设置回调函数,准备状态发生改变时激活这个函数
        request.onreadystatechange = function () {
            //准备状态等于4,服务器状态码等于200
            if (request.readyState == 4 && request.status == 200) {
                //接收服务器返回的数据
                let text = request.responseText;  //字符串
                //将字符串转成JSON
                let users = JSON.parse(text);  //所有用户的数组
                //得到用户在文本框中输入的数据
                let user = document.getElementById("user").value;
                //设置标记
                let exists = false;
                //遍历服务器返回的数组
                for (let u of users) {
                    if (u == user) {  //当前元素等于输入的用户名
                        exists = true;  //修改标记
                        break;
                    }
                }
                //判断标记
                if (exists) {  //用户名已经存在
                    document.getElementById("info").innerText = "用户名已经存在";
                }
                else {  //不存在
                    document.getElementById("info").innerText = "恭喜,可以注册";
                }
            }
        }
    }
</script>
</body>
</html>

小结

浏览器端的访问流程

在这里插入图片描述

案例:输入自动补全

需求

在输入框输入关键字,下拉框中异步显示与该关键字相关的用户名称

效果

在这里插入图片描述

服务器端代码

search.json

[
  "张三",
  "李四",
  "王五",
  "赵六",
  "田七",
  "孙八",
  "张三丰",
  "张无忌",
  "李寻欢",
  "王维",
  "李白",
  "杜甫",
  "李贺",
  "李逵",
  "宋江",
  "王英",
  "鲁智深",
  "武松",
  "张薇",
  "刘小轩",
  "刘浩宇",
  "刘六"
]

流程分析

在这里插入图片描述

分析页面组成

在这里插入图片描述

步骤

  1. 编写文本框的keyup事件
  2. 得到word文本框的值
  3. 去掉值前后的空格,判断值是否为空,如果为空,则返回不再继续。
  4. 否则使用$.post方法发送请求给服务器
  5. 在success回调函数中得到服务器返回数据:JSON格式所有用户的集合
  6. 创建正则表达式,匹配以^word开头的用户
  7. 创建一个数组用来保存符合条件的字符串
  8. 如果上面的数组不为空,则进行字符串拼接,每个用户是一个div。
  9. 拼接完成以后使用html()方法填充到#show的div中
  10. 如果集合为空,则隐藏#show的div

拓展

  1. 给#show中div添加点击事件
  2. 将 div 的文本内容显示在#word的值中
  3. 隐藏#show这个div

代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>自动完成</title>
    <style type="text/css">
        .content {
            width: 400px;
            margin: 30px auto;
            text-align: center;
        }

        input[type='text'] {
            box-sizing: border-box;
            width: 280px;
            height: 30px;
            font-size: 14px;
            border: 1px solid #38f;
        }

        input[type='button'] {
            width: 100px;
            height: 30px;
            background: #38f;
            border: 0;
            color: #fff;
            font-size: 15px;
        }

        #show {
            box-sizing: border-box;
            position: relative;
            left: 7px;
            font-size: 14px;
            width: 280px;
            border: 1px solid dodgerblue;
            text-align: left;
            border-top: 0;
            /*一开始是隐藏不可见*/
            display: none;
        }

        #show div {
            padding: 4px;
            background-color: white;
        }

        #show div:hover {
            /*鼠标移上去背景变色*/
            background-color: #3388ff;
            color: white;
        }
    </style>
    <script type="text/javascript" src="js/jquery-3.3.1.js"></script>
</head>
<body>
<div class="content">
    <img alt="传智播客" src="img/logo.png"><br/><br/>
    <input type="text" name="word" id="word">
    <input type="button" value="搜索一下">
    <div id="show"></div>
</div>

<script type="text/javascript">
    /*
    alert(event.keyCode);  //keyCode键盘码表,每个按键都会对应一个号码
     */
    //不能使用change事件,因为change要失去焦点才激活,只要松开任何一个键就激活
    $("#word").keyup(function () {
        //判断文本框中是否有数据,如果不为空,才继续,去掉前后空格
        let word = $("#word").val().trim();
        if (word == "") {
            //如果文本框中没有内容,也要隐藏div
            $("#show").hide();
            return;  //不再继续
        }
        //表示有数据的,开始访问服务器
        $.get({
            url: "json/search.json",  //服务器的访问地址
            success: function (users) {  //返回服务器上所有的数据
                //使用正则表达式,只保留指定字符串开头的用户名
                let reg = new RegExp("^" + word);
                //创建一个数组,用来保存所有以word开头的用户
                let arr = new Array();
                //遍历整个数组
                for (let user of users) {
                    //判断每个字符串是否匹配正则表达式
                    if (reg.test(user)) {
                        arr.push(user);  //添加到数组中
                    }
                }

                //把arr数组显示在div中
                //先判断数组中是否有元素,有元素才显示
                if (arr.length > 0) {
                    //拼接字符串
                    let html="";
                    for (let name of arr) {
                        html+="<div>" + name + "</div>";
                    }
                    //放在#show的div中
                    $("#show").html(html);  //括号中是一个变量名html
                    //显示div
                    $("#show").show();

                    //如果点击每个小的div,就把div中文本赋值给文本框的value
                    $("#show div").click(function () {
                        //this相当于你点击的那个div
                        $("#word").val($(this).text());
                        //隐藏大的div
                        $("#show").slideUp("normal");  //加个动画 
                    });
                }
                else {  //没有元素隐藏div
                    $("#show").hide();
                }
            }
        });
    });
</script>
</body>
</html>

小结

  1. 使用文本框的键盘松开事件 keyup
  2. $.post() 提交数据给服务器,返回JSON集合
  3. 遍历集合,拼接成div的字符串
  4. 使用html()方法将拼接好的字符串覆盖div中原有的内容
  5. 显示show这个div

案例:使用链接下载文件不足

目标

链接下载文件存在的问题

页面效果

在这里插入图片描述

下载页面

<!DOCTYPE html>
<html>
  <head>
    <title>资源下载列表</title>
    <meta charset="utf-8">
  </head>
  
  <body>
   <h2>文件下载页面列表</h2>
   <h3>超链接的下载</h3>
   <a href="download/file.txt">文本文件</a><br/>
   <a href="download/file.jpg">图片文件</a><br/>
   <a href="download/file.zip">压缩文件</a><br/>
   <hr/>
   <h3>手动编码的下载方式</h3>
   <!-- 注:down这里是下载的Servlet的访问地址 -->
   <a href="down?filename=file.txt">文本文件</a><br/>
   <a href="down?filename=file.jpg">图片文件</a><br/>
   <a href="down?filename=file.zip">压缩文件</a><br/>
   <a href="down?filename=美女.jpg">美女</a><br/>
  </body>
</html>

小结:使用超链接下载的不足

  1. 有些资源是直接打开的,不满足下载的要求
  2. 暴露资源的真实地址,有可能被盗链。
  3. 不利于业务逻辑的控制,要有积分才可以下载。

案例:使用Servlet下载文件

目标

  1. 实现使用Servlet的方式下载文件
  2. 文件名有汉字的处理

下载设置的响应头

技术点,需要设置以下响应头才可以实现下载

设置响应头参数说明
Content-Disposition:
attachment; filename=文件名
Content-Disposition: 浏览器打开资源的方式
attachment: 表示以附件的方式下载,而不是打开
filename: 下载到浏览器端使用的文件名

步骤

  1. 从链接上得到文件名
  2. 设置content-disposition头
  3. 得到文件的输入流
  4. 得到response的输出流
  5. 写出到浏览器端

汉字乱码原理

在这里插入图片描述

注:IE、Chrome下载中文采用的是URL编码,注:FireFox不能采用这种方式。

下载的Servlet代码

package com.itheima.servlet;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

/**
 * 处理下载的Servlet
 * 在其实开发过程中,不建议在服务器上使用汉字
 * 在Chrome和IE等浏览器中,文件名使用的URL编码
 */
@WebServlet("/down")
public class Demo5DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //0.得到下载的文件,filename与地址栏提交的名字相同
        String filename = request.getParameter("filename");  //GET汉字没有乱码
        System.out.println("下载的文件名是:" + filename);
        //必须设置响应头,考虑汉字的问题,对文件名使用URL编码
        response.setHeader("content-disposition","attachment;filename=" + URLEncoder.encode(filename,"utf-8"));
        //1.得到上下文对象
        ServletContext application = getServletContext();
        //2.得到web目录下资源的输入流对象
        InputStream inputStream = application.getResourceAsStream("/download/" + filename);
        //3.得到响应对象的字节输出流
        OutputStream outputStream = response.getOutputStream();
        //4.使用IOUtils复制流
        IOUtils.copy(inputStream, outputStream);
        //5.关闭流
        outputStream.close();
        inputStream.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

小结

  1. 下载需要设置哪个响应头?

    Content-disposition: attachment;filename=文件名
    
  2. 如果下载文件名中有汉字使用哪个方法编码?

    URLEncoder.encode("文件名","utf-8")
    

案例:得到用户上次访问的时间

目标

  1. 如果用户是第一次访问,则输出:您好,您是第1次访问,欢迎您的加入!当前的时间是xxx

  2. 如果之前已经访问过,则从Cookie中得到上次访问的时间,显示:您好,欢迎您再次访问。

    上次访问的时间是:xxxx,当前的时间是xxxxx。

  3. 无论是否是第一次访问,都要把这次访问服务器的时间写到Cookie中

流程分析

在这里插入图片描述

代码

package com.itheima.servlet;

import com.itheima.utils.CookieUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 1. 如果用户是第一次访问,则输出:您好,您是第1次访问,欢迎您的加入!当前的时间是xxx
 * 2. 如果之前已经访问过,则从Cookie中得到上次访问的时间,显示:您好,欢迎您再次访问。
 * 上次访问的时间是:xxxx,当前的时间是xxxxx。
 * 3. 无论是否是第一次访问,都要把这次访问服务器的时间写到Cookie中
 */
@WebServlet("/demo6")
public class Demo6VisitedServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        //0.创建现在的时间,不建议使用空格
        String now = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(new Date());
        //1.获取浏览器端发送来的Cookie的值,假设这个键是visitedTime,使用工具类
        String visitedTime = CookieUtils.getCookieValue(request, "visitedTime");
        //2.判断是否为空,如果为空表示第1次访问
        if (visitedTime == null) {
            out.print("<h1>您是第1次访问,现在的时间是:" + now + "</h1>");
        } else {
            //不为空,表示从cookie中获取到指定的值,这就是上次访问的时间
            out.print("<h1>欢迎您再次访问,上次访问的时间是:" + visitedTime + "</h1>");
            out.print("<h2 style='color:green'>现在的时间是:" + now + "</h2>");
        }

        //3.还要将现在的时间写回到浏览器,键与读取的要相同
        Cookie cookie = new Cookie("visitedTime", now);
        //设置过期时间
        cookie.setMaxAge(60 * 60 * 24 * 30);  //保存1个月
        //写到浏览器端
        response.addCookie(cookie);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

小结

  1. 从Cookie中取一个叫visitedTime的信息,如果为null,则表示第一次访问
  2. 否则读取Cookie的信息字符串,显示上次访问的时间
  3. 无论上次访问时间是否为空,都把这次访问的时间写入Cookie中

案例:利用cookie实现自动登录

案例part1:Cookie实现自动登录Servlet

目标

能够使用Cookie保存用户名和密码

结构

在这里插入图片描述

分析

在这里插入图片描述

Servlet步骤

  1. 得到表单提交的数据:用户名和密码
  2. 判断用户名和密码实现登录
  3. 如果登录成功,则判断是否勾选了记住我。
  4. 如果勾选了,则将用户名和密码写入Cookie中,保存一周的时间,并跳转到成功页面。
  5. 否则跳转到登录失败页面

代码

LoginServlet

package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 得到用户名和密码,判断是否正确
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //2. 如果登录成功
        if ("admin".equals(username) && "123".equals(password)) {
            //再判断是否勾选自动登录
            String remember = request.getParameter("remember");
            if (remember != null) {  //不为空表示勾选了
                //3. 把用户名和密码创建成Cookie
                Cookie cookieUsername = new Cookie("username", username);
                //4. 设置访问路径和过期的时间,发送给浏览器
                cookieUsername.setPath(request.getContextPath() + "/login.html");
                cookieUsername.setMaxAge(60 * 60 * 24 * 7);  //保存1周
                response.addCookie(cookieUsername);

                Cookie cookiePassword = new Cookie("password", password);
                //4. 设置访问路径和过期的时间,发送给浏览器
                cookiePassword.setPath(request.getContextPath() + "/login.html");
                cookiePassword.setMaxAge(60 * 60 * 24 * 7);  //保存1周
                response.addCookie(cookiePassword);
            }
            //表示登录成功,跳转到登录成功的页面
            response.sendRedirect("success.html");
        }
        else {
            response.sendRedirect("failure.html");  //登录失败
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

案例part2:Cookie实现自动登录浏览器端

目标

浏览器端代码的实现

JS代码步骤

  1. 编写工具方法,读取指定键的值
  2. 页面加载完成,读取cookie的信息
  3. 如果有username不为null,并且password也不为null
  4. 则将用户名和密码赋值给文件框,并且提交表单

代码

login.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
    <!--导入js文件-->
    <script src="js/commons.js"></script>
</head>
<body>
<h2>用户登录</h2>
<form action="login" method="post" id="loginForm">
    <table>
        <tr>
            <td width="60">用户名</td>
            <td><input type="text" name="username" id="username"/></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password" id="password"/></td>
        </tr>
        <tr>
            <td>记住我</td>
            <!--没有value属性的前提下,点中它的值是on,如果没有勾上为null -->
            <td><input type="checkbox" name="remember"/></td>
        </tr>
        <tr>
            <td colspan="2" align="center"><input type="submit" value="登录"/></td>
        </tr>
    </table>
</form>
</body>
</html>

commons.js

/**
 页面加载完毕以后执行
 */
window.onload = function () {
    //从cookie中得到用户名和密码
    let username = getCookieValue("username");
    let password = getCookieValue("password");
    //如果字符串不为空,才将数据提交给服务器
    if (username!="" && password!="") {
        //设置文本框的值
        document.getElementById("username").value = username;
        document.getElementById("password").value = password;

        //提交表单
        document.getElementById("loginForm").submit();
    }
}

/**
 通过键得到Cookie中的值
 @param name表示要找的名字
 */
function getCookieValue(name) {
    //可以得到当前路径下所有的cookie信息
    let cookie = document.cookie;   //username=admin; password=123
    if (cookie!="") {
        //按分号进行拆分成数组,数组中有2个元素
        let split = cookie.split("; ");
        for (let i = 0; i < split.length; i++) {   //split[0] = "username=admin"   split[1] = "password=123"
            let splitElement = split[i];   //每个元素
            //再按等于号来拆分
            let arr = splitElement.split("=");
            let key = arr[0];  //[0]号元素是username,[1]号元素admin
            let value = arr[1];
            //如果name与key相等,就返回value
            if (key == name) {
                return value;
            }
        }
    }
    return "";  //没有找到
}

案例:商品购物车

目标

有一个商品页面,可以点击超链接将商品添加到购物车,并可以查看购物车中商品信息

流程分析

购物车要保存在哪个作用域?

在这里插入图片描述

步骤

添加到购物车的Servlet
  1. 得到会话
  2. 从会话中取出购物车
  3. 如果购物车为空,则创建一个购物车
  4. 得到商品名字
  5. 通过名字为键取出有多少件商品
  6. 如果为空,表示第 1 次添加,设置为 值为1,否则将原来的值加 1,再放进去Map中
  7. 更新会话
  8. 输出操作结果和信息
查看购物车的Servlet
  1. 从会话中得到购物车
  2. 如果为空则输出购物车为空
  3. 否则遍历购物车输出每一行
  4. 输出继续购物的链接

代码

显示商品列表页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>商品列表页面</title>
    <style>
        table {
            border-collapse: collapse;
        }

        td,th {
            text-align: center;
            padding: 3px;
        }
    </style>
</head>
<body>
<table border="1" width="300">
    <tr>
        <th>商品名称</th>
        <th>操作</th>
    </tr>

    <tr>
        <td>洗衣机</td>
        <td>
            <!--访问servlet中,add是servlet的访问地址-->
            <a href="add?name=洗衣机">添加到购物车</a>
        </td>
    </tr>

    <tr>
        <td>电视机</td>
        <td>
            <!--访问servlet中,add是servlet的访问地址-->
            <a href="add?name=电视机">添加到购物车</a>
        </td>
    </tr>

    <tr>
        <td>缝纫机</td>
        <td>
            <!--访问servlet中,add是servlet的访问地址-->
            <a href="add?name=缝纫机">添加到购物车</a>
        </td>
    </tr>

    <tr>
        <td>打火机</td>
        <td>
            <!--访问servlet中,add是servlet的访问地址-->
            <a href="add?name=打火机">添加到购物车</a>
        </td>
    </tr>
</table>
</body>
</html>

添加到购物车的Servlet

package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

/**
 * 访问地址与html的链接地址相同
 */
@WebServlet("/add")
public class AddProductToServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获得会话对象
        HttpSession session = request.getSession();
        //2.从会话域中取出购物车对象(Map对象),购物车键是商品名,值是商品数量
        HashMap<String, Integer> cart = (HashMap<String, Integer>) session.getAttribute("cart");
        //3.判断购物车是否为空,如果为空创建一个购物车,表示第一次访问
        if (cart == null) {
            cart = new HashMap<>();
        }
        // 购物车对象是存在
        //4.得到商品的名字
        String name = request.getParameter("name");  //键:商品名
        //5.如果商品不存在,向购物车中添加1件商品,如果存在,从购物车中取出商品的数量,加1再放进去
        Integer num = cart.get(name);  //值:数量
        if (num == null) {   //商品在购物车中不存在
            cart.put(name, 1);  //添加数量1到购物车中
        }
        else {  //商品在购物车中存在
            cart.put(name, num + 1);  //加1放进去
        }
        //6. 更新会话域中购物车这个键
        session.setAttribute("cart", cart);
        //7.输出操作结果和链接
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print("添加1件" + name + "商品成功<hr/>");
        out.print("<a href='productlist.html'>继续购物</a>");
        out.print("<a href='cart'>查看购物车</a>");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

查看购物车的Servlet

package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

/**
 * 显示购物车
 * 与链接地址要相同
 */
@WebServlet("/cart")
public class CartServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();

        //1.从会话中获取购物车
        HttpSession session = request.getSession();
        HashMap<String, Integer> cart = (HashMap<String, Integer>) session.getAttribute("cart");

        //2.如果购物车为空输出空
        if (cart == null) {
            out.print("购物车竟然是空的");
        }
        //3.如果不为空,遍历Map集合输出购物车中每一项
        else {
            out.print("<table border=\"1\" width=\"300\">");
            out.print("<tr><th>商品名称</th><th>数量</th></tr>");
            //每一行是一个tr,就是购物车中一项
            cart.forEach((k,v) -> {
                out.print("<tr><td>"+ k +"</td><td>" +v + "</td></tr>");
            });
            out.print("</table>");
        }
        out.print("<a href='productlist.html'>继续购物</a>");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

小结

方法说明
Map<String,Integer> cart = new HashMap<>();创建一个购物车,结构是Map结构
键是商品名,值商品数量
request.getSession()获取会话对象,如果没有就是创建,如果有就获取
session.setAttribute()向会话域中添加对象

案例:过滤全局汉字乱码

目标

编写过滤器,过滤所有Servlet中使用POST方法提交的汉字的编码。

分析

在这里插入图片描述

开发步骤

  1. 有2个Servlet,一个是LoginServlet登录,一个是RegisterServlet注册
  2. 有2个JSP页面,1个是login.jsp,有表单,登录名。1个register.jsp,有表单,有注册的名字。都使用POST提交用户名使用汉字提交。
  3. 使用过滤器,对所有的Servlet的POST方法进行过滤。
  4. 在没有使用过滤器之前,每个Servlet必须加上汉字编码:request.setCharacterEncoding(字符集); 字符集与网页的编码要一致

代码

login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="login" method="post">
    登录名:<input type="text" name="user"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>
register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<h2>用户注册</h2>
<form action="register" method="post">
    注册名:<input type="text" name="name"><br>
    <input type="submit" value="注册">
</form>
</body>
</html>
LoginServlet.java
package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        //获取请求的数据
        String username = request.getParameter("user");
        //打印输出
        out.print("登录成功:" + username);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

RegisterServlet.java
package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        //得到请求提交的数据
        String name = request.getParameter("name");
        out.print("注册成功:" + name);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

CharacterEncodingFilter.java
package com.itheima.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/*
过滤所有的资源
 */
@WebFilter(filterName = "CharacterEncodingFilter", urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //0.将转成子接口才有这个方法
        HttpServletRequest request = (HttpServletRequest) req;
        //1.判断是否是POST方法,注:这里是大写
        if (request.getMethod().equals("POST")) {
            //2.如果是才进行编码
            request.setCharacterEncoding("utf-8");
        }
        //3.无论是否是POST方法都要放行
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

小结

编写一个过滤器就可以对所有的Servlet进行汉字编码

  1. 汉字编码使用哪个方法:setCharacterEncoding()
  2. 过滤的地址: /* 所有地址
  3. 放行使用哪个方法:chain.doFilter(request, response)

案例:过滤敏感词汇

需求

当用户发帖的时候,如果发出敏感词汇就进行过滤,并提示发贴失败,否则显示正常的发贴内容。

案例效果

  1. 在表单中输入含有非法字符的言论,点击提交按钮

在这里插入图片描述

  1. 在浏览器上显示发贴失败

在这里插入图片描述

  1. 正常发贴的情况

在这里插入图片描述

案例分析

  1. 创建一个表单用于发表言论。
  2. 创建一个PostWordServlet,正常接收用户的输入信息,并且打印到浏览器
  3. 创建一个words.txt文件,其中存入非法字符。
  4. 创建一个Filter,只过滤PostWordServlet。
    1. 在init方法中将txt文件中的非法字符读取到List集合中。注:指定字符的编码为utf-8
    2. 在doFilter方法中,使用获取请求中的参数,判断请求的文字中是否包含非法字符。
    3. 如果言论中含有非法字符,就拦截,并且提示用户非法言论,退出过滤器
    4. 继续执行表示没有非法字符,就放行。

分析

在这里插入图片描述

实现步骤

  1. 创建一个表单,用于发表言论
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>发贴</title>
</head>
<body>
<form method="post" action="PostWordServlet">
    发贴:<input type="text" name="message"> <input type="submit" value="提交">
</form>
</body>
</html>
  1. 创建一个txt文件,存入非法字符。要注意,文件存储使用的字符集,否则可能出现乱码。
穷逼
笨蛋
白痴
王八
贱人
傻逼

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLHsXleM-1592189481791)(C:\Users\Administrator\Desktop\study\zimg\1592189067781.png)]

  1. 创建一个servlet用于接受表单提交的内容
package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 发贴显示的Servlet
 */
@WebServlet("/PostWordServlet")
public class PostWordServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        //获取到帖子的内容
        String message = request.getParameter("message");
        //显示在页面上
        out.print("您的发贴内容如下:<hr/>" + message);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}
  1. 创建一个过滤器,用来拦截请求,过滤请求中发表的言论的非法字符
package com.itheima.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 只过滤发贴的Servlet
 */
@WebFilter(filterName = "IllegalWordFilter", urlPatterns = "/PostWordServlet")
public class IllegalWordFilter implements Filter {
    //创建一个集合,保存所有的词汇
    private List<String> illegalWords = new ArrayList<>();

    //在初始化的方法中读取所有的词汇,这个方法只会执行1次
    public void init(FilterConfig config) throws ServletException {
        //得到当前的类对象 -> 得到类加载器 -> 获取src目录下资源
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("words.txt");
        //将字节流使用utf-8转成字符流来使用,指定字符的编码
        try(BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"))) {
            String line = null;
            while((line = br.readLine())!=null) {  //不为空就继续,每次读取一行
                illegalWords.add(line);  //将读取到的这一行添加到集合中
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //在控制器上输出是否读取成功
        System.out.println(illegalWords);
    }

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //设置汉字编码,不然会有乱码
        req.setCharacterEncoding("utf-8");
        //获取发贴的信息
        String message = req.getParameter("message");

        HttpServletResponse response = (HttpServletResponse) resp;

        //遍历所有词汇,发贴的内容中是否包含了这些词汇
        for (String illegalWord : illegalWords) {
            //包含了这些词汇就拦截
            if (message.contains(illegalWord)) {
                response.setContentType("text/html;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.print("您的帖子中包含了非法的词汇,发贴失败");
                return;  //不再继续
            }
        }
        //执行这句话才会放行
        chain.doFilter(req, resp);
    }

}

小结

过滤敏感词汇过滤器的开发步骤:

  1. 在init中从文件中加载所有的敏感词汇,复习缓存字符流
  2. 判断用户发贴是否包含这些词汇之一
  3. 如果有就拦截,没有就放行

案例:统计网站当前在线人数

执行效果

页面

在这里插入图片描述

服务器控制台信息

在这里插入图片描述

分析

每当一个用户访问项目的时候,都会创建一个session会话。所以当前session会话被创建,当前在线用户+1,每当session会话被销毁,当前在线用户-1。

HttpSessionListener可以用来监听session对象的创建和销毁的。所以可以在HttpSessionListener中的监听session对象创建和销毁的方法中控制在线人数的加减。

步骤

  1. 创建一个监听器 SessionCountListener

    a) 监听会话创建的方法

​ i. 在会话创建的监听方法中,从上下文域中取出当前计数值

​ ii. 如果为空,表示是第1个用户,设置值为1

​ iii. 不为空则加1,并且更新上下文域

​ b) 监听会话销毁的方法

​ i. 从上下文域中得到当前在线的人数

​ ii. 减1后更新

  1. 创建一个注销的LogoutServlet

    a) 让会话失效

    b) 打印输出:您已经安全退出网站

  2. 编写JSP

    a) 在JSP上取出上下文域中用户数显示

    b) 显示安全退出的链接

代码

监听器
package com.itheima.listener;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * 统计会话的监听器
 * 当前session会话被创建,当前在线用户+1,每当session会话被销毁,当前在线用户-1
 * 人数最后是要放在上下文域中才能给所有的用户使用
 * HttpSessionListener 接口用来监听会话的创建和销毁
 */
@WebListener
public class SessionCounterListener implements HttpSessionListener {
    //监听会话的创建
    @Override
    public void sessionCreated(HttpSessionEvent event) {
        //获取当前会话对象
        HttpSession session = event.getSession();
        //在服务器上输出信息
        System.out.println("创建会话:" + session.getId());
        //得到上下文域,可以通过会话对象获取上下文对象
        ServletContext application = session.getServletContext();
        //从上下文对象中取number的值
        Integer number = (Integer) application.getAttribute("number");
        //因为第1个用户是没有值的
        if (number == null) {
            //第1个用户向上下文域中添加1的值
            application.setAttribute("number", 1);
        } else {
            //有值就加1,再放回去。这里要写成++number,不能写成number++
            application.setAttribute("number", ++number);
        }
    }

    //监听会话的销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        //向服务器输出信息
        HttpSession session = event.getSession();
        System.out.println("会话销毁:" + session.getId());
        //得到上下文域
        ServletContext application = session.getServletContext();
        //得到number的值
        Integer number = (Integer) application.getAttribute("number");
        //减1再放进去,要先减
        application.setAttribute("number", --number);
    }
}

考虑线程安全问题

package com.itheima.listener;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 统计会话的监听器
 * 当前session会话被创建,当前在线用户+1,每当session会话被销毁,当前在线用户-1
 * 人数最后是要放在上下文域中才能给所有的用户使用
 * HttpSessionListener 接口用来监听会话的创建和销毁
 *
 * 使用原子类,必须是同一个对象,无论是加或减都是操作同一个对象才正确
 * 如果作用域中属性值是引用类型,我们直接修改引用类型的值,不需要重新更新作用域
 */
@WebListener
public class SessionCounterListener implements HttpSessionListener {

    private AtomicInteger number = null;  //声明

    //监听会话的创建
    @Override
    public void sessionCreated(HttpSessionEvent event) {
        //获取当前会话对象
        HttpSession session = event.getSession();
        //在服务器上输出信息
        System.out.println("创建会话:" + session.getId());
        //得到上下文域,可以通过会话对象获取上下文对象
        ServletContext application = session.getServletContext();
        //从上下文对象中取number的值
        number = (AtomicInteger) application.getAttribute("number");
        //因为第1个用户是没有值的
        if (number == null) {
            number = new AtomicInteger(1);  //第1次赋值
            //第1个用户向上下文域中添加1的值
            application.setAttribute("number", number);
        } else {
            //把值取出来
            number = (AtomicInteger) application.getAttribute("number");
            //自己加1
            number.incrementAndGet();
            //更新这个值
            //application.setAttribute("number",number);
        }
    }

    //监听会话的销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        //向服务器输出信息
        HttpSession session = event.getSession();
        System.out.println("会话销毁:" + session.getId());
        //得到上下文域
        ServletContext application = session.getServletContext();
        //得到number的值
        number = (AtomicInteger) application.getAttribute("number");
        //减1
        number.decrementAndGet();
        //减1再放进去,要先减
        //application.setAttribute("number", number);
    }
}
退出的Servlet
package com.itheima.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 退出
 */
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print("您已经安全退出");
        //让会话过期
        request.getSession().invalidate();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

显示人数的JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>显示在线人数</title>
  </head>
  <body>
  <%--人数放在上下文域中,给所有的用户能访问--%>
  <h2>当前网站在线人数是:${applicationScope.number}</h2>
  <hr/>
  <a href="logout">安全退出</a>
  </body>
</html>

案例:上传图片

目标

通过案例学习文件上传组件file-upload的使用

效果

  1. 上传页面

在这里插入图片描述

  1. 上传结果

流程

  1. 复制jar包

    在这里插入图片描述

  2. 按以下流程编写上传的代码即可

在这里插入图片描述

步骤

  1. 创建文件上传的页面
  2. 复制文件上传的jar包到WEB-INF/lib目录下
  3. 编写文件上传的Servlet
    1. 创建磁盘操作工具类工厂 DiskFileItemFactory
    2. 创建核心解析类 ServletFileUpload,传入上面的工厂对象做为参数
    3. 解析Request请求,返回List结合,即分割线的每个部分内容
    4. 遍历List集合,如果是普通项,得到它的参数名和参数值。获取参数值要指定字符集,否则会有乱码。
    5. 如果是文件项则得到文件名和输入流
    6. 获取当前项目的绝对地址,通过绝对地址和得到的文件名,计算文件上传的真实地址。注:idea的bug,如果是空文件夹,服务器不会自动创建。
    7. 创建文件输出流
    8. 使用IOUtils工具类将输入流写到输出流中
    9. 关闭输入输出流
    10. 选做:使用img显示上传的图片

代码

页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>文件上传</title>
  </head>
  <body>
  <h2>文件上传</h2>
 <%--
 文件上传表单的三要素:
  1. 必须使用post方法(文件要在请求体中发送)
  2. 必须指定属性:enctype="multipart/form-data",如果没有指定这个属性,默认是application/x-www-form-urlencoded
  application/x-www-form-urlencoded:表单中所有的参数是键=值的方式发送
  multipart/form-data:将表单分成多个部分,每个部分使用一串数字进行分隔,不能通过servlet的getParameter()等方法去获取参数值
  3. 要使用文件域 type="file",必须指定name属性

  action是要提交的servlet地址
  --%>
  <form enctype="multipart/form-data" action="upload" method="post">
      用户名:
      <input type="text" name="user"> <br/>
      文件:<br/>
      <input type="file" name="file1"> <br/>
      <input type="file" name="file2"> <hr/>
      <input type="submit" value="上传文件">
  </form>
  </body>
</html>

上传的Servlet

package com.itheima.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List;

@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
        不能得到值
        String user = request.getParameter("user");
        System.out.println("用户名:" + user);
         */

        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();

        //1.创建硬盘文件的工厂类
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //2.创建一个文件上传的类,这是上传文件的核心类,需要上面的对象做为参数
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
        //3.通过上面这个类的方法解析请求的数据,生成一个List集合,集合中每个元素就是上传的一部分
        try {
            List<FileItem> fileItems = fileUpload.parseRequest(request);
            //4. FileItem表示上传的每个部分
            for (FileItem fileItem : fileItems) {
                //5. 判断是否是普通的文本值,是就返回true,不是表示这个是上传的文件
                if (fileItem.isFormField()) {  //普通文本框
                    //得到表单项的名字和值
                    String fieldName = fileItem.getFieldName();
                    String value = fileItem.getString("utf-8");
                    out.print("表单项名字:" + fieldName + ",表单项值:" + value + "<hr/>");
                }
                else {  //表示上传的文件
                    String name = fileItem.getName();   //得到文件名
                    InputStream inputStream = fileItem.getInputStream();  //得到文件输入流
                    //写到服务器上,得到文件的输出流,文件上传到out目录下/upload
                    //获取upload真实地址,注:如果upload是一个空目录,idea不会在out目录下自动创建
                    String realPath = getServletContext().getRealPath("/upload");
                    //创建文件输出流:  c:/xxx/upload/file1.txt
                    FileOutputStream outputStream = new FileOutputStream(realPath + "/" + name);
                    //使用工具类复制
                    IOUtils.copy(inputStream, outputStream);
                    //关闭流
                    outputStream.close();
                    inputStream.close();
                    //打印上传成功
                    out.print("上传文件:" + name + "成功<hr/>");
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

tResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List;

@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
不能得到值
String user = request.getParameter(“user”);
System.out.println(“用户名:” + user);
*/

    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();

    //1.创建硬盘文件的工厂类
    DiskFileItemFactory factory = new DiskFileItemFactory();
    //2.创建一个文件上传的类,这是上传文件的核心类,需要上面的对象做为参数
    ServletFileUpload fileUpload = new ServletFileUpload(factory);
    //3.通过上面这个类的方法解析请求的数据,生成一个List集合,集合中每个元素就是上传的一部分
    try {
        List<FileItem> fileItems = fileUpload.parseRequest(request);
        //4. FileItem表示上传的每个部分
        for (FileItem fileItem : fileItems) {
            //5. 判断是否是普通的文本值,是就返回true,不是表示这个是上传的文件
            if (fileItem.isFormField()) {  //普通文本框
                //得到表单项的名字和值
                String fieldName = fileItem.getFieldName();
                String value = fileItem.getString("utf-8");
                out.print("表单项名字:" + fieldName + ",表单项值:" + value + "<hr/>");
            }
            else {  //表示上传的文件
                String name = fileItem.getName();   //得到文件名
                InputStream inputStream = fileItem.getInputStream();  //得到文件输入流
                //写到服务器上,得到文件的输出流,文件上传到out目录下/upload
                //获取upload真实地址,注:如果upload是一个空目录,idea不会在out目录下自动创建
                String realPath = getServletContext().getRealPath("/upload");
                //创建文件输出流:  c:/xxx/upload/file1.txt
                FileOutputStream outputStream = new FileOutputStream(realPath + "/" + name);
                //使用工具类复制
                IOUtils.copy(inputStream, outputStream);
                //关闭流
                outputStream.close();
                inputStream.close();
                //打印上传成功
                out.print("上传文件:" + name + "成功<hr/>");
            }
        }
    } catch (FileUploadException e) {
        e.printStackTrace();
    }
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
}

}


  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值