首页 > 其他分享 >图书商城前台功能实现

图书商城前台功能实现

时间:2023-05-26 10:22:05浏览次数:45  
标签:return String req jsps book jsp 前台 商城 图书

前台功能实现

1 用户模块

  用户模块功能包括:注册、激活、登录、退出、修改密码。

1.1 创建用户模块相关类

在每个模块开始时,都要创建如下基本类:实体类、DAO类、Service类、Servlet类:

 

1.1.1 User

  User类作为实体类需要与数据库表对应,即t_user表对象。而且User类还要用来封装表单数据,所以User类还要与表单对应。

User类对照着t_user表来写即可。我们要保证User类的属性名称与t_user表的列名称完全相同。

User.java

public class User {

private String uid;

private String loginname;

private String loginpass;

private String reloginpass;

private String email;

private String verifyCode;

private boolean status;

private String activationCode;

}

 

1.1.2 UserDao

UserDao封装了对数据库的基本操作。UserDao需要使用TxQueryRunner来完成对数据库的操作。

因为刚开始准备User模块,还不知道要完成怎样的功能,所以我们现在不用在UserDao中添加任何方法,但我们可以给出一个TxQueryRunner类型的成员。

UserDao.java

public class UserDao {

private TxQueryRunner qr = new TxQueryRunner();

}

 

1.1.3 UserService

UserService封装了业务功能,在UserService中每个方法对应一个业务功能,例如:注册方法、登录方法等等。一个业务方法可能需要多次调用DAO中的方法!所以,Service依赖Dao,我们需要在UserService中给出一个UserDao类型的成员。

UserService.java

public class UserService {

private UserDao userDao = new UserDao();

}

 

1.1.4 UserServlet

UserServlet用来接收客户端请求,处理与WEB相关的问题。例如获取客户端的请求参数,然后转发或重定向等。在UserServlet中完成业务功能需要使用UserService,所以我们需要在UserServlet中给出一个UserService的成员。

因为可以让一个Servlet中有多个请求处理方法,我们让UserServlet继承BaseServlet!

UserServlet.java

public class UserServlet extends BaseServlet {

private UserService userService = new UserService();

}

 

1.2 用户注册

1.2.1 regist.jsp

1.2.1.1 regist.jsp页面功能实现

 

注册从regist.jsp页面开始。我们需要在regist.jsp页面中对表单数据使用JQuery进行校验。当用户在文本框中输入数据后,光标离开文件框时对数据进行校验!如果校验未通过,会在文本框后台显示错误信息。

l 用户名校验:

  • 用户名不能为空;
  • 用户名长度必须在3 ~ 20之间;
  • 用户名已被注册(需要异步访问服务器)。

l 登录密码校验:

  • 密码不能为空;
  • 密码长度必须在3 ~ 10之间;

l 确认密码校验:

  • 确认密码不能为空;
  • 两次输入不一致;

l Email校验:

  • Email不能为空;
  • Email格式错误(/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/);
  • Email已被注册(需要异步访问服务器);

l 验证码校验:

  • 验证码不能为空;
  • 验证码错误(需要异步访问服务器);

 

当点击“立即注册”按钮时,还要对表单每项进行校验!因为一开始可能填写了正确的验证码,所以光标离开时没有错误,但用户又点击了“换一张”链接,这时填入的验证码就是错误的了,所以我们需要在提交表单时再次进行校验。

regist.js

/*

 * 初始化时要执行的内容:

 */

$(function() {

/*

 * 1. 让注册按钮得到和失败光标时切换图片

 */

$("#submit").hover(

function() {

$("#submit").attr("src", "/goods/images/regist2.jpg");

},

function() {

$("#submit").attr("src", "/goods/images/regist1.jpg");

}

);

 

/*

 * 2. 给注册按钮添加submit()事件,完成表单校验

 */

$("#submit").submit(function(){

var bool = true;

$(".input").each(function() {

var inputName = $(this).attr("name");

bool = invokeValidateFunction(inputName);

})

return bool;

});

/*

 * 3. 输入框得到焦点时隐藏错误信息

 */

$(".input").focus(function() {

var inputName = $(this).attr("name");

$("#" + inputName + "Error").css("display", "none");

});

 

/*

 * 4. 输入框推动焦点时进行校验

 */

$(".input").blur(function() {

var inputName = $(this).attr("name");

invokeValidateFunction(inputName);

})

});

 

/*

 * 输入input名称,调用对应的validate方法。

 * 例如input名称为:loginname,那么调用validateLoginname()方法。

 */

function invokeValidateFunction(inputName) {

inputName = inputName.substring(0, 1).toUpperCase() + inputName.substring(1);

var functionName = "validate" + inputName;

return eval(functionName + "()");

}

 

/*

 * 校验登录名

 */

function validateLoginname() {

var bool = true;

$("#loginnameError").css("display", "none");

var value = $("#loginname").val();

if(!value) {// 非空校验

$("#loginnameError").css("display", "");

$("#loginnameError").text("用户名不能为空!");

bool = false;

} else if(value.length < 3 || value.length > 20) {//长度校验

$("#loginnameError").css("display", "");

$("#loginnameError").text("用户名长度必须在3 ~ 20之间!");

bool = false;

} else {// 是否被注册过

$.ajax({

cache: false,

async: false,

type: "POST",

dataType: "json",

data: {method: "validateLoginname", loginname: value},

url: "/goods/UserServlet",

success: function(flag) {

if(flag) {

$("#loginnameError").css("display", "");

$("#loginnameError").text("用户名已被注册!");

bool = false;

}

}

});

}

return bool;

}

 

/*

 * 校验密码

 */

function validateLoginpass() {

var bool = true;

$("#loginpassError").css("display", "none");

var value = $("#loginpass").val();

if(!value) {// 非空校验

$("#loginpassError").css("display", "");

$("#loginpassError").text("密码不能为空!");

bool = false;

} else if(value.length < 3 || value.length > 20) {//长度校验

$("#loginpassError").css("display", "");

$("#loginpassError").text("密码长度必须在3 ~ 20之间!");

bool = false;

}

return bool;

}

 

/*

 * 校验确认密码

 */

function validateReloginpass() {

var bool = true;

$("#reloginpassError").css("display", "none");

var value = $("#reloginpass").val();

if(!value) {// 非空校验

$("#reloginpassError").css("display", "");

$("#reloginpassError").text("确认密码不能为空!");

bool = false;

} else if(value != $("#loginpass").val()) {//两次输入是否一致

$("#reloginpassError").css("display", "");

$("#reloginpassError").text("两次密码输入不一致!");

bool = false;

}

return bool;

}

 

/*

 * 校验Email

 */

function validateEmail() {

var bool = true;

$("#emailError").css("display", "none");

var value = $("#email").val();

if(!value) {// 非空校验

$("#emailError").css("display", "");

$("#emailError").text("Email不能为空!");

bool = false;

} else if(!/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(value)) {//格式校验

$("#emailError").css("display", "");

$("#emailError").text("错误的Email格式!");

bool = false;

} else {//Email是否被注册过

$.ajax({

cache: false,

async: false,

type: "POST",

dataType: "json",

data: {method: "validateEmail", email: value},

url: "/goods/UserServlet",

success: function(flag) {

if(flag) {

$("#emailError").css("display", "");

$("#emailError").text("Email已被注册!");

bool = false;

}

}

});

}

return bool;

}

 

/*

 * 校验验证码

 */

function validateVerifyCode() {

var bool = true;

$("#verifyCodeError").css("display", "none");

var value = $("#verifyCode").val();

if(!value) {//非空校验

$("#verifyCodeError").css("display", "");

$("#verifyCodeError").text("验证码不能为空!");

bool = false;

} else if(value.length != 4) {//长度不为4就是错误的

$("#verifyCodeError").css("display", "");

$("#verifyCodeError").text("错误的验证码!");

bool = false;

} else {//验证码是否正确

$.ajax({

cache: false,

async: false,

type: "POST",

dataType: "json",

data: {method: "validateVerifyCode", verifyCode: value},

url: "/goods/UserServlet",

success: function(flag) {

if(!flag) {

$("#verifyCodeError").css("display", "");

$("#verifyCodeError").text("错误的验证码!");

bool = false;

}

}

});

}

return bool;

}

 

1.2.1.2 UserServlet对前端异步请求的支持

regist.jsp页面中有异步请求服务器来对表单进行校验:

l 校验登录名是否已注册过;

l 校验Email是否已注册过;

l 校验验证码是否正确。

这说明在UserServlet中需要提供相应的方法来支持前端的请求。

UserServlet.java

public class UserServlet extends BaseServlet {

private UserService userService = new UserService();

 

/**

 * 异步校验登录名

 * @param req

 * @param resp

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String validateLoginname(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String loginname = req.getParameter("loginname");

boolean flag = userService.validateLoginname(loginname);//如果登录名已被注册返回true

resp.getWriter().print(flag + "");

return null;

}

 

/**

 * 异步校验Email

 * @param req

 * @param resp

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String validateEmail(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String email = req.getParameter("email");

boolean flag = userService.validateEmail(email);//如果Email已被注册返回true

resp.getWriter().print(flag + "");

return null;

}

 

/**

 * 异步校验验证码

 * @param req

 * @param resp

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String validateVerifyCode(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String vCode = (String) req.getSession().getAttribute("vCode");

String verifyCode = req.getParameter("verifyCode");

boolean flag = vCode.equalsIgnoreCase(verifyCode);//如果验证码正确返回true

resp.getWriter().print(flag + "");

return null;

}

}

 

相应的,在UserDao和UserService中也需要提供方法!

UserDao.java

public class UserDao {

private QueryRunner qr = new TxQueryRunner();

 

/**

 * 校验指定登录名会员是否存在

 * @param loginname

 * @return

 * @throws SQLException

 */

public boolean validateLoginname(String loginname) throws SQLException {

String sql = "select count(1) from t_user where loginname=?";

Number cnt = (Number)qr.query(sql, new ScalarHandler(), loginname);

return cnt == null ? false : cnt.intValue() > 0;

}

 

/**

 * 校验指定email的会员是否存在

 * @param email

 * @return

 * @throws SQLException

 */

public boolean validateEmail(String email) throws SQLException {

String sql = "select count(1) from t_user where email=?";

Number cnt = (Number)qr.query(sql, new ScalarHandler(), email);

return cnt == null ? false : cnt.intValue() > 0;

}

}

 

UserService.java

public class UserService {

private UserDao userDao = new UserDao();

 

/**

 * 校验指定登录名的会员是否存在

 * @param loginname

 * @return

 */

public boolean validateLoginname(String loginname) {

try {

return userDao.validateLoginname(loginname);

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

 

/**

 * 校验指定Email的会员是否存在

 * @param loginname

 * @return

 */

public boolean validateEmail(String email) {

try {

return userDao.validateEmail(email);

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

}

 

1.3 UserServlet#regist()

当表单校验通过后,客户端会请求UserServlet#regist() 方法。regist()方法的工作内容如下:

l 封装表单数据到User对象中;

l 对User对象数据进行服务器端校验;

  • 如果校验失败,把错误信息保存到Map中;
  • 把Map保存到request中;
  • 把user保存到request,用来在表单中回显;
  • 转发到regist.jsp页面,return;

l 调用UserService#regist(User)方法完成注册;

  • 对user进行数据补全:uid、activationCode、status;
  • 通过userDao的add(User)方法完成向数据库表插入记录;

² 使用TxQueryRunner的update()完成插入记录;

  • 发送激活邮件。

l 保存成功信息,转发到msg.jsp。

UserDao.java

public void add(User user) throws SQLException {

String sql = "insert into t_user values(?,?,?,?,?,?)";

Object[] params = {user.getUid(), user.getLoginname(), user.getLoginpass(),

user.getEmail(), user.isStatus(), user.getActivationCode()};

qr.update(sql, params);

}

 

UserService.java

public void regist(User user) {

try {

/*

 * 1. 对user进行数据补全

 */

user.setUid(CommonUtils.uuid());

user.setActivationCode(CommonUtils.uuid() + CommonUtils.uuid());

user.setStatus(false);

 

/*

 * 2. 向数据库添加记录

 */

userDao.add(user);

 

/*

 * 3. 向用户注册邮箱地址发送“激活”邮件

 */

 

// 读取email模板中的数据

Properties props = new Properties();

props.load(this.getClass().getClassLoader()

.getResourceAsStream("email_template.properties"));

String host = props.getProperty("host");//获取邮件服务器地址

String username = props.getProperty("username");//获取用户名

String password = props.getProperty("password");//获取密码

String from = props.getProperty("from");//获取发件人地址

String to = user.getEmail();//获取收件人地址

String subject = props.getProperty("subject");//获取主题

//获取内容模板,替换其中的激活码

String content = MessageFormat.format(props.getProperty("content"),

user.getActivationCode());

 

// 发送邮件

Session session = MailUtils.createSession(host, username, password);

Mail mail = new Mail(from, to, subject, content);

MailUtils.send(session, mail);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

 

UserServlet.java

public String regist(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 封装表单数据到User对象中

 */

User user = CommonUtils.toBean(req.getParameterMap(), User.class);

 

/*

 * 2. 对表单数据进行服务器端校验

 */

Map<String,String> errors = validateRegist(user, req);

if(errors != null && errors.size() > 0) {//是否存在校验错误信息

req.setAttribute("errors", errors);

req.setAttribute("user", user);

return "f:/jsps/user/regist.jsp";

}

 

/*

 * 3. 调用userService完成注册

 */

userService.regist(user);

 

/*

 * 4. 保存注册成功信息,转发到msg.jsp显示

 */

req.setAttribute("code", "success");//是成功信息还是错误信息

req.setAttribute("msg", "恭喜,注册成功!请马上到邮箱完成激活!");

return "f:/jsps/msg.jsp";

}

private Map<String, String> validateRegist(User user, HttpServletRequest req) {

Map<String,String> errors = new HashMap<String,String>();

//对loginname进行校验

String loginname = user.getLoginname();

if(loginname == null || loginname.isEmpty()) {

errors.put("loginname", "用户名不能为空!");

} else if(loginname.length() < 3 || loginname.length() > 20) {

errors.put("loginname", "用户名长度必须在3~20之间!");

} else if(userService.validateLoginname(loginname)) {

errors.put("loginname", "用户名已被注册过!");

}

 

// 对loginpass进行校验

String loginpass = user.getLoginpass();

if(loginpass == null || loginpass.isEmpty()) {

errors.put("loginpass", "密码不能为空!");

} else if(loginpass.length() < 3 || loginpass.length() > 20) {

errors.put("loginpass", "密码长度必须在3~20之间!");

}

 

// 对确认密码进行校验

String reloginpass = user.getReloginpass();

if(reloginpass == null || reloginpass.isEmpty()) {

errors.put("reloginpass", "确认密码不能为空!");

} else if(!reloginpass.equalsIgnoreCase(loginpass)) {

errors.put("reloginpass", "两次输入密码不一致!");

}

 

// 对Email进行校验

String email = user.getEmail();

if(email == null || email.isEmpty()) {

errors.put("email", "Email不能为空!");

} else if(!email.matches("^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\\.[a-zA-Z0-9_-]{2,3}){1,2})$")) {

errors.put("email", "错误的Email格式!");

} else if(userService.validateEmail(email)) {

errors.put("email", "Email已被注册过!");

}

 

// 对验证码进行校验

String verifyCode = user.getVerifyCode();

String vCode = (String) req.getSession().getAttribute("vCode");

if(verifyCode == null || verifyCode.isEmpty()) {

errors.put("verifyCode", "验证码不能为空!");

} else if(verifyCode.length() != 4) {

errors.put("verifyCode", "错误的验证码!");

} else if(!verifyCode.equalsIgnoreCase(vCode)) {

errors.put("verifyCode", "错误的验证码!");

}

return errors;

}

 

email_template.properties

subject=\u6765\u81EAITCAST\u7684\u6FC0\u6D3B\u90AE\u4EF6

content=\u606D\u559C\uFF0C\u60A8\u5DF2\u6CE8\u518C\u6210\u529F\uFF0C\u8BF7\u8F6C\u53D1<a href\="http\://localhost\:8080/goods/UserServlet?method\=activation&activationCode\={0}">\u8FD9\u91CC</a>\u5B8C\u6210\u6FC0\u6D3B\u3002

[email protected]

host=smtp.163.com

username=itcast_cxf

password=****

 

 

1.3 用户登录

1.3.1 login.jsp

login.jsp为登录页面,与注册一样需要做前端表单校验。

 

l 用户名

  • 非空校验;
  • 长度校验;

l 密码

  • 非空校验;
  • 长度校验;

l 验证码

  • 非空校验;
  • 是否正确(需要异步请求服务器)。

 

可以依照regist.js来完成登录表单的校验!

服务器端无需添加验证码校验,因为在完成注册时已经为UserServlet提供了validateVerifyCode()方法。

同regist.jsp一样,login.jsp也需要使用验证码,所以需要在<img>中请求VerifyCodeServlet。

 

在用户名文本框中显示cookie中保存的loginname,就是所谓的记住用户名。当用户登录成功后,会把当前用户的名称保存到cookie中,当用户再次到登录页面时,会在文本框中显示用户名。

<script type="text/javascript">

$(function() {

var loginname = "${user.loginname == null ? cookie.loginname.value : user.loginname}";

loginname = window.decodeURI(loginname);

$("#loginname").val(loginname);

});

</script>

 

  因为上面代码中使用了EL表达式,所以一定要放到JSP页面中才可以。

 

1.3.2 实现服务器端代码

当login.jsp通过校验后,会请求UserServlet#login()方法:

UserServlet#login():

l 封装表单数据到User(formBean)对象中;

l 对formBean进行校验,如果出现错误把错误信息保存到Map中,把Map保存到request中,转发到login.jsp显示,为了回显把formBean也保存到request中;

l 调用UserService#login(User formBean)方法;

l 如果返回的user为null,那么说明用户名或密码为空,保存错误信息到request中,转发到login.jsp;

l 如果user.isStatus()为false,说明用户还没有激活,保存错误信息到request中,转发到login.jsp

l 如果返回的user不为null,说明登录成功,保存user到session中。保存当前用户名到cookie中(为了让login.jsp页面记住当前用户名)。转发到主页;

 

UserService#login(User user):

l 调用userDao#findByLoginnameAndLoginpass(String loginname, String loginpass)方法;

 

UserDao# indByLoginnameAndLoginpass(String loginname, String loginpass):

l 略!

1.3.3 top.jsp

当用户登录成功后,会重定向到index.jsp。这时top.jsp中还会显示所有的超链接,所以我们需要根据用户是否登录来显示不同的超链接。

 

用户未登录显示:登录、注册;

用户已登录显示:您好:xxx、我的购物车、我的订单、修改密码、退出

 

因为用户登录成功后,当前用户已经保存到session中,所以我们只需要在top.jsp中判断session中是否存在user即可分别用户是否登录。

1.4 退出

  当用户登录成功后,会把当前User保存到session中。退出只需要从session中的user移除即可。然后重定向到login.jsp即可!

1.4.1 top.jsp

退出超链接是在top.jsp页面中,我们需要修改“退出”超链接,让它请求UserServlet#quit()方法。

<a href="<c:url value='/UserServlet?method=quit'/>" target="_parent">退出</a>

 

1.4.2 服务器端代码实现

UserServlet#quit():

l 获取session,移除user;

l 重定向到login.jsp。

 

1.5 修改密码

  没有做服务器端表单校验,学员可以根据“注册”自行添加服务器端表单校验!

1.5.1 pwd.jsp

 

因为表单中包含新密码,而User类中没有这一属性,所以 修改User类,添加newpass属性。

表单校验:

l 原密码:

  • 非空校验;
  • 长度校验;
  • 是否正确(异步请求);

l 新密码:

  • 非空校验;
  • 长度校验;

l 确认密码:

  • 非空校验;
  • 两次是否一致;

l 验证码:

  • 非空校验;
  • 是否正确(异步请求)。

 

需要为UserServlet#validateLoginpass()方法,用来支持表单异步请求。

UserServlet.java

public String updatePassword(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 封装表单数据到User

 */

User formBean = CommonUtils.toBean(req.getParameterMap(), User.class);

 

/*

 * 2. 调用userService的updatePassword(String uid, String newpass)方法修改密码

 */

User user = (User) req.getSession().getAttribute("user");

userService.updatePassword(user.getUid(), formBean.getNewpass());

 

/*

 * 3. 转发到msg.jsp

 */

req.setAttribute("code", "success");

req.setAttribute("msg", "恭喜,密码修改成功!");

return "f:/jsps/msg.jsp";

}

 

  校验方法对照regist.js完成!

1.5.2 实现服务器端代码

当表单校验通过后,会请求UserServlet#updatePassword():

l 封装表单数据到User中,获取新密码;

l 获取session中的User,获取uid;

l 调用userService#updatePassword(String uid, newpass)完成修改密码;

l 保存成功信息,转发到msg.jsp。

 

public String updatePassword(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 封装表单数据到User

 */

User formBean = CommonUtils.toBean(req.getParameterMap(), User.class);

 

/*

 * 2. 调用userService的updatePassword(String uid, String newpass)方法修改密码

 */

User user = (User) req.getSession().getAttribute("user");

userService.updatePassword(user.getUid(), formBean.getNewpass());

 

/*

 * 3. 转发到msg.jsp

 */

req.setAttribute("code", "success");

req.setAttribute("msg", "恭喜,密码修改成功!");

return "f:/jsps/msg.jsp";

}

 

2 分类模块

2.1 创建分类模块相关类

在每个模块开始时,都要创建如下基本类:实体类、DAO类、Service类、Servlet类:

 

2.1.1 Category

Category类作为实体类需要与数据库表对应,即t_category表对象。Category类对照着t_category表来写即可。我们要保证Category类的属性名称与t_category表的列名称完全相同。

注意,在表中如果存在关联关系时,使用主外键进行关联,而在实体类中不能只给出外键,而是要给出关联的类型。

t_category表存在自身关联,pid列用来指定父分类的主键。也就是说一个分类可能有一个父分类,也可能有多个子分类。

Category.java

public class Category {

private String cid;

private String cname;

private List<Category> children;//所有子分类

private Category parent;//父分类

private String desc;

}

 

2.1.2 各层的类

  还需要添加:CategoryDao、CategoryService、CategoryServlet类,这里就不在赘述,根据用户模块自己完成!

CategoryDao.java

public class CategoryDao {

private QueryRunner qr = new TxQueryRunner();

}

 

CategoryService.java

public class CategoryService {

private CategoryDao categoryDao = new CategoryDao();

}

 

CategoryServlet.java

public class CategoryServlet extends BaseServlet {

private CategoryService categoryService = new CategoryService();

}

 

2.2 显示所有分类

2.2.1 分类显示

请求是从main.jsp的框架页中发出的,当客户端打开main.jsp后,会发出三个请求,分别是:

l 上部:请求top.jsp页面;

l 左部:请求CategoryServlet#findAll()方法,得到所有分类,转发到left.jsp显示;

l 高级:请求gj.jsp;

l 中部:请求body.jsp

我们现在需要的是修改main.jsp,让它去请求CategoryServlet#findAll()方法,在findAll()方法中得到所有分类,保存到request中,最后转发到left.jsp。

main.jsp

<td class="tdLeft" rowspan="2">

<iframe frameborder="0" src="<c:url value='/CategoryServlet?method=findAll'/>" name="left"></iframe>

</td>

 

在left.jsp中使用javascript小工具(手风琴式菜单)来显示所有分类。所以,我们需要先学习一下手风琴式菜单的使用。

 

  

手风琴小工具在在menu目录中,我们只需要学习一个名为Q6MenuBar的类即可。使用Q6MenuBar类的步骤如下:

l 创建Q6MenuBar对象,传递标题;

l 向Q6MenuBar中添加一级、二级菜单,在这里就是指定一级、二级分类;

l 调用Q6MenuBar的toString(),把字符串插入到某个<div>中。

  1. 创建页面元素

<body>  

  <div id="menu"></div>

</body>

 

  页面中只有一个<div>

 

  1. 创建全局对象

var bar = new Q6MenuBar("bar", "ITCAST网络图书商城");

  

创建Q6MenuBar对象:

l 它必须是一个全局对象;

l 参数“bar”必须与对象名相同(例如:var abc=new Q6MenuBar(“abc”, “xxx”);

l 参数“ITCAST网络图书商城”指定“手风琴”的标题。

 

  1. 配置Q6MenuBar对象

$(function() {

bar.colorStyle = 4;

bar.config.imgDir = "<c:url value='/menu/img/'/>";

bar.config.radioButton=true;

    …

}

 

在页面加载完成后,对Q6MenuBar进行配置:

l bar.colorStyle:指定配色方案。Q6MenuBar一共5种配色方案,分别为0~4;

l bar.config.imgDir:指定一级菜单上的“展开”和“闭合”图片所在目录;

l bar.config.radioButton:指定是否可以同时展开多个一级菜单,为true表示最多只能展开一个一级菜单,false表示可以同时展开多个一级菜单。

 

  1. 添加一级、二级菜单

$(function() {

bar.add("程序设计", "Java Javascript", "/goods/jsps/book/list.jsp", "body");

bar.add("程序设计", "JSP", "/goods/jsps/book/list.jsp", "body");

bar.add("程序设计", "C C++ VC VC++", "/goods/jsps/book/list.jsp", "body");

 

bar.add("办公室用书", "微软Office", "/goods/jsps/book/list.jsp", "body");

bar.add("办公室用书", "计算机初级入门", "/goods/jsps/book/list.jsp", "body");

}

 

调用Q6MenuBar的add()方法可以添加菜单,该方法一共4个参数,分别表示:

l 1:一级菜单名称,如果这个名称的一级菜单不存在,那么会创建它,如果已经存在,那么就不会再创建;

l 2:二级菜单名称;

l 3:点击二级菜单请求的URL;

l 4:结果在哪个框架页显示。

  上面代码一共添加了5个二级菜单,前3个在一个一级菜单中,后2个在一个一级菜单中。

2.2.2 显示所有分类服务器端实现

CategoryServlet#findAll():

l 调用CategoryService#findAll()得到所有分类;

l 把所有分类保存到request中;

l 转发到left.jsp页面显示所有分类。

 

CategoryService#findAll():

l 调用CategoryDao#findAll()得到所有分类返回;

 

CategoryDao#findAll():

l 查询所有一级分类,得到一级分类的List;

l 循环遍历每个一级分类,为每个一级分类加载它的所有二级分类;

l 返回分类List。

 

CategoryDao.java

public List<Category> findAll() throws SQLException {

/*

 * 1. 获取所有一级分类

 *   pid为null就是一级分类。

 */

String sql = "select * from t_category where pid is null order by orderBy";

List<Category> parents = qr.query(sql,

new BeanListHandler<Category>(Category.class));

 

/*

 * 2. 循环遍历每个一级分类,为其加载它的所有二级分类

 */

sql = "select * from t_category where pid=? order by orderBy";

for(Category parent : parents) {

// 获取当前一级分类的所有二级分类

List<Category> children = qr.query(sql,

new BeanListHandler<Category>(Category.class),

parent.getCid());

// 给当前一级分类设置二级分类

parent.setChildren(children);

// 为每个二级分类设置一级分类

for(Category child : children) {

child.setParent(parent);

}

}

/*

 * 3. 返回一级分类List,每个一级分类都包含了自己的二级分类

 */

return parents;

}

 

CategoryService.java

public List<Category> findAll() {

try {

return categoryDao.findAll();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

 

CategoryServlet.java

public String findAll(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 获取所有分类

 */

List<Category> parents = categoryService.findAll();

/*

 * 2. 保存到request中

 */

req.setAttribute("parents", parents);

/*

 * 3. 转发到left.jsp

 */

return "f:/jsps/left.jsp";

}

 

2.2.3 left.jsp显示所有分类

在left.jsp中显示所有分类

<script language="javascript">

var bar = new Q6MenuBar("bar", "ITCAST网络图书商城");

$(function() {

bar.colorStyle = 4;

bar.config.imgDir = "<c:url value='/menu/img/'/>";

bar.config.radioButton=true;

<c:forEach items="${parents}" var="parent">

  <c:forEach items="${parent.children}" var="child">

    bar.add("${parent.cname}", "${child.cname}", "<c:url value='/index.jsp'/>", "body");

  </c:forEach>

</c:forEach>

$("#menu").html(bar.toString());

});

</script>

 

  上面代码必须放到jsp页面中,因为使用了JSTL标签!

 

3 图书模块

3.1 创建图书模块相关类

在每个模块开始时,都要创建如下基本类:实体类、DAO类、Service类、Servlet类:

l cn.itcast.goods.book.domain.Book;

l cn.itcast.goods. book.dao. BookDao;

l cn.itcast.goods. book.service. BookService;

l cn.itcast.goods. book.web.servlet. BookServlet。

3.1.1 Book

对照t_book表完成Book类,其中t_book表的cid列对应Book类的Category对象,它是关联列。

Book.java

public class Book {

private String bid;// id

private String bname;// 图名

private String author;// 作者

private double price;// 定价

private double currPrice;// 当前价

private double discount;// 折扣

private String press;// 出版社

private String publishtime;// 出版时间

private String edition;// 版次

private int pageNum;// 页数

private int wordNum;// 字数

private String printtime;// 印刷时间

private int booksize;// 开本

private String paper;// 纸质

private Category category;// 所属分类

private String image_w;// 大图

private String image_b;// 小图

}

 

3.2 分页显示

我们希望查看图书列表时显示一页数据,而不是所有数据。这时就需要使用分页了!下面我们来对分页进行分析。

3.2.1 分页数据

  1. 列表页面

我们希望在页面上得到如下结果:

 

列表页面需要:

l 当前页的记录,即所有图书;

l 当前页码;

l 总页数;

l 要请求的URL,以及请求参数。

  1. Servlet

列表页面需要的数据都由Servlet保存到request中传递给页面,也就是说,页面需要的就是Servlet的工作。

 

Servlet的任务有:

l 当前页码:由页面传递,如果页面没有传递那么就是1;

l 总页数:需要总记录数和每页记录数来计算:

  • 总记录数:查询数据库;
  • 每页记录数:系统数据;

l 当前页记录:查询数据库;

l 请求的URL及参数:Servlet需要把每次请求时的URL及参数再次传递回页面。

 

  1. Dao

在Servlet中已经分析出需要访问DAO来获取很多数据:

l 获取总记录数;

l 当前页记录;

 

3.2.2 分页Bean

通过上面的分析,已经知道有很多数据需要在各层之间传递,所以我们应该写一个分页Bean,把所有与分页相关的数据封装到一个Bean中。

PageBean.java

public class PageBean<T> {

private int pc;//当前页码

private int tr;//总记录数

private int ps;//每页记录数

private List<T> dataList;//当前页记录

private String url;//请求的url

 

/**

 * 计算总页数

 * @return

 */

public int getTp() {

int tp = tr / ps;//总记录数/每页记录数

return tr % ps == 0 ? tp : tp+1;//如果是整除,返回tp,否则再加1。

}

}

 

3.2.3 分页查询流程图

 

3.2.4 页面显示分页导航

我们专门写一个分页导航页面(pager.jsp),然后在其他需要显示分页导航的页面中使用<%@include%>来导入pager.jsp页面即可。

这里我们不讨论样式的问题!在导入pager.jsp的页面中同时导入pager.css文件,这样就可以有漂亮的样式了,例如在/jsps/book/list.jsp中需要使用分页导航,所以导入pager.jsp,同时在/jsps/book/list.jsp导入pager.css文件。

 

pager.jsp

<div class="divBody">

  <div class="divContent">

    ...

</div>

</div>

 

上面代码中已经给出了基本的<div>,我们把导航内容写入到内层<div>中即可。

  1. 上一页和下一页

我们首先来完成“上一页”和“下一页”。

<div class="divBody">

  <div class="divContent">

    <a href="${pb.url }&pc=${pb.pc-1}" class="aBtn bold">上一页</a>

    <a href="${pb.url }&pc=${pb.pc+1}" class="aBtn bold">下一页</a>

  </div>

</div>

 

如果你看不懂“${pb.url}&pc=${pb.pc-1}”是什么意思,那么你等等,一会看了BookServlet的代码,也可能就会明白了。

注意,不是什么时候都可以点击“上一页”和“下一页”的。当前页如果是第一页,那么就不能点击“上一页”,当前页如果是最后一页,那么就不可以点击“下一页”。所以,我们需要判断,然后在需要时显示“灰色”的按钮!

    <c:choose>

      <c:when test="${pb.pc eq 1 }">

        <span class="spanBtnDisabled">上一页</span>

      </c:when>

      <c:otherwise>

        <a href="${pb.url }&pc=${pb.pc-1}" class="aBtn bold">上一页</a>

      </c:otherwise>

    </c:choose>

    

    

     <c:choose>

      <c:when test="${pb.pc eq pb.tp }">

        <span class="spanBtnDisabled">下一页</span>

      </c:when>

      <c:otherwise>

        <a href="${pb.url }&pc=${pb.pc+1}" class="aBtn bold">下一页</a>

      </c:otherwise>

    </c:choose>  

 

  1. 共N页、到M页

接下来我们来完成共N页,到M页。

    <span>共${pb.tp }页</span>

    <span>到</span>

    <input type="text" class="inputPageCode" id="pageCode" value="${pb.pc }"/>

    <span>页</span>

    <a href="javascript:_go();" class="aSubmit">确定</a>

<script type="text/javascript">

function _go() {

var pc = $("#pageCode").val();//获取文本框中的当前页码

if(!/^[1-9]\d*$/.test(pc)) {//对当前页码进行整数校验

alert('请输入正确的页码!');

return;

}

if(pc > ${pb.tp}) {//判断当前页码是否大于最大页

alert('请输入正确的页码!');

return;

}

location = "${pb.url}&pc=" + pc;

}

</script>

 

  1. 页码列表

实现页码列表需要先确定页码列表的开始值(begin)和结束值(end),例如begin=1,end=6,那么就可以使用循环来遍历列表了:<c:forEach begin=”${begin}” end=”${end}” …>。

  我们设定列表长度为6,即最多显示6 个页码。当前,如果一定就只有3页,那么就只显示3页。

 

 

  我们设定当前页在列表的第3位置上,例如下图当前页码为4,4在列表中第3个位置上显示。当然,如果当前页码为1,或是当前页码为最大页,那么就不能在3位置上显示了。

 

 

设定页码列表:

l 如果tp<=6,那么设置begin=1, end=tp。tp表示总页数;

l 否则设置begin=pc-2, end=pc+3,即保证当前页在列表的3位置上;

  • 如果begin<1,那么让begin=1, end=6;
  • 如果end>tp,那么让begin=tp-5, end=tp。

  如果end<tp,那么显示“省略号”。

    <%-- 计算begin和end --%>

    <c:choose>

      <%-- 如果总页数<=6,那么显示所有页码,即begin=1 end=${pb.tp} --%>

      <c:when test="${pb.tp <= 6 }">

        <c:set var="begin" value="1"/>

        <c:set var="end" value="${pb.tp }"/>

      </c:when>

      <c:otherwise>

        <%-- 设置begin=当前页码-2,end=当前页码+3 --%>

        <c:set var="begin" value="${pb.pc-2 }"/>

        <c:set var="end" value="${pb.pc+3 }"/>

        <c:choose>

          <%-- 如果begin<1,那么让begin=1 end=6 --%>

          <c:when test="${begin<1}">

            <c:set var="begin" value="1"/>

            <c:set var="end" value="6"/>

          </c:when>

          <%-- 如果end>最大页,那么begin=最大页-5 end=最大页 --%>

          <c:when test="${end>pb.tp}">

            <c:set var="begin" value="${pb.tp-5 }"/>

            <c:set var="end" value="${pb.tp}"/>

          </c:when>

        </c:choose>

      </c:otherwise>

    </c:choose>

    

    <%-- 显示页码列表 --%>

    <c:forEach var="pc" begin="${begin }" end="${end }">

      <c:choose>

        <c:when test="${pb.pc eq pc }">

          <span class="spanBtnSelect">${pc }</span>

        </c:when>

        <c:otherwise>

          <a href="${pb.url}&pc=${pc}" class="aBtn">${pc }</a>

        </c:otherwise>

      </c:choose>

    </c:forEach>

    

    <%-- 显示点点点 --%>

    <c:if test="${end < pb.tp }">

      <span class="spanApostrophe">...</span> 

    </c:if>

 

 

测试:

1) 当tp=3时,因为tp<=6,所以begin=1, end=tp,即显示1,2,3。

 

2) 当tp=8时,pc=4时,begin=4-2, end=4+3,即显示2,3,4,5,6,7,其中4在列表的第三位置上。

 

3) 当tp=8时,pc=2时,begin=2-2,end=2+3,这时begin=0,所以让begin=1,而end=6,即显示1,2,3,4,5,6。

 

4) 当tp=8时,pc=7时,begin=7-2,end=7+3,这时end=10,大于了tp,所以让begin=tp-5即begin=3,end=tp,即tp=8,即显示3,4,5,6,7,8。

 

3.3 按分类查询图书

点击某个二级分类,显示该分类下的图书。

 

3.3.1 left.jsp

修改left.jsp页面,重新为2级菜单指定请求的URL,让其请求BookServlet#findByCategory()方法,并给出cid参数,即分类id。

left.jsp

<script language="javascript">

var bar = new Q6MenuBar("bar", "ITCAST网络图书商城");

$(function() {

bar.colorStyle = 4;

bar.config.imgDir = "<c:url value='/menu/img/'/>";

bar.config.radioButton=true;

<c:forEach items="${parents}" var="parent">

  <c:forEach items="${parent.children}" var="child">

    bar.add("${parent.cname}", "${child.cname}", "<c:url value='/BookServlet?method=findByCategory&cid=${child.cid}'/>", "body");

  </c:forEach>

</c:forEach>

$("#menu").html(bar.toString());

});

</script>

 

3.3.2 服务器端代码实现

1. PageBean<Book> BookDao#findByCategory(String cid, int pc)

l 组装查询总记录数的SQL语句,然后执行,得到总记录数;

l 组装查询当前页记录的SQL语句,然后执行,得到当前页记录;

l 创建PageBean,设置总记录数、设置当前页码、设置每页记录数(设置为12)、设置当前页记录;

l 返回PageBean。

BookDao#findByCategory

/**

 * 按分类查询(分页)

 * @param cid

 * @param pc

 * @return

 * @throws SQLException

 */

public PageBean<Book> findByCategory(String cid, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("cid", cid);

return findByCriteria(criteria, pc);

}

 

/*

 * 根据条件分页查询

 */

private PageBean<Book> findByCriteria(Map<String,Object> criteria, int pc) throws SQLException {

/*

 * 1. 创建sql语句条件子句

 */

List<Object> params = new ArrayList<Object>();//条件,对应sql中的“?”

StringBuilder whereSql = new StringBuilder(" where 1=1");

for(String name : criteria.keySet()) {//循环遍历每个条件

Object value = criteria.get(name);

if(value == null) {//如果值为空,说明没有这个条件

continue;

}

whereSql.append(" and ").append(name).append(" like ?");

params.add("%" + value + "%");

}

 

/*

 * 2. 创建排序和limit子句

 */

String orderByAndLimitSql = " order by orderBy limit ?,?";

 

/*

 * 3. 生成个数查询语句,执行sql,得到总记录数

 */

String cntSql = "select count(*) from t_book" + whereSql;

Number cnt = (Number)qr.query(cntSql, new ScalarHandler(), params.toArray());

int tr = cnt != null ? cnt.intValue() : 0;

 

/*

 * 4. 生成记录查询,执行SQL语句,得到当前页记录

 */

String sql = "select * from t_book" + whereSql + orderByAndLimitSql;

 

// 计算limit参数

int ps = PageConstants.BOOK_PAGE_SIZE;//得到每页记录数

params.add(ps * (pc-1));//计算当前页第一条记录的下标位置,下标从0开始

params.add(ps);//一共查询几条记录

 

// 把mapList映射成List<Book>

List<Map<String,Object>> mapList = qr.query(sql, new MapListHandler(), params.toArray());

List<Book> bookList = new ArrayList<Book>();

for (Map<String, Object> map : mapList) {

Book book = CommonUtils.toBean(map, Book.class);

Category category = CommonUtils.toBean(map, Category.class);

book.setCategory(category);

bookList.add(book);

}

 

/*

 * 5. 创建PageBean,返回

 */

PageBean<Book> pb = new PageBean<Book>();

pb.setPc(pc);

pb.setPs(ps);

pb.setTr(tr);

pb.setDataList(bookList);

return pb;

}

 

2. BookServlet#findByCategory()

l 获取当前页页码,如果不存在,那么设置为1;

l 获取cid;

l 通过当前页码和cid来调用BookService#findByCategory()方法得到PageBean对象;

l 获取url,设置给PageBean对象,把PageBean保存到request中;

  • url = req.getRequestURI() + "?" + req.getQueryString(),再把其中pc参数截取掉即可。

l 转发到/jsps/book/list.jsp。

BookServlet#findByCartegory()

/*

 * 获取当前页码

 */

private int getPageCode(HttpServletRequest req) {

String pageCode = req.getParameter("pc");

if(pageCode == null) return 1;

try {

return Integer.parseInt(pageCode);

} catch(RuntimeException e) {

return 1;

}

}

 

/*

 * 获取请求的url,但去除pc参数

 */

private String getUrl(HttpServletRequest req) {

String url = req.getRequestURI() + "?" + req.getQueryString();

int fromIndex = url.lastIndexOf("&pc=");

if(fromIndex == -1) return url;

int toIndex = url.indexOf("&", fromIndex + 1);

if(toIndex == -1) return url.substring(0, fromIndex);

return url.substring(0, fromIndex) + url.substring(toIndex);

}

 

/**

 * 按分类查询图书(分页)

 * @param request

 * @param response

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String findByCategory(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(request);

/*

 * 2. 使用BookService查询,得到PageBean

 */

String cid = request.getParameter("cid");

PageBean<Book> pb = bookService.findByCategory(cid, pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(request);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/book/list.jsp

 */

request.setAttribute("pb", pb);

return "/jsps/book/list.jsp";

}

 

3.3.3 list.jsp

在当Servlet转发到/jsps/book/list.jsp页面时,把PageBean保存到了request中。在list.jsp页面中循环显示PageBean的dataList,即可。

list.jsp

<c:forEach items="${pb.dataList }" var="book">

 <li>

  <div class="inner">

    <a class="pic" href="<c:url value='/jsps/book/desc.jsp'/>"><img src="<c:url value='/${book.image_b }'/>" border="0"/></a>

    <p class="price">

<span class="price_n">¥${book.currPrice }</span>

<span class="price_r">¥${book.price }</span>

(<span class="price_s">${book.discount}折</span>)

</p>

<p><a id="bookname" title="${book.bname }" href="<c:url value='/jsps/book/desc.jsp'/>">${book.bname }</a></p>

<p><a href="<c:url value='/jsps/book/list.jsp'/>" name='P_zz' title='${book.author }'>${book.author }</a></p>

<p class="publishing">

<span>出 版 社:</span><a href="<c:url value='/jsps/book/list.jsp'/>">${book.press }</a>

</p>

<p class="publishing_time"><span>出版时间:</span>${book.publishtime }</p>

  </div>

 </li>

</c:forEach>

 

因为list.jsp中需要使用分页导航,所以还需要使用<%@include%>来导入pager.jsp页面,而且还要在list.jsp页面中导入pager.css文件。

<link rel="stylesheet" type="text/css" href="<c:url value='/jsps/pager/pager.css'/>" />

<%@include file="/jsps/pager/pager.jsp" %>

 

3.4 其他条件查询

在图书模块中还有按作者查询、按出版社查询、按图名查询,以及高级查询!这些查询都与按分类查询相似,所以这里就一起介绍了。

 

  这些查询最终都是到/jsps/book/list.jsp页面显示图书列表!

3.4.1 前端页面

页面涉及到:

l /jsps/book/list.jsp;

l /jsps/gj.jsp;

l /jsps/search.jsp。

list.jsp

<c:forEach items="${pb.dataList }" var="book">

  <c:url value='/BookServlet' var="findByAuthorUrl">

<c:param name="method" value="findByAuthor"/>

<c:param name="author" value="${book.author }"/>

  </c:url>

  <c:url value='/BookServlet' var="findByPressUrl">

<c:param name="method" value="findByPress"/>

<c:param name="press" value="${book.press }"/>

  </c:url>

  <li>

  <div class="inner">

    <a class="pic" href="<c:url value='/jsps/book/desc.jsp'/>"><img src="<c:url value='/${book.image_b }'/>" border="0"/></a>

    <p class="price">

<span class="price_n">¥${book.currPrice }</span>

<span class="price_r">¥${book.price }</span>

(<span class="price_s">${book.discount}折</span>)

</p>

<p><a id="bookname" title="${book.bname }" href="<c:url value='/jsps/book/desc.jsp'/>">${book.bname }</a></p>

<p><a href="${findByAuthorUrl }" name='P_zz' title='${book.author }'>${book.author }</a></p>

<p class="publishing">

<span>出 版 社:</span><a href="${findByPressUrl }">${book.press }</a>

</p>

<p class="publishing_time"><span>出版时间:</span>${book.publishtime }</p>

  </div>

  </li>

</c:forEach>

 

search.jsp

    <form action="<c:url value='/BookServlet'/>" method="get" target="body" id="form1">

     <input type="hidden" name="method" value="findByBname"/>

     <input type="text" name="bname"/>

     <span>

     <a href="javascript:document.getElementById('form1').submit();"><img align="top" border="0" src="../images/btn.bmp"/></a>

     <a href="gj.jsp" style="font-size: 10pt; color: #404040;" target="body">高级搜索</a>

     </span>

    </form>

 

gj.jsp

  <form action="<c:url value='/BookServlet'/>" method="get">

   <input type="hidden" name="method" value="findByCombination"/>

<table align="center">

<tr>

<td>书名:</td>

<td><input type="text" name="bname"/></td>

</tr>

<tr>

<td>作者:</td>

<td><input type="text" name="author"/></td>

</tr>

<tr>

<td>出版社:</td>

<td><input type="text" name="press"/></td>

</tr>

<tr>

<td> </td>

<td>

<input type="submit" value="搜  索"/>

<input type="reset" value="重新填写"/>

</td>

</tr>

</table>

</form>

 

3.4.2 BookDao

需要在BookDao中提供相当的查询方法。可以借鉴BookDao#findByCategory方法完成。

/**

 * 按作者查询

 * @param cid

 * @param pc

 * @return

 * @throws SQLException

 */

public PageBean<Book> findByAuthor(String author, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("author", author);

return findByCriteria(criteria, pc);

}

 

/**

 * 按出版社查询

 * @param press

 * @param pc

 * @return

 * @throws SQLException

 */

public PageBean<Book> findByPress(String press, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("press", press);

return findByCriteria(criteria, pc);

}

 

/**

 * 按图名查询

 * @param cid

 * @param pc

 * @return

 * @throws SQLException

 */

public PageBean<Book> findByBname(String bname, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("bname", bname);

return findByCriteria(criteria, pc);

}

 

/**

 * 多条件组合查询

 * @param cid

 * @param pc

 * @return

 * @throws SQLException

 */

public PageBean<Book> findByCombination(Book book, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("bname", book.getBname());

criteria.put("author", book.getAuthor());

criteria.put("press", book.getPress());

return findByCriteria(criteria, pc);

}

 

3.4.3 BookServlet

BookServlet中提供相应的查询方法,在BookServlet中调用相应的BookService中的方法完成查询。这里没有给出BookService的代码,因为它太简单了,所以没有给出!

/**

 * 按作者查询

 * @param request

 * @param response

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String findByAuthor(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(request);

/*

 * 2. 使用BookService查询,得到PageBean

 */

String author = request.getParameter("author");

PageBean<Book> pb = bookService.findByAuthor(author, pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(request);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/book/list.jsp

 */

request.setAttribute("pb", pb);

return "/jsps/book/list.jsp";

}

 

/**

 * 按出版社查询

 * @param request

 * @param response

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String findByPress(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(request);

/*

 * 2. 使用BookService查询,得到PageBean

 */

String press = request.getParameter("press");

PageBean<Book> pb = bookService.findByPress(press, pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(request);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/book/list.jsp

 */

request.setAttribute("pb", pb);

return "/jsps/book/list.jsp";

}

 

/**

 * 按图名查询

 * @param request

 * @param response

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String findByBname(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(request);

/*

 * 2. 使用BookService查询,得到PageBean

 */

String bname = request.getParameter("bname");

PageBean<Book> pb = bookService.findByBname(bname, pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(request);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/book/list.jsp

 */

request.setAttribute("pb", pb);

return "/jsps/book/list.jsp";

}

 

/**

 * 多条件组合查询

 * @param request

 * @param response

 * @return

 * @throws ServletException

 * @throws IOException

 */

public String findByCombination(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(request);

/*

 * 2. 使用BookService查询,得到PageBean

 */

Book book = CommonUtils.toBean(request.getParameterMap(), Book.class);

PageBean<Book> pb = bookService.findByCombination(book, pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(request);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/book/list.jsp

 */

request.setAttribute("pb", pb);

return "/jsps/book/list.jsp";

}

 

3.5 加载图书

  加载图书从图书列表页面开始,点击某一本图书的图片,或者是图名就可以完成图书的加载!最终在/jsps/book/desc.jsp页面显示图书的详细信息。

3.5.1 修改list.jsp页面中的超链接

list.jsp

<ul>

<c:forEach items="${pb.dataList }" var="book">

  <c:url value='/BookServlet' var="findByAuthorUrl">

<c:param name="method" value="findByAuthor"/>

<c:param name="author" value="${book.author }"/>

  </c:url>

  <c:url value='/BookServlet' var="findByPressUrl">

<c:param name="method" value="findByPress"/>

<c:param name="press" value="${book.press }"/>

  </c:url>

  <li>

  <div class="inner">

    <a class="pic" href="<c:url value='/BookServlet?method=load&bid=${book.bid }'/>"><img src="<c:url value='/${book.image_b }'/>" border="0"/></a>

    <p class="price">

<span class="price_n">¥${book.currPrice }</span>

<span class="price_r">¥${book.price }</span>

(<span class="price_s">${book.discount}折</span>)

</p>

<p><a id="bookname" title="${book.bname }" href="<c:url value='/BookServlet?method=load&bid=${book.bid }'/>">${book.bname }</a></p>

<p><a href="${findByAuthorUrl }" name='P_zz' title='${book.author }'>${book.author }</a></p>

<p class="publishing">

<span>出 版 社:</span><a href="${findByPressUrl }">${book.press }</a>

</p>

<p class="publishing_time"><span>出版时间:</span>${book.publishtime }</p>

  </div>

  </li>

</c:forEach>

</ul>

 

3.5.2 服务器端代码实现

BookDao.java

/**

 * 通过bid加载图书

 * 需要查询出图书所属1级、2级分类

 * @param bid

 * @return

 * @throws SQLException

 */

public Book load(String bid) throws SQLException {

// 多表查询

String sql = "select * from t_book b, t_category c where b.cid=c.cid and bid=?";

Map<String,Object> map = qr.query(sql, new MapHandler(), bid);

 

// 把结果映射成Book

Book book = CommonUtils.toBean(map, Book.class);

// 把结果映射成Category,它是2级分类

Category category = CommonUtils.toBean(map, Category.class);

 

// 创建父分类

Category parent = new Category();

// 设置pid,即父分类id为其cid

parent.setCid((String)map.get("pid"));

 

 

category.setParent(parent);

book.setCategory(category);

return book;

}

 

BookServlet.java

public String load(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

String bid = request.getParameter("bid");

request.setAttribute("book", bookService.load(bid));

return "/jsps/book/desc.jsp";

}

 

3.5.3 desc.jsp显示图书

desc.jsp

  <div class="divBookName">${book.bname }</div>

  <div>

    <img align="top" src="<c:url value='/${book.image_w }'/>" class="img_image_w"/>

    <div class="divBookDesc">

    <ul>

     <li>商品编号:${book.bid }</li>

     <li>当前价:<span class="price_n">¥${book.currPrice }</span></li>

     <li>定价:<span class="spanPrice">¥${book.price }</span> 折扣:<span style="color: #c30;">${book.discount }</span>折</li>

    </ul>

<hr class="hr1"/>

<table>

<tr>

<td colspan="3">

作者:${book.author } 著

</td>

</tr>

<tr>

<td colspan="3">

出版社:${book.press }

</td>

</tr>

<tr>

<td colspan="3">出版时间:${book.publishtime }</td>

</tr>

<tr>

<td>版次:${book.edition }</td>

<td>页数:${book.pageNum }</td>

<td>字数:${book.wordNum }</td>

</tr>

<tr>

<td width="180">印刷时间:${book.printtime }</td>

<td>开本:${book.booksize }开</td>

<td>纸张:${book.paper }</td>

</tr>

</table>

<div class="divForm">

<form id="form1" action="<c:url value='/CartServlet'/>" method="post">

<input type="hidden" name="method" value="add"/>

<input type="hidden" name="bid" value=""/>

   我要买:<input id="cnt" style="width: 40px;text-align: center;" type="text" name="quantity" value="1"/>件

   </form>

   <a id="btn" href="<c:url value='/jsps/cart/list.jsp'/>"></a>

   </div>

</div>

  </div>

 

4 购物车模块

4.1 创建购物车模块相关类

在每个模块开始时,都要创建如下基本类:实体类、DAO类、Service类、Servlet类:

 

4.1.1 CartItem

在t_cartItem表中,bid和uid都是关联列,所以需要在CartItem中给出对象!

这里只给出CartItem类,其他类对照其他模块自行编写!

CartItem.java

public class CartItem {

private String cartItemId;

private int quantity;

private Book book;

private User owner;

/**

 * 返回小计

 * @return

 */

public double getSubtotal() {

BigDecimal v1 = new BigDecimal(Double.toString(book.getCurrPrice()));

BigDecimal v2 = new BigDecimal(Double.toString(quantity));

return v1.multiply(v2).doubleValue();

}…

}

 

4.2 购物车模块功能介绍

本系统中在数据库中存储购物车数据,而不是在session中,也不是在cookie中。

本系统中对购物车的操作如下:

l 添加购物车条目;

l 修改购物车条目数量;

l 批量删除条目;

l 查看我的购物车;

l 显示被选中的购物车条目,准备生成订单;

4.3 登录过滤器

本系统中,对购物车操作的前提是登录,只有登录用户才可以,这里使用登录校验过滤器来进行校验,但过滤的资源包括:CartItemServlet,以及/jsps/cart目录。

LoginFilter.java

public class LoginFilter implements Filter {

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

Object sessionUser = req.getSession().getAttribute("sessionUser");

if (sessionUser == null) {

req.setAttribute("code", "error");

req.setAttribute("msg", "您还没有登录!");

req.getRequestDispatcher("/jsps/msg.jsp")

.forward(request, response);

} else {

chain.doFilter(request, response);

}

}

}

<filter>

<filter-name>LoginFilter</filter-name>

<filter-class>cn.itcast.bookstore.web.filter.LoginFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>LoginFilter</filter-name>

<servlet-name>CartItemServlet</servlet-name>

<url-pattern>/jsps/cart/*</url-pattern>

</filter-mapping>

 

4.4 查看我的购物车

查看我的购物车从top.jsp中“我的购物车”超链接开始,请求CartItemServlet#myCart()方法。在myCart()方法中,从session中获取当前用户,然后获取用户id,调用CartItemService的myCart(String uid)得到当前用户的所有购物车条目,把条目保存到request中,转发到/jsps/cart/list.jsp显示。

在list.jsp中判断是否存在条目,如果不存在,显示“您的车是空的”;如果存在条目,那么就显示条目。在list.jsp中还要自行计算合计金额,然后显示!

4.4.1 修改top.jsp中的“我的购物车”链接

<a href="<c:url value='/CartItemServlet?method=myCart'/>" target="body">我的购物车</a>

 

4.4.2 服务器端代码实现

CartItemDao提供按uid查询的方法:

CartItemDao.java

/*

 * 把Map映射成CartItem

 */

private CartItem toBean(Map<String,Object> map) {

CartItem cartItem = CommonUtils.toBean(map, CartItem.class);

Book book = CommonUtils.toBean(map, Book.class);

User owner = CommonUtils.toBean(map, User.class);

cartItem.setBook(book);

cartItem.setOwner(owner);

return cartItem;

}

 

/*

 * 把List<Map>映射成List<CartItem>

 */

private List<CartItem> toBeanList(List<Map<String,Object>> mapList) {

List<CartItem> cartItemList = new ArrayList<CartItem>();

for(Map<String,Object> map : mapList) {

cartItemList.add(toBean(map));

}

return cartItemList;

}

 

/**

 * 按uid查询

 * @param uid

 * @return

 * @throws SQLException

 */

public List<CartItem> findByUser(String uid) throws SQLException {

// 不只是查询购物车条目,还要查询条目关联的图书

String sql = "select * from t_cartItem ci, t_book b where ci.bid=b.bid and uid=? order by ci.orderBy";

List<Map<String,Object>> mapList = qr.query(sql, new MapListHandler(), uid);

return toBeanList(mapList);

}

 

CartItemService提供myCart(String uid)方法:

CartItemService.java

public List<CartItem> myCart(String uid) {

try {

return cartItemDao.findByUser(uid);

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

 

CartItemServlet提供myCart()方法;

CartItemServlet.java

public String myCart(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 获取session中的user,并购物user的uid

 */

User user = (User)req.getSession().getAttribute("sessionUser");

String uid = user.getUid();

/*

 * 2. 调用service的myCart(String uid)方法获取当前用户的所有购物车条目

 */

List<CartItem> cartItemList = cartItemService.myCart(uid);

/*

 * 3. 保存到request中,转发到/jsps/cart/list.jsp

 */

req.setAttribute("cartItemList", cartItemList);

return "/jsps/cart/list.jsp";

}

 

4.4.3 /jsps/cart/list.jsp

1 当没有条目时显示空车:

 

list.jsp

<table width="95%" align="center" cellpadding="0" cellspacing="0">

<tr>

<td align="right">

<img align="top" src="<c:url value='/images/icon_empty.png'/>"/>

</td>

<td>

<span class="spanEmpty">您的购物车中暂时没有商品</span>

</td>

</tr>

</table>  

 

 

2. 循环遍历所有购物车条目。

list.jsp

<c:forEach items="${cartItemList }" var="item">

<tr align="center">

<td align="left">

<input value="${item.cartItemId }" type="checkbox" name="checkboxBtn" checked="checked"/>

</td>

<td align="left" width="70px">

<a class="linkImage" href="<c:url value='/BookServlet?method=load&bid=${item.book.bid }'/>"><img border="0" width="54" align="top" src="<c:url value='/${item.book.image_b }'/>"/></a>

</td>

<td align="left" width="400px">

    <a href="<c:url value='/BookServlet?method=load&bid=${item.book.bid }'/>"><span>${item.book.bname }</span></a>

</td>

<td><span>¥<span class="currPrice">${item.book.currPrice }</span></span></td>

<td>

<a class="jian" id="${item.cartItemId }Jian"></a><input readonly="readonly" id="${item.cartItemId }Quantity" type="text" value="${item.quantity}"/><a class="jia" id="${item.cartItemId }Jia"></a>

</td>

<td width="100px">

<span class="price_n">¥<span class="subTotal" id="${item.cartItemId }Subtotal">${item.subtotal }</span></span>

</td>

<td>

<a href="javascript:alert('购物项已删除!');">删除</a>

</td>

</tr>

</c:forEach>

 

3. 显示合计:每个条目前面都有一个复选框,默认为勾选状态。合计金额为所有被勾选的条目小计之和。我们需要获取所有被勾选的条目的小计,然后计算出合计。

 

$(function() {

showTotal();

});

 

// 显示合计

function showTotal() {

var total = 0;//创建total,准备累加

/*

1. 获取所有被勾选的复选框,遍历之

*/

$(":checkbox[name=checkboxBtn][checked=true]").each(function() {

/*

2. 通过复选框找到小计

*/

var subtotal = Number($("#" + $(this).val() + "Subtotal").text());

total += subtotal;

});

/*

3. 设置合计

*/

$("#total").text(round(total,2));

}

 

  1. 给“全选”复选框添加事件:

l 勾选“全选”:

  • 让所有条目复选框为勾选状态,重复计算合计;
  • 让“结算”按钮为有效状态;

l 不选“全选”:

  • 让所有条目复选框为不选状态,设置合计为0;
  • 让“结算”按钮为无效状态。

 

$(function() {

showTotal();//显示合计

// 给全选按钮添加点击事件

$("#selectAll").click(function() {

var flag = $(this).attr("checked");//获取全选的状态

setAll(flag);//让所有条目复选框与全选同步

setJieSuanBtn(flag);//让结算按钮与全选同步

});

});

 

// 设置所有条目复选框

function setAll(flag) {

$(":checkbox[name=checkboxBtn]").attr("checked", flag);//让所有条目的复选框与参数flag同步

showTotal();//重新设置合计

}

 

// 设置结算按钮的样式

function setJieSuanBtn(flag) {

if(flag) {// 有效状态

$("#jiesuan").removeClass("kill").addClass("jiesuan");//切换样式

$("#jiesuan").unbind("click");//撤消“点击无效”

} else {// 无效状态

$("#jiesuan").removeClass("jiesuan").addClass("kill");//切换样式

$("#jiesuan").click(function() {//使其“点击无效”

return false;

});

}

}

 

  1. 给条目复选框添加事件

l 先重新计算合计;

l 全选了:

  • 全选按钮勾选;
  • 结算按钮有效;

l 未全选:

  • 全选按钮撤消;
  • 结算按钮有效;

l 全撤消:

  • 全选按钮撤消;
  • 结算按钮无效;

// 给条目复选框添加事件

$(":checkbox[name=checkboxBtn]").click(function() {

var selectedCount = $(":checkbox[name=checkboxBtn][checked=true]").length;//被勾选复选框个数

var allCount = $(":checkbox[name=checkboxBtn]").length;//所有条目复选框个数

if(selectedCount == allCount) {//全选了

$("#selectAll").attr("checked", true);//勾选全选复选框

setJieSuanBtn(true);//使结算按钮可用

} else if(selectedCount == 0) {//全撤消了

$("#selectAll").attr("checked", false);//撤消全选复选框

setJieSuanBtn(false);//使结算按钮不可用

} else {//未全选

$("#selectAll").attr("checked", false);//撤消全选复选框

setJieSuanBtn(true);//使结算按钮可用

}

showTotal();//重新计算合计

});

 

4.5 添加条目到购物车

添加条目到购物车是从/jsps/book/desc.jsp开始的:

 

修改/jsps/book/desc.jsp的表单,请求CartItemServlet#add()方法。传递图书id和数量两个参数。

CartItemServlet#add()方法创建CartItem对象,通过bid创建Book对象设置给CartItem对象;从session中获取User对象设置给CartItem对象;把数量设置给CartItem对象;使用CommonUtils.uuid()生成cartItemId设置给CartItem对象。调用CartItemService#add()方法添加条目。最后查询所有条目,保存到request中,转发到/jsps/cart/list.jsp显示。

4.5.1 修改/jsps/book/desc.jsp中的表单

<form id="form1" action="<c:url value='/CartItemServlet'/>" method="post">

<input type="hidden" name="method" value="add"/>

<input type="hidden" name="bid" value="${book.bid }"/>

   我要买:<input id="cnt" style="width: 40px;text-align: center;" type="text" name="quantity" value="1"/>件

</form>

<a id="btn" href="javascript:$('#form1').submit();"></a>

 

4.5.2 服务器端代码实现

1. CartItemServlet

l 把表单中的bid封装到Book对象中;

l 获取session中的User;

l 把表单中的quantity封装到CartItem对象中;

l 设置CartItem的cartItemId(uuid)、book、owner;

l 调用CartItemService#add(CartItem)添加条目;

l 调用myCart(),即显示当前用户的所有条目。

CartItemServlet.java

public String add(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 封装表单中的bid到Book对象中

 */

Book book = CommonUtils.toBean(req.getParameterMap(), Book.class);

/*

 * 2. 从session中获取当前用户

 */

User owner = (User)req.getSession().getAttribute("sessionUser");

/*

 * 3. 把表单中的quantity封装到CartItem中

 */

CartItem cartItem = CommonUtils.toBean(req.getParameterMap(), CartItem.class);

/*

 * 4. 设置CartItem的id

 *    设置book

 *    设置owner

 */

cartItem.setCartItemId(CommonUtils.uuid());

cartItem.setBook(book);

cartItem.setOwner(owner);

 

/*

 * 5. 把cartItem添加到数据库

 */

cartItemService.add(cartItem);

/*

 * 6. 调用myCart(),即查询所有条目,保存到request中,转发到/jsps/cart/list.jsp

 */

return myCart(req, resp);

}

 

2. CartService

  CartItemService的add()方法需要先判断这一条目是否已经存在,如果已经存在,那么就合并数量,如果不存在,才会去添加条目。

CartService的add(CartItem)方法:

l 使用cartItem的uid和bid查询数据库,得到_cartItem;

l 如果_cartItem不为null,说明这一条目已经存在,那么合并数量,然后修改原条目的数量即可;

l 如果_cartItem为null,说明数据库中不存在这一条目,那么添加新条目。

CartService.java

public void add(CartItem cartItem) {

try {

/*

 * 查询这个条目是否已经存在,如果存在,那么就合并条目,而不是添加条目

 */

JdbcUtils.beginTransaction();

CartItem _cartItem = cartItemDao.findByUserAndBook(

cartItem.getOwner().getUid(), cartItem.getBook().getBid());

if(_cartItem == null) {//如果原来不存在这一条目,那么添加条目

cartItemDao.add(cartItem);

} else {//如果原来存在这一条目,那么把原条目和新条目的数量合并,然后修改条目数量

int quantity = cartItem.getQuantity() + _cartItem.getQuantity();

cartItemDao.updateQuantity(_cartItem.getCartItemId(), quantity);

}

JdbcUtils.commitTransaction();

} catch (SQLException e) {

try {

JdbcUtils.rollbackTransaction();

} catch (SQLException e1) {

throw new RuntimeException(e1);

}

throw new RuntimeException(e);

}

}

 

3. CartItemDao

CartItemDao中需要提供三个方法才能满足CartItemService的需要:添加条目、修改条目数量、按uid和bid查询条目。

CartItemDao.java

/**

 * 添加购物车条目

 * @param cartItem

 * @throws SQLException

 */

public void add(CartItem cartItem) throws SQLException {

String sql = "insert into t_cartItem(cartItemId,quantity,bid,uid) values(?,?,?,?)";

Object[] params = {cartItem.getCartItemId(), cartItem.getQuantity(),

cartItem.getBook().getBid(), cartItem.getOwner().getUid()};

qr.update(sql, params);

}

 

/**

 * 查询当前用户指定bid的购物车条目是否存在

 * 在添加条目时需要使用,用来判断是否合并条目

 * @param uid

 * @param bid

 * @return

 * @throws SQLException

 */

public CartItem findByUserAndBook(String uid, String bid) throws SQLException {

String sql = "select * from t_cartItem where uid=? and bid=?";

return qr.query(sql, new BeanHandler<CartItem>(CartItem.class), uid, bid);

}

 

/**

 * 修改条目的数量

 * @param cartItemId

 * @param quantity

 * @throws SQLException

 */

public void updateQuantity(String cartItemId, int quantity) throws SQLException {

String sql = "update t_cartItem set quantity=? where cartItemId=?";

qr.update(sql, quantity, cartItemId);

}

4.6 删除条目和批量删除条目

 

 

删除条目:点击某个条目后面的“删除”链接就会删除当前条目,然后会再次回到当前页面,这时你会发现当前条目已经被删除了。

批量删除:选中要删除的条目后,点击“批量删除”链接会删除被选中的条目,然后会再次回到当前页面。

4.6.1 修改/jsps/cart/list.jsp

  修改“删除”和“批量删除”链接,其中“删除”链接直接请求CartItemServlet的delete()方法,传递当前条目的cartItemId;而“批量删除”链接需要通过javascript函数获取所有被选中的条目的cartItemId,然后请求CartItemServlet的delete()方法,传递所有被选中条目的cartItemId。

<a href="<c:url value='/CartItemServlet?method=delete&cartItemIds=${item.cartItemId }'/>">删除</a>

<a href="javascript:deleteBatch();">批量删除</a>

function deleteBatch() {

var cartItemIds = new Array();//创建数据

var index = 0;

$(":checkbox[name=checkboxBtn][checked=true]").each(function() {//循环遍历所有被选中的条目

cartItemIds[index++] = $(this).val();//把被选择条目的cartItemId保存到数组中

});

if(cartItemIds.length == 0) {//如果没有被选中的条目

alert("请选择要删除的条目!");

return;

}

location = "<c:url value='/CartItemServlet?method=delete&cartItemIds='/>" + cartItemIds;

}

4.6.2 服务器端代码实现

CartItemServlet#delete()方法:

l 获取参数cartItemIds;

l 调用CartItemService#deleteBatch(String)方法完成批量删除;

l 调用本类myCart()返回到/jsps/cart/list.jsp显示所有条目。

 

CartItemService#deleteBatch(String cartItemIds):

l 直接调用CartItemDao#deleteBatch(String)方法。

 

CartItemDao#deleteBatch(String cartItemIds):

l 生成SQL语句,条件子句为:where cartItemId in (?,?,?,…),参数cartItemIds中包含几个ID,条件子句中就包含几个问号;

l 把cartItemIds分隔成数组,执行SQL语句时,数组就是参数值,对应问号。

 

CartItemServlet.java

public String delete(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String cartItemIds = req.getParameter("cartItemIds");

cartItemService.deleteBatch(cartItemIds);

return myCart(req, resp);

}

 

CartItemService.java

public void deleteBatch(String cartItemIds) {

try {

cartItemDao.deleteBatch(cartItemIds);

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

 

CartItemDao.java

public void deleteBatch(String cartItemIds) throws SQLException {

/*

 * 1. 拼凑SQL语句

 */

StringBuilder sql = new StringBuilder("delete from t_cartitem where cartItemId in ");

Object[] params = cartItemIds.split(",");//必须是Object[]类型,而不能是String[]类型!

sql.append("(");

for(int i = 0; i < params.length; i++) {

sql.append("?");

if(i < params.length - 1) {

sql.append(",");

}

}

sql.append(")");

/*

 * 2. 执行SQL语句

 */

// 如果这里的params是String[]类型,那么那么就表示可变参数的一个元素了。

qr.update(sql.toString(), params);

}

 

4.7 修改条目数量

修改条目数量是在当前条目上点击“加”、“减”按钮完成的。这里我们使用异步请求来完成。

4.7.1 /jsps/cart/list.jsp中的javascript

我们需要给所有的“加”、“减”按钮添加事件!

l 添加数量:

  • 通过“加”按钮的id得到当前条目的cartItemId;
  • 再通过cartItemId找到对应输入框中的数量;
  • 发送异步请求,数量为原数量+1。

l 减少数量:

  • 通过“减”按钮的id得到当前条目的cartItemId;
  • 再通过cartItemId找到对应输入框中的数量;
  • 如果数量等于1,这说明再减就为0了,那么就表示要删除当前条目,所以弹出确认框,内容为“你是否要删除该条目?”,如果选择是,那么设置location,完成删除当前条目;
  • 如果数量大于1,那么发送异步请求,数量为原数量-1。

 

发送异步请求成功:

l 得到result结果;

l 使用result.quantity来更新输入框内容;

l 使用result.subtotal来更新小计;

l 重新计算合计。

// 给jia、jian添加事件

$(".jian").click(function() {

var cartItemId = $(this).attr("id").substring(0, 32);

var quantity = Number($("#" + cartItemId + "Quantity").val());

if(quantity == 1) {

if(confirm("您是否真要删除该条目?")) {

location = "<c:url value='/CartItemServlet?method=delete&cartItemIds='/>" + cartItemId;

}

} else {

sendUpdate(cartItemId, quantity-1);

}

});

$(".jia").click(function() {

var cartItemId = $(this).attr("id").substring(0, 32);

var quantity = Number($("#" + cartItemId + "Quantity").val());

sendUpdate(cartItemId, quantity+1);

});

function sendUpdate(cartItemId, quantity) {

/*

 1. 通过cartItemId找到输入框元素

 2. 通过cartItemId找到小计元素

*/

var input = $("#" + cartItemId + "Quantity");

var subtotal = $("#" + cartItemId + "Subtotal");

/*

 3. 发送异步请求,请求目标为CartItemServlet。

   参数为:method:"updateQuantity", cartItemId:cartItemId, quantity:quantity

 4. 执行成功得到结果,结果中包含两个属性:quantity和subtotal,使用它们来更新输入框和小计

 5. 执行失败:弹出网络繁忙,请稍后。

*/

$.ajax({

async:false,

cache:false,

url:"<c:url value='/CartItemServlet'/>",

data:{method:"updateQuantity",cartItemId:cartItemId,quantity:quantity},

type:"POST",

dataType:"json",

success: function(result) {

input.val(result.quantity);

subtotal.text(result.subtotal);

showTotal();

},

error: function(a,b,c) {

alert("网络繁忙,请稍后!");

}

});

}

 

4.7.2 服务器端代码实现

1. CartItemServlet

CartItemServlet#updateQuantity()方法

l 首先获取cartItemId和quantity两个参数;

l 调用cartItemService#updateQuantity(cartItemid, quantity)方法完成条目数量的修改;

l 使用cartItemId加载当前条目;

l 把当前条目的quantity和subtotal封装成json对象返回。

public String updateQuantity(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 获取cartItemId和quantity

 */

String cartItemId = req.getParameter("cartItemId");

int quantity = Integer.parseInt(req.getParameter("quantity"));

/*

 * 2. 调用cartItemService#updateQuantity()方法修改数量

 */

cartItemService.updateQuantity(cartItemId, quantity);

/*

 * 3. 通过cartItemId加载CartItem对象

 */

CartItem cartItem = cartItemService.load(cartItemId);

/*

 * 4. 把cartItem对象的quantity和subtotal封装成json对象返回

 */

String result = "{\"quantity\":" + quantity + ", \"subtotal\":" + cartItem.getSubtotal() + "}";

resp.getWriter().print(result);

return null;

}

 

  对于DAO和SERVICE中的方法这里就省略了。

 

4.8 显示选中的条目

  在购物车中选中条目,然后显示“结算”按钮显示选中的条目。用来准备生成订单!

 

 

4.8.1 /jsps/cart/list.jsp

在点击“结算”按钮时:

l 获取被选中的条目id;

l 判断是否有条目被选中,如果没有弹出警告框;

l 设置给列表的hidden字符;

l 提交表单。

<a href="javascript:jieSuan();" id="jiesuan" class="jiesuan"></a>

function jieSuan() {

/*

1. 获取选中的条目id

*/

var cartItemIds = new Array();//创建数据

var index = 0;

$(":checkbox[name=checkboxBtn][checked=true]").each(function() {//循环遍历所有被选中的条目

cartItemIds[index++] = $(this).val();//把被选择条目的cartItemId保存到数组中

});

/*

2. 如果一个条目也没有选中,那么弹出警告框

*/

if(cartItemIds.length == 0) {//如果没有被选中的条目

alert("请选择条目!");

return;

}

/*

3. 设置表单的隐藏字段

*/

$("#cartItemIds").val(cartItemIds);

/*

4. 提交表单

*/

$("#form1").submit();

}

<form id="form1" action="<c:url value='/CartItemServlet'/>" method="post">

<input type="hidden" name="cartItemIds" id="cartItemIds"/>

<input type="hidden" name="method" value="loadCartItems"/>

</form>

 

4.8.2 服务器端代码实现

CartItemServlet#loadCartItems()方法:

l 获取cartItemIds;

l 调用service方法完成加载,并保存到request中;

l 转发到/jsps/cart/showitem.jsp页面显示。

CartItemServlet.java

public String loadCartItems(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String cartItemIds = req.getParameter("cartItemIds");

List<CartItem> cartItemList = cartItemService.loadCartItems(cartItemIds);

req.setAttribute("cartItemList", cartItemList);

req.setAttribute("cartItemIds", cartItemIds);

return "/jsps/cart/showitem.jsp";

}

 

4.8.3 showitem.jsp显示被选中条目

1. 循环显示所有条目

<c:forEach items="${cartItemList }" var="item">

<tr align="center">

<td align="right">

<a class="linkImage" href="<c:url value='/BookServlet?method=load&bid=${item.book.bid }'/>"><img border="0" width="54" align="top" src="<c:url value='/${item.book.image_b }'/>"/></a>

</td>

<td align="left">

<a href="<c:url value='/BookServlet?method=load&bid=${item.book.bid }'/>"><span>${item.book.bname }</span></a>

</td>

<td>¥${item.book.currPrice }</td>

<td>${item.quantity }</td>

<td>

<span class="price_n">¥<span class="subtotal">${item.subtotal }</span></span>

</td>

</tr>

</c:forEach>

 

2. 计算合计并显示

<span id="total"></span>

$(function() {

var total = 0;

$(".subtotal").each(function() {

total += Number($(this).text());

});

$("#total").text(round(total, 2));

});

 

5 订单模块

5.1 创建购物车模块相关类

在每个模块开始时,都要创建如下基本类:实体类、DAO类、Service类、Servlet类:

5.1.1 Order和OrderItem

Order和OrderItem是一对多关系(一个订单中可以包含多个订单条目)!业务需求中显示订单时,一定要显示其订单条目,而没有只显示某个订单条目的。所以,这里我们需要把这两个类定义为双向关联,即Order中有List<OrderItem>类型属性,并且OrderItem中有Order类型的属性。

Order.java

public class Order {

private String oid;

private String ordertime;

private double total;

private int status;

private String address;

private User owner;

private List<OrderItem> orderItemList;

}

 

OrderItem.java

public class OrderItem {

private String orderItemId;

private int quantity;

private double subtotal;

private Book book;

private Order order;

}

 

5.2 订单模块功能介绍

订单模块的功能只有登录用户才能使用,所以需要在LoginFilter中添加对订单模块页面,以及Servlet的过滤:

<filter>

<filter-name>LoginFilter</filter-name>

<filter-class>cn.itcast.bookstore.web.filter.LoginFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>LoginFilter</filter-name>

<servlet-name>CartItemServlet</servlet-name>

<url-pattern>/jsps/cart/*</url-pattern>

<servlet-name>OrderServlet</servlet-name>

<url-pattern>/jsps/order/*</url-pattern>

</filter-mapping>

 

订单的状态有5种:

l 1:未付款,刚刚生成的订单就是未付款状态;

l 2:未发货,订单支付后为已付款,但未发货状态;

l 3:未确认收货:当后台管理员发货后,但前台用户没有确认收货时;

l 4:交易成功:当用户确认收货后,订单结束,为交易成功状态;

l 5:已取消:未付款状态的订单是可以取消的,其他状态不行。

 

 

订单模块功能如下:

l 生成订单;

l 我的订单(分页查询);

l 订单支付;

l 查看订单详细;

l 取消未支付订单;

l 确认收货

5.3 生成订单

生成订单从/jsps/cart/showitem.jsp页面开始,用户在页面中输入收货地址,然后点击“提交订单”按钮即可。

 

5.3.1 /jsps/cart/showitem.jsp

在showitem.jsp页面中,获取所有条目的cartItemId,然后请求OrderServlet的create()方法来生成订单。

在/jsps/cart/list.jsp页面中选中购物车条目后,点击“结算”按钮,通过CartServlet#loadCartItems()方法,然后转发到showitem.jsp页面,其中loadCartItems()方法会把选中的购物车条目id保存到request中,在showitem.jsp页面中,把它显示在表单中。

showitem.jsp

<form id="form1" action="<c:url value='/OrderServlet'/>" method="post">

<input type="hidden" name="cartItemIds" value="${cartItemIds }"/>

<input type="hidden" name="method" value="create"/>

    …

</form>

 

5.3.2 服务器端代码实现

1. OrderServlet#create()方法

l 获取所有购物车条目的cartItemId,以及收货地址;

l 通过cartItemId来加载CartItem,得到List<CartItem>;

l 创建订单Order;

l 循环遍历List<CartItem>,通过每个CartItem生成一个OrderItem,最终得到List<OrderItem>,然后把List<OrderItem>设置给Order对象;

l 使用OrderService的create(Order)方法创建订单;

l 删除购物车中被选中条目,也就是说,生成订单的条目需要从购物车中删除;

l 把当前订单保存到request中,转发到/jsps/order/ordersucc.jsp页面。

OrderServlet.java

public String create(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 获取所有购物车条目id,以及收货地址

 */

String cartItemIds = req.getParameter("cartItemIds");

String address = req.getParameter("address");

/*

 * 2. 通过CartItemService加载所有购物车条目

 */

List<CartItem> cartItemList = cartItemService.loadCartItems(cartItemIds);

/*

 * 3. 创建订单

 */

Order order = new Order();

order.setOid(CommonUtils.uuid());//设置oid

order.setOrdertime(String.format("%tF %<tT", new java.util.Date()));//设置下单时间为当前时间

// 设置合计

BigDecimal total = new BigDecimal("0");

for(CartItem cartItem : cartItemList) {

total = total.add(new BigDecimal(Double.toString(cartItem.getSubtotal())));

}

order.setTotal(total.doubleValue());

 

order.setStatus(1);// 设置状态,刚生成的订单为1状态,表示未付款

order.setAddress(address);// 设置地址

// 设置所属会员

User owner = (User)req.getSession().getAttribute("sessionUser");

order.setOwner(owner);

/*

 * 4. 通过List<CartItem>来创建List<OrderItem>,再把List<OrderItem>设置给Order

 */

List<OrderItem> orderItemList = new ArrayList<OrderItem>();

for(CartItem cartItem : cartItemList) {

OrderItem orderItem = new OrderItem();

orderItem.setOrderItemId(CommonUtils.uuid());//设置orderItemId

orderItem.setQuantity(cartItem.getQuantity());//设置数量

orderItem.setSubtotal(cartItem.getSubtotal());//设置小计

orderItem.setBook(cartItem.getBook());//设置book

orderItem.setOrder(order);//设置所属订单

 

orderItemList.add(orderItem);

}

order.setOrderItemList(orderItemList);//把订单条目设置给订单

/*

 * 5. 调用orderService方法生成order

 */

orderService.create(order);//设置order

// 删除购物车中用来生成订单的条目

cartItemService.deleteBatch(cartItemIds);

/*

 * 6. 保存Order到request中,转发到/jsps/order/ordersucc.jsp

 */

req.setAttribute("order", order);

return "f:/jsps/order/ordersucc.jsp";

}

 

2. OrderService.java

在OrderService的create(Order)方法中直接调用OrderDao的add(Order)方法,但在OrderService中需要添加事务处理。

OrderService.java

public void create(Order order) {

try {

JdbcUtils.beginTransaction();

orderDao.add(order);

JdbcUtils.commitTransaction();

} catch(SQLException e) {

try {

JdbcUtils.rollbackTransaction();

} catch (SQLException e1) {

throw new RuntimeException(e1);

}

throw new RuntimeException(e);

}

}

 

3. OrderDao#add(Order)方法

在OrderDao的add(Order)方法中:

l 添加订单

  • 给出t_order的insert语句;
  • 执行之;

l 添加订单条目

  • 循环遍历所有OrderItem,生成Object[][]类型的参数;
  • 给出t_orderitem的insert语句;
  • 执行批处理。

 

5.3.3 /jsps/order/ordersucc.jsp

在ordersucc.jsp页面中显示当前刚刚生成的订单信息:

 

5.4 我的订单

5.4.1 top.jsp

我的订单是从top.jsp中点击“我的订单”链接开始的,所以我们需要把“我的订单”链接请求的目录修改为OrderServlet#myOrders()。

top.jsp

<a href="<c:url value='/OrderServlet?method=myOrders'/>" target="body">我的订单</a>

 

5.4.2 服务器端代码实现

1. OrderServlet#myOrders()

OrderServlet#myOrders()需要分页查询订单。可以参考图书模块中的分页查询来完成订单分页。

OrderServlet.java

public String myOrders(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 获取当前页码

 */

int pc = getPageCode(req);

/*

 * 2. 获取当前用户uid

 */

User user = (User)req.getSession().getAttribute("sessionUser");

PageBean<Order> pb = orderService.myOrders(user.getUid(), pc);

/*

 * 3. 获取url,设置给PageBean

 */

String url = getUrl(req);

pb.setUrl(url);

/*

 * 4. 把PageBean保存到request,转发到/jsps/order/list.jsp

 */

req.setAttribute("pb", pb);

return "f:/jsps/order/list.jsp";

}

 

2. OrderDao.java

/**

 * 按用户查询订单

 * @param uid

 * @return

 * @throws SQLException

 */

public PageBean<Order> findByUser(String uid, int pc) throws SQLException {

Map<String,Object> criteria = new HashMap<String,Object>();

criteria.put("uid", uid);

return findByCriteria(criteria, pc);

}

 

/*

 * 根据条件分页查询

 */

private PageBean<Order> findByCriteria(Map<String,Object> criteria, int pc) throws SQLException {

/*

 * 1. 创建sql语句条件子句

 */

List<Object> params = new ArrayList<Object>();//条件,对应sql中的“?”

StringBuilder whereSql = new StringBuilder(" where 1=1");

for(String name : criteria.keySet()) {//循环遍历每个条件;

Object value = criteria.get(name);

if(value == null) {//如果值为空,说明没有这个条件

continue;

}

whereSql.append(" and ").append(name).append("=?");

params.add(value);

}

 

/*

 * 2. 创建排序和limit子句

 */

String orderByAndLimitSql = " order by ordertime desc limit ?,?";

 

/*

 * 3. 生成个数查询语句,执行sql,得到总记录数

 */

String cntSql = "select count(*) from t_order" + whereSql;

Number cnt = (Number)qr.query(cntSql, new ScalarHandler(), params.toArray());

int tr = cnt != null ? cnt.intValue() : 0;

 

/*

 * 4. 生成记录查询,执行SQL语句,得到当前页记录

 */

String sql = "select * from t_order" + whereSql + orderByAndLimitSql;

 

// 计算limit参数

int ps = PageConstants.BOOK_PAGE_SIZE;//得到每页记录数

params.add(ps * (pc-1));//计算当前页第一条记录的下标位置,下标从0开始

params.add(ps);//一共查询几条记录

 

// 把mapList映射成List<Book>

List<Order> orderList = qr.query(sql, new BeanListHandler<Order>(Order.class), params.toArray());

for(Order order : orderList) {

loadOrderItemList(order);//为当前Order加载其所有OrderItem

}

/*

 * 5. 创建PageBean,返回

 */

PageBean<Order> pb = new PageBean<Order>();

pb.setPc(pc);

pb.setPs(ps);

pb.setTr(tr);

pb.setDataList(orderList);

return pb;

}

 

/*

 * 为当前Order对象加载其所有的OrderItem

 */

private void loadOrderItemList(Order order) throws SQLException {

/*

 * 1. 执行SQL语句,得到List<Map>,List<Map>中的很Map对象t_orderitem表中的一行记录

 */

String sql = "select * from t_orderitem where oid=?";

List<Map<String,Object>> mapList = qr.query(sql, new MapListHandler(), order.getOid());

/*

 * 2. 循环遍历每个Map,把每个Map映射成一个OrderItem对象和一个Book对象,然后再建立关系

 *   再把OrderItem添加到List中

 */

List<OrderItem> orderItemList = new ArrayList<OrderItem>();

for(Map<String,Object> map : mapList) {

OrderItem orderItem = CommonUtils.toBean(map, OrderItem.class);

Book book = CommonUtils.toBean(map, Book.class);

orderItem.setBook(book);

orderItem.setOrder(order);

 

orderItemList.add(orderItem);

}

/*

 * 把生成的List<OrderItem>设置给当前Order对象。

 */

order.setOrderItemList(orderItemList);

}

 

5.4.3 /jsps/order/list.jsp

在list.jsp页面中需要使用两层嵌套循环来打印订单列表,第一层循环打印的是订单,第二层打印的是订单条目。

<c:forEach items="${pb.dataList }" var="order">

<tr class="tt">

<td width="320px">订单号:<a  href="<c:url value='/jsps/order/desc.jsp'/>">${order.oid }</a></td>

<td width="200px">下单时间:${order.ordertime }</td>

<td> </td>

<td> </td>

<td> </td>

<td> </td>

</tr>

<tr style="padding-top: 10px; padding-bottom: 10px;">

<td colspan="2">

 

 

<c:forEach items="${order.orderItemList }" var="item">

<a class="link2" href="<c:url value='/BookServlet?method=load&bid=${item.book.bid }'/>">

    <img border="0" width="70" src="<c:url value='/${item.book.image_b }'/>"/>

</a>

</c:forEach>

 

 

</td>

<td width="115px">

<span class="price_t">¥${order.total }</span>

</td>

<td width="142px">

<c:choose>

<c:when test="${order.status eq 1 }">未付款</c:when>

<c:when test="${order.status eq 2 }">未发货</c:when>

<c:when test="${order.status eq 3 }">未确认收货</c:when>

<c:when test="${order.status eq 4 }">交易完成</c:when>

<c:when test="${order.status eq 5 }">已取消</c:when>

</c:choose>

</td>

<td>

<a href="<c:url value='/jsps/order/desc.jsp'/>">查看</a><br/>

<c:choose>

<c:when test="${order.status eq 1 }">

<a href="<c:url value='/jsps/order/pay.jsp'/>">支付</a><br/>

<a href="<c:url value='/jsps/order/desc.jsp'/>">取消</a><br/>

</c:when>

<c:when test="${order.status eq 3 }">

<a href="<c:url value='/jsps/order/desc.jsp'/>">确认收货</a><br/>

</c:when>

</c:choose>

</td>

</tr>

</c:forEach>

<%@include file="/jsps/pager/pager.jsp" %>

 

5.5 查看订单详细

  查看订单详细是从/jsps/order/list.jsp开始的,点击“订单编号”、“查看”、“取消”、“确认收货”链接,都会查看订单详细。最终到达/jsps/order/desc.jsp页面显示订单详细信息,点击不同的链接到达/jsps/order/desc.jsp页面显示不同的按钮。例如,点击“取消”按钮,到达desc.jsp显示“取消”按钮;点击“确认收货”链接到达desc.jsp显示“确认收货”按钮;如果点击“查看”链接,并且订单状态为“未付款”,那么显示“支付”按钮。

5.5.1 /jsps/order/list.jsp

修改/jsps/order/list.jsp页面中的N个链接,请求OrderServlet的load()方法,传递oid参数,以及oper参数,其中oper参数用来说明点击的是什么按钮:

/jsps/order/list.jsp

订单号:<a  href="<c:url value='/OrderServlet?method=load&oid=${order.oid }&oper=desc'/>">${order.oid }</a>

<a href="<c:url value='/OrderServlet?method=load&oid=${order.oid }&oper=desc'/>">查看</a>

<c:choose>

 <c:when test="${order.status eq 1 }">

     <a href="<c:url value='/jsps/order/pay.jsp'/>">支付</a><br/>

     <a href="<c:url value='/OrderServlet?method=load&oid=${order.oid }&oper=cancel'/>">取消</a><br/>

 </c:when>

 <c:when test="${order.status eq 3 }">

 <a href="<c:url value='/OrderServlet?method=load&oid=${order.oid }&oper=confirm'/>">确认收货</a><br/>

 </c:when>

</c:choose>

5.5.2 服务器端代码实现

1. OrderServlet#load()

OrderServlet#load()方法:

l 获取oid,以及oper;

l 调用OrderService#load(String oid)方法得到Order对象;

l 保存Order对象到request中,保存oper到request中;

l 转发到/jsps/order/desc.jsp

OrderServlet.java

public String load(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String oid = req.getParameter("oid");//获取订单编号

String oper = req.getParameter("oper");//获取操作,包括:查看、确认收货、取消

req.setAttribute("order", orderService.load(oid));//加载订单并保存到request中

req.setAttribute("oper", oper);//把操作也保存到request中,在desc.jsp中会通过oper来显示不同的按钮

return "/jsps/order/desc.jsp";//转发到desc.jsp

}

 

2. OrderDao#load(String oid)

OrderDao#load(String oid):

l 查询Order对象;

l 为Order加载其所有OrderItem。

OrderDao.java

public Order load(String oid) throws SQLException {

String sql = "select * from t_order where oid=?";

Order order = qr.query(sql, new BeanHandler<Order>(Order.class), oid);

loadOrderItemList(order);

return order;

}

5.5.3 /jsps/order/desc.jsp

desc.jsp页面要显示当前订单内容,而且还要根据oper来显示不同的按钮。

<%--

用户点击的是“查看”或“订单编号”链接过来的,并且当前订单是“未付款”状态,显示“支付”按钮

--%>

<c:if test="${oper eq 'desc' and order.status eq 1}">

<a href="<c:url value='/jsps/order/pay.jsp'/>" class="pay"></a><br/>

</c:if>

<%--

用户点击的是“取消”链接,并且当前订单是“未付款”状态,显示“取消”按钮

--%>

<c:if test="${oper eq 'cancel' and order.status eq 1}">

    <a id="cancel" href="<c:url value='/jsps/msg.jsp'/>">取消订单</a><br/>

</c:if>

<%--

用户点击的是“确认收货”链接,并且当前订单是“未确认”状态,显示“确认收货”按钮

--%>

<c:if test="${oper eq 'confirm' and order.status eq 3}">

<a id="confirm" href="<c:url value='/jsps/msg.jsp'/>">确认收货</a><br/>

</c:if>

 

5.6 取消未付款订单

取消未付款订单是在/jsps/order/desc.jsp页面,点击“取消订单”按钮开始,请求OrderServlet#cancel()方法,调用OrderService#updateStatus(String oid, int stauts)方法完成。最后中转发到msg.jsp显示“订单已取消”。

5.6.1 修改desc.jsp的链接

<a id="cancel" href="<c:url value='/OrderServlet?method=cancel&oid=${order.oid }/>">取消订单</a>

 

 

5.6.2 服务器端代码实现

1. OrderServlet#cancel()

l 获取oid;

l 调用orderService#updateStatus()方法,传递oid,以及状态为5;

l 向request中保存“订单已取消”,转发到msg.jsp显示。

 

5.7 确认收货

  确认收货与取消订单很相似,都是修改订单的状态,只是一个是4状态,一个是5状态而已,这里就不在赘述!

5.8 订单支付

订单支付分为两步:

l 在/jsps/order/ordersucc.jsp、/jsps/order/list.jsp、/jsps/order/desc.jsp中点击链接到达/jsps/order/pay.jsp页面;

l 在/jsps/order/pay.jsp页面选择银行,点击下一步开始在线支付。

5.8.1 到达pay.jsp页面

在/jsps/order/ordersucc.jsp、/jsps/order/list.jsp、/jsps/order/desc.jsp三个页面中都有链接可以到达pay.jsp,下面我们来修改这三个页面的链接。

这三个页面的链接请求OrderServlet的prepareForPayment()方法,传递oid参数。该方法获取oid,加载订单,保存到request中,转发到pay.jsp页面。

ordersucc.jsp

 

list.jsp

 

desc.jsp

 

 

2. OrderServlet

OrderServlet.java

public String prepareForPayment(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String oid = req.getParameter("oid");

req.setAttribute("order", orderService.load(oid));

return "/jsps/order/pay.jsp";

}

 

3. pay.jsp

在pay.jsp页面中显示当前订单部分信息。

pay.jsp

 

5.8.2 在线支付

5.8.2.1 在线支付分类

所谓在线支付,就是让电商与网银对接。当用户在电商点击支付时页面会跳转到网银页面,用户完成支付后,再返回到电商页面。

在线支付分为两种方式:

l 电商与网银直接对接:

  • 安全性好;
  • 没有服务费;
  • 但银行一般不会和小的电商合作。

l 电商通过第三方支付与网银对接:

  • 安全性差;
  • 有服务费;
  • 只要有ICP即可在第三方支付平台开户。

 

 

第三方支付平台有:支付宝、易宝、财富通等等,我们需要先在第三方上注册一个商家账号(注册需要ICP),然后使用第三方提供的支付接口(履行第三方协议规范)完成在线支付,这样买家的钱会打到电商在第三方的商家账号上。

因为在第三方注册商家账号需要ICP,还要审核,所以也不是很方便。但我们现在有易宝支付的一个测试账号。在我们练习在线支付时,钱都打到这个测试账号上去,这个钱是不能退回的,所以大家一定要注意,每次打一分钱即可。

 

 

5.8.3 支付功能

本系统的支付功能从/jsps/order/pay.jsp开始,请求OrderServlet#payment()方法,传递当前要支付的订单oid,以及用户选择的银行编号:

l 获取参数,以及配置文件,得到13参数,以及keyValue(密钥);

l 使用13参数和keyValue来调用PaymentUtil.buildHmac()方法,得到hmac;

l 把13参数和hmac保存到request中,转发到/WEB-INF/jsp/sendpay.jsp。

  /WEB-INF/jsp/sendpay.jsp页面中获取13参数和hmac保存到表单中,这个表单会自动请求易宝网关(使用javascript来完成自动提交)。

 

src/payment.properties

p1_MerId=10001126856

keyValue=69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl

p8_Url=http://42.87.163.104:8080/goods/OrderServlet?method=back

 

OrderServlet#payment

public String payment(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

/*

 * 1. 准备访问易宝网关的数据

 */

Properties props = new Properties();

props.load(this.getClass().getClassLoader()

.getResourceAsStream("payment.properties"));

 

String p0_Cmd = "Buy";// 业务类型

String p1_MerId = props.getProperty("p1_MerId");// 商户编号,从配置文件上加载

String p2_Order = req.getParameter("oid");// 商户订单号

String p3_Amt = "0.01";// 支付金额

String p4_Cur = "CNY";// 交易币种

String p5_Pid = "";// 商品名称

String p6_Pcat = "";// 商品种类

String p7_Pdesc = "";// 商品描述

String p8_Url = props.getProperty("p8_Url");// 商户接收支付成功数据的地址,从配置文件上加载

String p9_SAF = "";// 送货地址

String pa_MP = "";// 商户扩展信息

String pd_FrpId = req.getParameter("yh");// 支付通道编码

String pr_NeedResponse = "1";// 应答机制

 

// 使用密钥生成hmac,它是不可逆的

String keyValue = props.getProperty("keyValue");// 密钥,从配置文件上加载

String hmac = PaymentUtil.buildHmac(p0_Cmd, p1_MerId, p2_Order, p3_Amt,

p4_Cur, p5_Pid, p6_Pcat, p7_Pdesc, p8_Url, p9_SAF, pa_MP,

pd_FrpId, pr_NeedResponse, keyValue);

 

/*

 * 把数据保存到request中,

 * 转发到/WEB-INF/jsp/sendpay.jsp页面,

 * 这个页面会把数据显示在表单中,表单会自动提交到易宝网关。

 */

req.setAttribute("p0_Cmd", p0_Cmd);

req.setAttribute("p1_MerId", p1_MerId);

req.setAttribute("p2_Order", p2_Order);

req.setAttribute("p3_Amt", p3_Amt);

req.setAttribute("p4_Cur", p4_Cur);

req.setAttribute("p5_Pid", p5_Pid);

req.setAttribute("p6_Pcat", p6_Pcat);

req.setAttribute("p7_Pdesc", p7_Pdesc);

req.setAttribute("p8_Url", p8_Url);

req.setAttribute("p9_SAF", p9_SAF);

req.setAttribute("pa_MP", pa_MP);

req.setAttribute("pd_FrpId", pd_FrpId);

req.setAttribute("pr_NeedResponse", pr_NeedResponse);

req.setAttribute("hmac", hmac);

 

return "/WEB-INF/jsp/sendpay.jsp";

}

 

/WEB-INF/jsp/sendpay.jsp

<form action="https://www.yeepay.com/app-merchant-proxy/node" method="get" id="form1">

<input type="hidden" name="p0_Cmd" value="${p0_Cmd }"/>

<input type="hidden" name="p1_MerId" value="${p1_MerId }"/>

<input type="hidden" name="p2_Order" value="${p2_Order }"/>

<input type="hidden" name="p3_Amt" value="${p3_Amt }"/>

<input type="hidden" name="p4_Cur" value="${p4_Cur }"/>

<input type="hidden" name="p5_Pid" value="${p5_Pid }"/>

<input type="hidden" name="p6_Pcat" value="${p6_Pcat }"/>

<input type="hidden" name="p7_Pdesc" value="${p7_Pdesc }"/>

<input type="hidden" name="p8_Url" value="${p8_Url }"/>

<input type="hidden" name="p9_SAF" value="${p9_SAF }"/>

<input type="hidden" name="pa_MP" value="${pa_MP }"/>

<input type="hidden" name="pd_FrpId" value="${pd_FrpId }"/>

<input type="hidden" name="pr_NeedResponse" value="${pr_NeedResponse }"/>

<input type="hidden" name="hmac" value="${hmac }"/>

</form>

$(function() {

$("#form1").submit();

});

标签:return,String,req,jsps,book,jsp,前台,商城,图书
From: https://www.cnblogs.com/LiuZhongquan/p/17433988.html

相关文章

  • 商城项目搭建
    项目搭建1.前台需求分析前台是针对用户购书,而后台是管理员管理系统。1.1前台主页  /index.jsp使用<jsp:forward>转发到/jsps/main.jsp,、main.jsp中只有一个<table>,结构如下(1)图书商城用户名:张三我的购物车 我的订单 修改密码 退出(2)图书分类......
  • 报错问题:谷粒商城关于pubsub、publish报错,无法发送查询品牌信息的请求
    1、npminstall--savepubsub-js2、在src下的main.js中引用:①importPubSubfrom'pubsub-js'②Vue.prototype.PubSub=PubSub ......
  • C/C++图书信息管理系统[2023-05-25]
    C/C++图书信息管理系统[2023-05-25]图书信息管理系统主要内容:整个图书管理系统采用结构体作为基本数据结构,最终数据一定要保存到数据文件中。主函数通过switch语句来根据用户的需求跳转到各个模块,以实现用户的需要。系统要求能进行图书信息的浏览、添加、查询、修改、删除......
  • 直播商城系统源码,BottomSheetDialog实现-底部滑动栏
    直播商城系统源码,BottomSheetDialog实现-底部滑动栏bottom_popwindoow.xml中的代码 <?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"  android:orientation="vertical"......
  • 前台向后台传值后台接到乱码
    前台向后台传值后台接到乱码总会遇到前台输出的值没有问题正常的,传给后台就是乱码 1、首先将请求改成post请求2、其次在后台接收值的位置上加上转码就可以了:例:str为前台传入在后台接收的字符串Stringstr=newString(str.getBytes("ISO8859-1"),"UTF-8")  亲测有......
  • 仿京东淘宝购物商城手机小程序全套页面业务流程源码
    分享一个仿淘宝京东商城的小程序源码,是使用微信开发者工具开发的,亲测可直接运行。该程序包含了详细完整的框架架构结构,从设计上满足了相关应用服务的设计要求,是一款非常值得学习小程序源码。源码地址项目目录效果预览......
  • odoo 后台传递信息给前台
     defset_values(self):super(ResConfigSettings,self).set_values()self.env['ir.config_parameter'].sudo().set_param('invoicefilepath',self.invoicefilepath)print(dir(exceptions))raiseexceptions.warnings("错误,地址无权限.......
  • java基于springboot+vue的土特产在线销售平台、特产在线销售商城,附源码+数据库+lw文档
    1、项目介绍考虑到实际生活中在藏区特产销售管理方面的需要以及对该系统认真的分析,将系统权限按管理员和用户这两类涉及用户划分。(1)管理员功能需求管理员登陆后,主要模块包括首页、个人中心、用户管理、特产信息管理、特产分类管理、特产分类管理、特产评分管理、系统管理、订单......
  • 多款前端商城购物网站html模板源码
    1、仿淘宝粉色女性化妆品网上商城模板html源码​编辑切换为居中添加图片注释,不超过140字(可选)​编辑切换为居中添加图片注释,不超过140字(可选)​编辑切换为居中添加图片注释,不超过140字(可选)2、淘宝京东商......
  • COMP30027 图书预测算法
    SchoolofComputingandInformationSystemsTheUniversityofMelbourneCOMP30027,MachineLearning,2023Project2:BookRatingPredictionTask:BuildaclassifiertopredicttheratingofbooksDue:GroupRegistration:Friday5May,5pmStageI:Friday19May......