案例:黑马旅游注册
目标
使用CSS样式排版出如图所示的效果
效果
- 表格有四行,第一行和第四行跨2列,第一列占30%的宽度,第一行是标题th,第四行是放按钮。使用图片按钮
- table居中,宽300px,高180px; 边框solid 1px gray
- td的文字对齐居中,字体大小14px
- table添加背景,不平铺,图片背景的宽度和高度与table的宽和高一样。
- 用户名和密码文本框使用类样式,也可以使用其它选择器。宽150px,高32px,边框用实线,圆角5px,1个宽度,黑色
- 使用伪类样式,当鼠标移动到文本框上的时候,变成虚线橙色边框。得到焦点,背景色变成浅黄色
- 文本框前面有头像,密码框前面有键盘,左内边距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>
案例:轮播图
目标
- 函数的应用
- 定时函数的使用
方法说明
方法 | 描述 |
---|---|
document.getElementById(“id”) | 作用:通过id获取网页上唯一的元素(标签) 参数:ID的值 |
window.setInterval(“函数名()”,时间) window.setInterval(函数名,时间) | 作用:每隔一段时间调用一次指定的函数 参数1:要调用的函数名 参数2:隔多久调用,单位是毫秒 |
需求
每过3秒中切换一张图片的效果,一共5张图片,当显示到最后1张的时候,再次显示第1张。
步骤
- 创建HTML页面,页面有一个img标签,id为pic,宽度600。
- body的背景色为黑色,内容居中。
- 五张图片的名字依次是0~4.jpg,放在项目的img文件夹下,图片一开始的src为第0张图片。
- 编写函数:changePicture(),使用setInterval()函数,每过3秒调用一次。
- 定义全局变量:num=1。
- 在changePicture()方法中,设置图片的src属性为img/num.jpg。
- 判断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秒以后跳转到另一个页面
效果
分析
- 在页面上创建一个span用于放置变化的数字。
- 定义一个全局变量为5,每过1秒调用1次自定义refresh()函数
- 编写refresh()函数,修改span中的数字
- 判断变量是否为0,如果是0则跳转到新的页面
- 否则变量减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>
案例:会动的时钟
目标
页面上有两个按钮,一个开始按钮,一个暂停按钮。点开始按钮时间开始走动,点暂停按钮,时间不动。再点开始按钮,时间继续走动。
步骤
- 在页面上创建一个h1标签,用于显示时钟,设置颜色和大小。
- 一开始暂停按钮不可用,设置disabled属性,disabled=true表示不可用。
- 创建全局的变量,用于保存计时器
- 为了防止多次点开始按钮出现bug,点开始按钮以后开始按钮不可用,暂停按钮可用。点暂停按钮以后,暂停按钮不可用,开始按钮可用。设置disabled=true。
- 点开始按钮,在方法内部每过1秒中调用匿名函数,在匿名函数内部得到现在的时间,并将得到的时间显示在h1标签内部。
- 暂停的按钮在方法内部清除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个列表框中。当选择不同的省份的时候,加载这个省份下所有的城市显示在第二个下拉列表中。
案例效果
实现步骤
- 创建两个下拉列表,一个显示省份,一个显示城市
- 注册网页加载事件:在网页加载完毕只会显示省份信息
- 给省份下拉列表注册值改变事件:在省份发生变化时更新城市下拉列表信息
示例代码
<!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中使用正则表达式进行验证。
- 用户名:只能由英文字母和数字组成,长度为4~16个字符,并且以英文字母开头
- 密码: 大小写字母和数字6-20个字符
- 确认密码:两次密码要相同
- 电子邮箱: 符合邮箱地址的格式 /^\w+@\w+(.[a-zA-Z]{2,3}){1,2}$/
- 手机号:/^1[3456789]\d{9}$/
- 生日:生日的年份在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])$/
案例分析
- 创建正则表达式
- 得到文本框中输入的值
- 如果不匹配,在后面的span中显示错误信息,返回false
- 如果匹配,在后面的span中显示一个打勾图片,返回true
- 写一个验证表单中所有的项的方法,所有的方法都返回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"> </td>
<td class="center">
<input name="" type="image" src="img/register.jpg"/>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>
案例:表格隔行换色与全选全不选
目标
- 实现隔行变色的效果
- 实现全选全不选的效果
效果
步骤
隔行变色
- 页面加载完毕,得到所有的tr。
- 使用基本过滤选择器,除了第0行,设置偶数行背景色为lightgreen
- 使用基本过滤选择器,除了第0行,设置奇数行背景色为lightyellow
全选全不选
- 给最上面的id=all的复选框添加点击事件
- 使用属性选择器选中所有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>
小结
-
选中大于0的tr的偶数行的选择器怎么写?
tr:gt(0):even
-
设置某个复选框选中的方法是什么?
jq对象.prop("checked",true)
案例:实现购物车
目标
使用今天学习的内容制作一个购物车案例
需求
- 实现添加商品,如果商品名为空,提示:商品名不能为空。如果不为空,则添加成功一行,清空文本框的内容,图片使用固定一张。
- 实现删除一行商品的功能,删除前弹出确认对话框。
效果
代码
<!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="" />
<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"
]
客户端
步骤
- 文本框失去焦点,得到文本框中的姓名
- 创建 XMLHttpRequest 请求对象
- 设置请求的 URL
- 调用 open 方法,设置提交给服务器的请求方式和访问地址
- 调用 send 方法发送请求
- 设置请求对象的 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
[
"张三",
"李四",
"王五",
"赵六",
"田七",
"孙八",
"张三丰",
"张无忌",
"李寻欢",
"王维",
"李白",
"杜甫",
"李贺",
"李逵",
"宋江",
"王英",
"鲁智深",
"武松",
"张薇",
"刘小轩",
"刘浩宇",
"刘六"
]
流程分析
分析页面组成
步骤
- 编写文本框的keyup事件
- 得到word文本框的值
- 去掉值前后的空格,判断值是否为空,如果为空,则返回不再继续。
- 否则使用$.post方法发送请求给服务器
- 在success回调函数中得到服务器返回数据:JSON格式所有用户的集合
- 创建正则表达式,匹配以^word开头的用户
- 创建一个数组用来保存符合条件的字符串
- 如果上面的数组不为空,则进行字符串拼接,每个用户是一个div。
- 拼接完成以后使用html()方法填充到#show的div中
- 如果集合为空,则隐藏#show的div
拓展
- 给#show中div添加点击事件
- 将 div 的文本内容显示在#word的值中
- 隐藏#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>
小结
- 使用文本框的键盘松开事件 keyup
- $.post() 提交数据给服务器,返回JSON集合
- 遍历集合,拼接成div的字符串
- 使用html()方法将拼接好的字符串覆盖div中原有的内容
- 显示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>
小结:使用超链接下载的不足
- 有些资源是直接打开的,不满足下载的要求
- 暴露资源的真实地址,有可能被盗链。
- 不利于业务逻辑的控制,要有积分才可以下载。
案例:使用Servlet下载文件
目标
- 实现使用Servlet的方式下载文件
- 文件名有汉字的处理
下载设置的响应头
技术点,需要设置以下响应头才可以实现下载
设置响应头 | 参数说明 |
---|---|
Content-Disposition: attachment; filename=文件名 | Content-Disposition: 浏览器打开资源的方式 attachment: 表示以附件的方式下载,而不是打开 filename: 下载到浏览器端使用的文件名 |
步骤
- 从链接上得到文件名
- 设置content-disposition头
- 得到文件的输入流
- 得到response的输出流
- 写出到浏览器端
汉字乱码原理
注: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);
}
}
小结
-
下载需要设置哪个响应头?
Content-disposition: attachment;filename=文件名
-
如果下载文件名中有汉字使用哪个方法编码?
URLEncoder.encode("文件名","utf-8")
案例:得到用户上次访问的时间
目标
-
如果用户是第一次访问,则输出:您好,您是第1次访问,欢迎您的加入!当前的时间是xxx
-
如果之前已经访问过,则从Cookie中得到上次访问的时间,显示:您好,欢迎您再次访问。
上次访问的时间是:xxxx,当前的时间是xxxxx。
-
无论是否是第一次访问,都要把这次访问服务器的时间写到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);
}
}
小结
- 从Cookie中取一个叫visitedTime的信息,如果为null,则表示第一次访问
- 否则读取Cookie的信息字符串,显示上次访问的时间
- 无论上次访问时间是否为空,都把这次访问的时间写入Cookie中
案例:利用cookie实现自动登录
案例part1:Cookie实现自动登录Servlet
目标
能够使用Cookie保存用户名和密码
结构
分析
Servlet步骤
- 得到表单提交的数据:用户名和密码
- 判断用户名和密码实现登录
- 如果登录成功,则判断是否勾选了记住我。
- 如果勾选了,则将用户名和密码写入Cookie中,保存一周的时间,并跳转到成功页面。
- 否则跳转到登录失败页面
代码
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代码步骤
- 编写工具方法,读取指定键的值
- 页面加载完成,读取cookie的信息
- 如果有username不为null,并且password也不为null
- 则将用户名和密码赋值给文件框,并且提交表单
代码
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 次添加,设置为 值为1,否则将原来的值加 1,再放进去Map中
- 更新会话
- 输出操作结果和信息
查看购物车的Servlet
- 从会话中得到购物车
- 如果为空则输出购物车为空
- 否则遍历购物车输出每一行
- 输出继续购物的链接
代码
显示商品列表页面
<!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方法提交的汉字的编码。
分析
开发步骤
- 有2个Servlet,一个是LoginServlet登录,一个是RegisterServlet注册
- 有2个JSP页面,1个是login.jsp,有表单,登录名。1个register.jsp,有表单,有注册的名字。都使用POST提交用户名使用汉字提交。
- 使用过滤器,对所有的Servlet的POST方法进行过滤。
- 在没有使用过滤器之前,每个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进行汉字编码
- 汉字编码使用哪个方法:setCharacterEncoding()
- 过滤的地址: /* 所有地址
- 放行使用哪个方法:chain.doFilter(request, response)
案例:过滤敏感词汇
需求
当用户发帖的时候,如果发出敏感词汇就进行过滤,并提示发贴失败,否则显示正常的发贴内容。
案例效果
- 在表单中输入含有非法字符的言论,点击提交按钮
- 在浏览器上显示发贴失败
- 正常发贴的情况
案例分析
- 创建一个表单用于发表言论。
- 创建一个PostWordServlet,正常接收用户的输入信息,并且打印到浏览器
- 创建一个words.txt文件,其中存入非法字符。
- 创建一个Filter,只过滤PostWordServlet。
- 在init方法中将txt文件中的非法字符读取到List集合中。注:指定字符的编码为utf-8
- 在doFilter方法中,使用获取请求中的参数,判断请求的文字中是否包含非法字符。
- 如果言论中含有非法字符,就拦截,并且提示用户非法言论,退出过滤器
- 继续执行表示没有非法字符,就放行。
分析
实现步骤
- 创建一个表单,用于发表言论
<!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>
- 创建一个txt文件,存入非法字符。要注意,文件存储使用的字符集,否则可能出现乱码。
穷逼
笨蛋
白痴
王八
贱人
傻逼
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLHsXleM-1592189481791)(C:\Users\Administrator\Desktop\study\zimg\1592189067781.png)]
- 创建一个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);
}
}
- 创建一个过滤器,用来拦截请求,过滤请求中发表的言论的非法字符
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);
}
}
小结
过滤敏感词汇过滤器的开发步骤:
- 在init中从文件中加载所有的敏感词汇,复习缓存字符流
- 判断用户发贴是否包含这些词汇之一
- 如果有就拦截,没有就放行
案例:统计网站当前在线人数
执行效果
页面
服务器控制台信息
分析
每当一个用户访问项目的时候,都会创建一个session会话。所以当前session会话被创建,当前在线用户+1,每当session会话被销毁,当前在线用户-1。
HttpSessionListener可以用来监听session对象的创建和销毁的。所以可以在HttpSessionListener中的监听session对象创建和销毁的方法中控制在线人数的加减。
步骤
-
创建一个监听器 SessionCountListener
a) 监听会话创建的方法
i. 在会话创建的监听方法中,从上下文域中取出当前计数值
ii. 如果为空,表示是第1个用户,设置值为1
iii. 不为空则加1,并且更新上下文域
b) 监听会话销毁的方法
i. 从上下文域中得到当前在线的人数
ii. 减1后更新
-
创建一个注销的LogoutServlet
a) 让会话失效
b) 打印输出:您已经安全退出网站
-
编写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的使用
效果
- 上传页面
- 上传结果
流程
-
复制jar包
-
按以下流程编写上传的代码即可
步骤
- 创建文件上传的页面
- 复制文件上传的jar包到WEB-INF/lib目录下
- 编写文件上传的Servlet
- 创建磁盘操作工具类工厂 DiskFileItemFactory
- 创建核心解析类 ServletFileUpload,传入上面的工厂对象做为参数
- 解析Request请求,返回List结合,即分割线的每个部分内容
- 遍历List集合,如果是普通项,得到它的参数名和参数值。获取参数值要指定字符集,否则会有乱码。
- 如果是文件项则得到文件名和输入流
- 获取当前项目的绝对地址,通过绝对地址和得到的文件名,计算文件上传的真实地址。注:idea的bug,如果是空文件夹,服务器不会自动创建。
- 创建文件输出流
- 使用IOUtils工具类将输入流写到输出流中
- 关闭输入输出流
- 选做:使用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);
}
}