前台功能实现
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 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>中。
- 创建页面元素
<body> <div id="menu"></div> </body> |
页面中只有一个<div>
- 创建全局对象
var bar = new Q6MenuBar("bar", "ITCAST网络图书商城"); |
创建Q6MenuBar对象:
l 它必须是一个全局对象;
l 参数“bar”必须与对象名相同(例如:var abc=new Q6MenuBar(“abc”, “xxx”);
l 参数“ITCAST网络图书商城”指定“手风琴”的标题。
- 配置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表示可以同时展开多个一级菜单。
- 添加一级、二级菜单
$(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 分页数据
- 列表页面
我们希望在页面上得到如下结果:
列表页面需要:
l 当前页的记录,即所有图书;
l 当前页码;
l 总页数;
l 要请求的URL,以及请求参数。
- Servlet
列表页面需要的数据都由Servlet保存到request中传递给页面,也就是说,页面需要的就是Servlet的工作。
Servlet的任务有:
l 当前页码:由页面传递,如果页面没有传递那么就是1;
l 总页数:需要总记录数和每页记录数来计算:
- 总记录数:查询数据库;
- 每页记录数:系统数据;
l 当前页记录:查询数据库;
l 请求的URL及参数:Servlet需要把每次请求时的URL及参数再次传递回页面。
- 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>中即可。
- 上一页和下一页
我们首先来完成“上一页”和“下一页”。
<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> |
- 共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> |
- 页码列表
实现页码列表需要先确定页码列表的开始值(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)); } |
- 给“全选”复选框添加事件:
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; }); } } |
- 给条目复选框添加事件
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(); }); |