一、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;
}
}
2、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访问和当前页面不同源的服务器
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 - 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 {
public void init(FilterConfig filterConfig) throws ServletException { }
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);
}
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 style="background: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578736051725&di=76768c9735298e1e0422b1930727c243&imgtype=0&src=http%3A%2F%2Fimg.pptjia.com%2Fimage%2F20190112%2F43847e4d1e0b3af9623f502c45148e15.jpg)">
<div class="form">
<%--个人信息--%>
<div id="content">
<form action="/user/editUserInfo">
<div class="heading"><h2>个人信息中心</h2></div>
<table class="table">
<tr>
<th>头 像:</th>
<td>
<c:if test="${userInfo.icon == null}">
<img id="img" onclick="openFile()" src="http://office-1256119282.file.myqcloud.com/20190730/office-cn/official-web/static/img/ic_logo@2x.png">
</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">
</td>
</tr>
<tr>
<th>昵 称:</th>
<td><input class="inp" name="nickName" value="${user.nickName}"></td>
</tr>
<tr>
<th>真实姓名:</th>
<td><input class="inp" name="realName" value="${userInfo.realName}"></td>
</tr>
<tr>
<th>性 别:</th>
<td>
<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> > 女
</td>
</tr>
<tr>
<th>邮 箱:</th>
<td><input class="inp" name="email" value="${userInfo.email}"></td>
</tr>
<tr>
<th>电 话:</th>
<td><input class="inp" name="phone" value="${userInfo.phone}"></td>
</tr>
<tr>
<th>住 址:</th>
<td><input class="inp" name="address" value="${userInfo.address}"></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" class="btn" value=修改个人信息>
<a href="//www.greatytc.com/shop/toMainPage">返 回</a>
</td>
</tr>
</table>
</form>
</div>
<hr><hr>
<div id="addresses">
<a href="javascript:void(0)" onclick="addAddress()">添 加</a><br><br>
</div>
<%--添加--%>
<div class="form-group" id="add" style="display: none">
<table class="table">
<tr><td colspan="2"><div class="heading">添加地址</div></td></tr>
<tr>
<td>收货地址:</td>
<td><input class="inp" id="addAddress"></td>
</tr>
<tr>
<td colspan="2">
<button class="btn" onclick="addAddressById()">添 加</button>
    
<button class="btn" onclick="cancel()">取 消</button>
</td>
</tr>
</table>
</div><br>
</div>
<br><br>
</body>
2、修改用户信息
我们在用户信息详情页面已经展示了用户信息。还提供了修改用户信息的操作,用户点击修改按钮就会触发Ajax操作与数据库后台进行交互,完成用户信息的修改,修改完成之后通过Ajax重新请求当前页面。
<script>
//预加载
$(function () {
getAddresses();
});
//取消添加
function cancel(){
$("#content").show(1000);
$("#add").hide(1000);
}
//打开添加框
function addAddress(){
$("#content").hide(1000);
$("#add").show(1000);
}
//删除地址
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 editAddressById(id) {
var content = $("#address").val();;
$.ajax({
url:"/user/editAddressById",
type:"post",
data:{id:id,content:content},
dataType:"json",
success:function (data) {
if(data.code==-1) {
alert(data.message);
} else {
$("#addresses").empty();
getAddresses();
location.reload();
}
}
});
}
//添加地址
function addAddressById() {
var content = $("#addAddress").val();
$.ajax({
url:"/user/addAddressById",
type:"post",
data:{content:content},
dataType:"json",
success:function (data) {
if(data.code==-1) {
alert(data.message);
} else {
$("#addresses").empty();
getAddresses();
location.reload();
}
}
});
}
//获取用的所有收货地址
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 = "<br><input id='address' class='inp' value='"+item.content+"'>     " +
"<a href='javascript:void(0)' onclick='deleteAddressById("+item.id+")'>删 除</a> " +
"<a href='javascript:void(0)' onclick='editAddressById("+item.id+",this)'>修 改</a> <br><br>";
$("#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拼接
- 用户在购买商品的时候,应该可以进行价格排序与数量排序,既可以正序,也可以倒序,想要解决这个问题就需要在JavaBean中再设置两个字段,这两个字段是字符串用来记录价格排序与数量排序。根据前台发送过来的数据将这两个字符串放进SQL语句中。
package com.shop.dao;
import com.shop.bean.Goods;
import com.utils.DBUtils;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class ShopDao {
/**
* 根据id查找商品详情信息
* @param goods
* @return
*/
public Goods getGoodsById(Goods goods) {
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
ps = cn.prepareStatement("select g.*,t.name as typeName from goods g left join types t on t.id = g.typeId where g.id = ?");
ps.setInt(1,goods.getId());
rs = ps.executeQuery();
Goods goodsBean = DBUtils.selectOne(Goods.class,rs);
return goodsBean;
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return null;
}
/**
* 获取全部上架商品
* @param goods
* @return
*/
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 ";
//有商品名称, 拼接
if(goods.getName()!=null){
sql += " and name like ? ";
objects.add("%"+goods.getName()+"%");
}
//有类型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;
if (goods.getPriceOrder()!=null&&!goods.getPriceOrder().equals("")){
if (order){
sql += " order by ";
order = false;
} else {
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;
}
/**
* 获取上架商品数量
* @param goods
* @return
*/
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;
}
}
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="/resource/pages/header.jsp"%>
<html>
<head>
<title>商城首页</title>
<style>
body { font-family: 楷体; }
div { text-align: center; }
.goods{ display: inline-block; }
img {
width: 200px;
height: 200px;
}
</style>
<link rel="stylesheet" href="/resource/css/MyCss.css">
</head>
<body style="background: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578736051725&di=76768c9735298e1e0422b1930727c243&imgtype=0&src=http%3A%2F%2Fimg.pptjia.com%2Fimage%2F20190112%2F43847e4d1e0b3af9623f502c45148e15.jpg)">
<div class="form">
<div class="heading">
<h3>商城商品页面</h3>
</div>
<div class="form-group">
<form id="form" action="//www.greatytc.com/shop/toMainPage">
<input id="search" class="inp" name="name" value="${goods.name}"> <button class="btn">搜索</button>
<br>类型 :
<c:forEach items="${types}" var="item">
${item.name}
<input type="checkbox" name="typeIds" value="${item.id}" onclick="check()"
<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 class="btn" onclick="priceOrder('price ASC')">价格排序</button>
</c:if>
<c:if test="${goods.priceOrder eq 'price ASC'}">
<button class="btn" onclick="priceOrder('price DESC')">价格正序</button>
</c:if>
<c:if test="${goods.priceOrder eq 'price DESC'}">
<button class="btn" onclick="priceOrder('price ASC')">价格倒序</button>
</c:if>
<%--数量排序--%>
<c:if test="${empty goods.numOrder}">
<button class="btn" onclick="numOrder('num ASC')">数量排序</button>
</c:if>
<c:if test="${goods.numOrder eq 'num ASC'}">
<button class="btn" onclick="numOrder('num DESC')">数量正序</button>
</c:if>
<c:if test="${goods.numOrder eq 'num DESC'}">
<button class="btn" onclick="numOrder('num ASC')">数量倒序</button>
</c:if>
</div>
<div class="form-group">
<c:forEach items="${list}" var="item">
<div class="goods">
<img src="${item.icon}">
<div>${item.name}</div>
<div style="color: lawngreen;font-weight: 600;">${item.price}¥</div>
<div style="color: blue">${item.num}件</div>
<button class="btn" onclick="toGoodsInfo(${item.id})">加入购物车</button>
</div>
</c:forEach>
</div><br>
<div class="paging">
${paging}
</div>
</div>
</body>
<script src="/resource/js/jquery.js"></script>
<script>
function check(){
$("#form").submit();
}
function priceOrder(po){
$("#priceOrder").val(po);
$("#form").submit();
}
function numOrder(po){
$("#numOrder").val(po);
$("#form").submit();
}
function toGoodsInfo(goodsId){
location.href = "/shop/toGoodsInfoPage?id="+goodsId;
}
</script>
</html>