第十四篇学习总结
一、Ajax
Ajax可以让浏览器在不刷新页面的情况下。利用js代码和后台进行交互,这种交互方式称之为异步。
1、原生js的Ajax
//获取异步对象
var ajax = new XMLHttpRequest();
//设置数据, method: 请求模式 url: 地址 arsc: 是否异步
ajax.open(method,url,async);
//data: 数据
ajax.send(data);
//定义个请求过程中每个状态变化时的回调函数
ajax.onreadystatechange = function () {
//当请求进行到第四阶段,也就是有数据返回时,和返回的状态值是200时
if(ajax.readyState==4&&ajax.status==200){
//获取返回的数据
var resp = ajax.responseText;
}
}
3、jQuery的Ajax
$.ajax({
url:"请求地址",
type:"请求模式",
data:{"数据名":"数据值"},//js对象
contentType:"application/json",//默认"application/x-www-form-urlencoded"
dataType:"json",//默认是 text , js 会按照预制的数据格式解析数据
success:function(data){ //请求成功返回之后调用的方法 data:返回的数据
}
})
二、跨域解决方案CORS
1、同源策略
- 同源:ip地址+端口号相同
- 浏览器禁止js访问和当前页面不同源的服务器
浏览器之所以禁止js访问和当前页面不同源的服务器,是因为浏览器发送到服务器的请求会默认携带cookie信息,如果用户访问了恶意页面,恶意页面中会有Ajax悄悄的访问用户
2、跨域
有的时候,我们自己的两个服务器之间需要相互访问,这时就必须要跨域了,比如说图片服务器和本地服务器。要解决跨域问题,就需要浏览器和服务器相互配合,浏览器发出跨域请求,服务器允许跨域请求
3、两种请求
简单请求与非简单请求
3.1 简单请求
符合简单请求必须满足以下两个条件:
- 请求方法是head、get、post这三种方法之一
- content-type只限于三种类型:application/x-www-form-urlencoded、multipart/form-data、text/plain
- 不自定义字段
3.2 简单请求基本流程
- 浏览器如果发现是跨域请求, 就会在请求头中添加Origin字段,该字段的值为当前域名
- 服务器收到请求后,检查这个字段的值, 判断是否允许这个域名下的请求跨域进来
- 如果允许的话, 就需要在响应中返回特殊字段, 字段的值要设置为允许请求的域名
- 当浏览器接受到请求,检查服务返回的特殊字段,如果这个特殊字段中记录的值是符合当前域名, 才会将返回的数据传递给js的接收方法,否则就报错
响应中的特殊字段:
Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin
字段的值,要么是一个*
,表示接受任意域名的请求。
Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true
,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true
,如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar
字段的值。
withCredentials
如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段,另一方面,开发者必须在AJAX请求中打开withCredentials
属性
但是,如果省略withCredentials
设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials
= false
无论服务器是否允许跨域请求,浏览器都能接收到返回的数据,只是如果服务器不允许的话,浏览器是不会将数据交给发起请求的js代码的
3.3 非简单请求基本流程
-
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),就请求当前url指向的Servlet中的 OPTIONS方法
-
如果 OPTIONS 方法返回的信息中没有允许的特殊字段, 浏览器就会拒绝发起正式请求
-
除了
Origin
字段,"预检"请求的头信息包括两个特殊字段。Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是
PUT
。Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是
X-Custom-Header
-
响应字段
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
4、文件服务器
将文件但粗存放在一个服务器上有很多好处,此时就需要用到跨域请求,解决跨域请求的最佳方式是在过滤器中对特定源的请求允许跨域
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) servletResponse;
resp.setHeader("Access-Control-Allow-Origin","http://localhost:8090");//* 表示允许所有
resp.setHeader("Access-Control-Allow-Methods","post, get");//表示允许post与get请求
filterChain.doFilter(servletRequest,resp);
}
@Override
public void destroy() {
}
}
三、用户信息的增删改查
1、显示用户详情
在用户详情界面应该展示用户的详细信息,比如用户名、头像、昵称。要完成这些,首先要拿到用户的id,用户的信息在登录的时候就已经存在session中了,可以根据这个来从数据库中查询用户的详细信息,然后放进response中,转发到jsp页面。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
UserBean userBean = (UserBean) req.getSession().getAttribute("user");
int id = userBean.getId();
UserInfo userInfo = userInfoService.getUserInfoByUserId(id);
req.setAttribute("userInfo",userInfo);
req.getRequestDispatcher("/WEB-INF/pages/userInfo.jsp").forward(req,resp);
}
<body>
<!-- 将用户信息放在表单中,便于用户修改个人信息 -->
<form action="/user/editUserInfo">
<!--<input type="hidden" value="${userInfo.id}" name="id">-->
头像 :
<!-- 如果用户没有头像显示一个默认头像 -->
<c:if test="${userInfo.icon==null}">
<img id="img" onclick="openFile()" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578898078047&di=ec0f9cae6ae4f21bf4fdaaf14644c26f&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201802%2F20%2F20180220165946_RiGPS.thumb.700_0.jpeg">
</c:if>
<c:if test="${userInfo.icon != null}">
<img id="img" src="${userInfo.icon}" onclick="openFile()">
</c:if>
<input type="hidden" name="icon" value="${userInfo.icon}" id="icon">
<input type="file" id="file" onchange="upload()" style="display: none">
<br>
昵称 : <input name="nickName" value="${user.nickName}"> <br>
真实姓名 : <input name="realName" value="${userInfo.realName}"> <br>
性别 : <input type="radio" name="sex" value="1"
<c:if test="${userInfo.sex==1}"> checked="checked" </c:if>
>男
<input type="radio" name="sex" value="2"
<c:if test="${userInfo.sex==2}"> checked="checked" </c:if>
> 女 <br>
邮箱 : <input name="email" value="${userInfo.email}"> <br>
电话 : <input name="phone" value="${userInfo.phone}"> <br>
住址 : <input name="address" value="${userInfo.address}"> <br>
<input type="submit">
</form>
<hr>
<div id="addresses">
</div>
</body>
2、修改用户信息
我们在用户信息详情页面已经展示了用户信息。还提供了修改用户信息的操作,用户点击修改按钮就会触发Ajax操作与数据库后台进行交互,完成用户信息的修改,修改完成之后通过Ajax重新请求当前页面。
<script>
//预加载
$(function () {
getAddresses();
})
function deleteAddressById(id){
$.ajax({
url:"/user/deleteAddressById",
type:"post",
data:{id:id},
dataType:"json",
success:function (data) {
if(data.code==-1){
alert(data.message);
}else{
//清空地址之后再重新获取
$("#addresses").empty();
getAddresses();
}
}
})
}
//获取用的所有收货地址
function getAddresses(){
$.ajax({
url:"/user/getAddress",
type:"post",
dataType:"json",
success:function (data) {
if(data.code==-1){
alert(data.message);
}else{
var list = data.data;
list.forEach(function(item){
var html = "<input value='"+item.content+"'></input> <a href='javascirpt:void(0)' onclick='deleteAddressById("+item.id+")'>删除</a>"
$("#addresses").append(html);
})
}
}
})
}
function openFile(){
$("#file").click();
}
function upload(){
var formData = new FormData();
formData.append("file",$("#file")[0].files[0]);
$.ajax({
url:"http://localhost:8070/fileserver/file/upload",
type:"post",
data:formData,
dataType:"json",
contentType:false,
processData: false,
success:function(data){
if(data.errno==0){
alert("上传成功");
$("#icon").val(data.data[0]);
$("#img").attr("src",data.data[0]);
}else{
alert("上传失败");
}
}
})
}
</script>
四、商品条件筛选
应当提供商品名称的搜索以及商品类型的筛选,但是有一个问题,商品类型这个参数用户可以不选,可以选一个,也可以选多个,处理这种问题需要SQL拼接
public class ShopDao {
public List<Goods> getGoods(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
//拼接预制型sql语句
//sql语句中每拼接一个问号, 就需要往数组中存入一个对应的值,
// 这样,我们就可以知道总共有几个问号,而且对应的值的顺序还是正确的
List<Object> objects = new ArrayList<>();
//基础sql语句
String sql = "select * from goods where status=1 ";
//如果有商品名称传递过来, 拼接到sql中
if(goods.getName()!=null){
sql += " and name like ?";
objects.add("%"+goods.getName()+"%");
}
//如果有类型id传递过来,拼接到sql中,注意, 类型id可能时多个
if(goods.getTypeIds()!=null){
sql += " and typeId in (" ;
int[] typeIds = goods.getTypeIds(); //遍历类型id
for (int i = 0; i < typeIds.length; i++) {
if(i==typeIds.length-1){
sql += "? )";
}else{
sql += "? , ";
}
}
objects.add(goods.getTypeIds());
}
ps = cn.prepareStatement(sql+" limit ? , ?");
//设置sql语句对应的参数值
int n = 0; //记录已经设置了多少个参数值
for (Object object : objects) {
//如果拿到的值时一个数组, 需要转换并遍历
if(object.getClass().isArray()){
int[] typeIds = (int[]) object;
for (int i = 0; i <typeIds.length; i++) {
n++;
ps.setObject(n,typeIds[i]);
}
}else{
n++;
ps.setObject(n,object);
}
}
ps.setInt(n+1,goods.getStart());
ps.setInt(n+2,goods.getPageSize());
rs = ps.executeQuery();
List<Goods> list = DBUtils.selectMore(Goods.class,rs);
return list;
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return null;
}
public int getGoodsCount(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
List<Object> objects = new ArrayList<>();
String sql = "select count(1) from goods where status=1 ";
if(goods.getName()!=null){
sql += " and name like ?";
objects.add("%"+goods.getName()+"%");
}
if(goods.getTypeIds()!=null){
sql += " and typeId in (" ;
int[] typeIds = goods.getTypeIds();
for (int i = 0; i < typeIds.length; i++) {
if(i==typeIds.length-1){
sql += "? )";
}else{
sql += "? , ";
}
}
objects.add(goods.getTypeIds());
}
ps = cn.prepareStatement(sql);
int n = 0;
for (Object object : objects) {
if(object.getClass().isArray()){
int[] typeIds = (int[]) object;
for (int i = 0; i <typeIds.length; i++) {
n++;
ps.setObject(n,typeIds[i]);
}
}else{
n++;
ps.setObject(n,object);
}
}
rs = ps.executeQuery();
if(rs.next()){
return rs.getInt(1);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return 0;
}
}
五、商品排序
用户在购买商品的时候,应该可以进行价格排序与数量排序,既可以正序,也可以倒序,想要解决这个问题就需要在JavaBean中再设置两个字段,这两个字段是字符串用来记录价格排序与数量排序。根据前台发送过来的数据将这两个字符串放进SQL语句中。
import com.qianfeng.utils.BaseBean;
import java.sql.Timestamp;
import java.util.Arrays;
public class Goods extends BaseBean {
private int id;
private int userId;
private String name;
private double price;
private int num;
private String img;
private String content;
private Timestamp createTime;
private int typeId;
private int status;
private int[] typeIds;
private String typeName;
private String userName;
private String priceOrder;//用来记录价格排序
private String numOrder; //用来记录数量排序
public List<Goods> getGoods(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
//拼接预制型sql语句
//sql语句中每拼接一个问号, 就需要往数组中存入一个对应的值,
//// 这样,我们就可以知道总共有几个问号,而且对应的值的顺序还是正确的
List<Object> objects = new ArrayList<>();
//基础sql语句
String sql = "select * from goods where status=1 ";
//如果有商品名称传递过来, 拼接到sql中
if(goods.getName()!=null){
sql += " and name like ?";
objects.add("%"+goods.getName()+"%");
}
//如果有类型id传递过来,拼接到sql中,注意, 类型id可能时多个
if(goods.getTypeIds()!=null){
sql += " and typeId in (" ;
int[] typeIds = goods.getTypeIds(); //遍历类型id
for (int i = 0; i < typeIds.length; i++) {
if(i==typeIds.length-1){
sql += "? )";
}else{
sql += "? , ";
}
}
objects.add(goods.getTypeIds());
}
boolean order = true; //表示是否已经拼接的 order by 关键字 true: 没有拼接 false: 已经拼接
//拼接排序的sql语句
if(goods.getPriceOrder()!=null&&!goods.getPriceOrder().equals("")){
//如果order为true,说明没有拼接过order by, 应该拼接上
if(order){
sql += " order by ";
order = false;
}else{ //否则,说明order by已经拼接过了, 而且order by 后面应该有排序方式, 为了拼接当前排序方式, 需要先拼接一个 逗号
sql += " , ";
}
sql += goods.getPriceOrder(); //拼接具体的排序字段和方式
}
if(goods.getNumOrder()!=null&& !goods.getNumOrder().equals("")){
if(order){
sql += " order by ";
order = false;
}else{
sql += " ,";
}
sql += goods.getNumOrder();
}
ps = cn.prepareStatement(sql+" limit ? , ?");
//设置sql语句对应的参数值
int n = 0; //记录已经设置了多少个参数值
for (Object object : objects) {
//如果拿到的值时一个数组, 需要转换并遍历
if(object.getClass().isArray()){
int[] typeIds = (int[]) object;
for (int i = 0; i <typeIds.length; i++) {
n++;
ps.setObject(n,typeIds[i]);
}
}else{
n++;
ps.setObject(n,object);
}
}
ps.setInt(n+1,goods.getStart());
ps.setInt(n+2,goods.getPageSize());
rs = ps.executeQuery();
List<Goods> list = DBUtils.selectMore(Goods.class,rs);
return list;
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return null;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="/resource/pages/header.jsp"%>
<html>
<head>
<title>商城首页</title>
<style>
.goods img {
width: 251px;
height: 200px;
}
.goods{
display: inline-block;
}
.paging {
text-align: center;
}
.paging a{
margin: 0px 10px;
}
</style>
</head>
<body>
<div>
<form action="/shop/toMainPage" id="form">
<input id="search" name="name" value="${goods.name}"> <button>搜索</button> <br>
类型 :
<c:forEach items="${types}" var="item">
<%--form表单提交参数的时候,可以一个参数对应多个值--%>
${item.name}<input type="checkbox" name="typeIds" value="${item.id}" onclick="cc()"
<c:forEach items="${goods.typeIds}" var="typeId">
<c:if test="${typeId==item.id}">checked="checked"</c:if>
</c:forEach>
>
</c:forEach>
<br>
<%--//用来记录主上一次发送到后台的排序方式--%>
<input type="hidden" value="${goods.priceOrder}" name="priceOrder" id="priceOrder">
<input type="hidden" value="${goods.numOrder}" name="numOrder" id="numOrder">
</form>
</div>
<div>
<%--判断回显的排序方式来显示对应的按钮--%>
排序 :
<c:if test="${empty goods.priceOrder}">
<button onclick="priceOrder('price ASC')">价格排序</button>
</c:if>
<c:if test="${goods.priceOrder eq 'price ASC'}">
<button onclick="priceOrder('price DESC')">价格正序</button>
</c:if>
<c:if test="${goods.priceOrder eq 'price DESC'}">
<button onclick="priceOrder('price ASC')">价格倒序</button>
</c:if>
<c:if test="${empty goods.numOrder}">
<button onclick="numOrder('num ASC')">数量排序</button>
</c:if>
<c:if test="${goods.numOrder eq 'num ASC'}">
<button onclick="numOrder('num DESC')">数量正序</button>
</c:if>
<c:if test="${goods.numOrder eq 'num DESC'}">
<button onclick="numOrder('num ASC')">数量倒序</button>
</c:if>
</div>
<div>
<c:forEach items="${list}" var="item">
<div class="goods">
<img src="${item.img}" onclick="toGoodsInfo(${item.id})">
<div>${item.name}</div>
<div>${item.price}</div>
<div>${item.num}</div>
<button onclick="addCart(${item.id})">加入购物车</button>
</div>
</c:forEach>
<div >${paging}</div>
</div>
</body>
<script>
function toGoodsInfo(goodsId){
location.href = "/shop/toGoodsInfoPage?id="+goodsId;
}
//点击排序按钮,将字符串值存入到form表单中的input框中,提交form表单
function priceOrder(po){
$("#priceOrder").val(po);
$("#form").submit();
}
function numOrder(po){
$("#numOrder").val(po);
$("#form").submit();
}
function cc(){
$("#form").submit();
}
</script>
</html>
网友评论