前台一共有以下这些场景
这里记录购买商品这里场景开发,首先看看购物流程
1. 登录
2. 访问产品页
3. 立即购买
4. 进入结算页面
5. 加入购物车
6. 查看购物车
7. 选中购物车中的商品
8. 又到了第4步的结算页面
9. 在结算页面生成订单
10. 付款
11. 确认收货
12. 评价
围绕购物流程最重要的两个表是OrderItem 和 Order表
关于OrderItem的业务行为
1. 立即购买 —— 新增 OrderItem
2. 加入购物车 —— 新增 OrderItem
3. 查看购物车 —— 显示未和Order关联的OrderItem
4. 选中购物车中的商品 —— 选中OrderItem
5. 结算页面 —— 显示选中的OrderItem
6. 生成订单 —— 新增Order
7 .付款 —— 修改Order状态
8. 我的订单 —— 显示Order
9. 确认收货 —— 修改Order状态
OrderItem表
Order表
当我们按下立即购买时,如果未登陆,那么点击立即购买之后会弹出一个登陆窗口,需要登陆后才能购买
前端检查是否登录js代码
$(".buyLink").click(function(){
var page = "forecheckLogin";
$.get(
page,
function(result){
if("success"==result){
var num = $(".productNumberSetting").val();
location.href= $(".buyLink").attr("href")+"&num="+num;
}
else{
$("#loginModal").modal('show');
}
}
);
return false;
});
forecheckLogin
@RequestMapping("forecheckLogin")
@ResponseBody
public String checkLogin( HttpSession session) {
User user =(User) session.getAttribute("user");
if(null!=user)
return "success";
return "fail";
}
如果我们登陆了会做如下操作
通过上个步骤访问的地址 /forebuyone 导致ForeController.buyone()方法被调用
1. 获取参数pid
2. 获取参数num
3. 根据pid获取产品对象p
4. 从session中获取用户对象user
接下来就是新增订单项OrderItem,如
在OrderItem表里插入一条数据,这条数据会表示:
1. pid =844 购买的商品id
2. oid = null, 这个订单项还没有生成对应的订单,即还在购物车中
3. uid= 3,用户的id是3
4. number=3, 购买了3件产品
新增订单项要考虑两个情况
a. 如果已经存在这个产品对应的OrderItem,并且还没有生成订单,即还在购物车中。 那么就应该在对应的OrderItem基础上,调整数量
a.1 基于用户对象user,查询没有生成订单的订单项集合
a.2 遍历这个集合
a.3 如果产品是一样的话,就进行数量追加
a.4 获取这个订单项的 id
b. 如果不存在对应的OrderItem,那么就新增一个订单项OrderItem
b.1 生成新的订单项
b.2 设置数量,用户和产品
b.3 插入到数据库
b.4 获取这个订单项的 id
最后, 基于这个订单项id客户端跳转到结算页面/forebuy
@RequestMapping("forebuyone")
public String buyone(int pid, int num, HttpSession session) {
Product p = productService.get(pid);
int oiid = 0;
User user =(User) session.getAttribute("user");
boolean found = false;
List ois = orderItemService.listByUser(user.getId());
for (OrderItem oi : ois) {
if(oi.getProduct().getId().intValue()==p.getId().intValue()){
oi.setNumber(oi.getNumber()+num);
orderItemService.update(oi);
found = true;
oiid = oi.getId();
break;
}
}
if(!found){
OrderItem oi = new OrderItem();
oi.setUid(user.getId());
oi.setNumber(num);
oi.setPid(pid);
orderItemService.add(oi);
oiid = oi.getId();
}
return "redirect:forebuy?oiid="+oiid;
}
}
结算页面是这样的
可以发现跳转到/forebuy其实就是进行了服务器端的跳转,使得ForeServlet.buy()被调用了
1. 通过字符串数组获取参数oiid
为什么这里要用字符串数组试图获取多个oiid,而不是int类型仅仅获取一个oiid?因为结算页面还需要显示在购物车中选中的多条OrderItem数据,所以为了兼容从购物车页面跳转过来的需求,要用字符串数组获取多个oiid
2. 准备一个泛型是OrderItem的集合ois
3. 根据前面步骤获取的oiids,从数据库中取出OrderItem对象,并放入ois集合中
4. 累计这些ois的价格总数,赋值在total上
5. 把订单项集合放在session的属性 "ois" 上
6. 把总价格放在 model的属性 "total" 上
7. 服务端跳转到buy.jsp
@RequestMapping("forebuy")
public String buy( Model model,String[] oiid,HttpSession session){
List ois = new ArrayList<>();
float total = 0;
for (String strid : oiid) {
int id = Integer.parseInt(strid);
OrderItem oi= orderItemService.get(id);
total +=oi.getProduct().getPromotePrice()*oi.getNumber();
ois.add(oi);
}
session.setAttribute("ois", ois);
model.addAttribute("total", total);
return "fore/buy";
}
}
在buy.jsp中,点击提交按钮后,就把表单里面的信息提交到/forecreateOrder
到了订单生成的开发,对于每个订单,都有自己的一个状态
1. 首先是创建订单,刚创建好之后,订单处于waitPay 待付款状态
2. 接着是付款,付款后,订单处于waitDelivery 待发货状态
3. 前两步都是前台用户操作导致的,接下来需要到后台做发货操作,发货后,订单处于waitConfirm 待确认收货状态
4. 接着又是前台用户进行确认收货操作,操作之后,订单处于waitReview 待评价状态
5. 最后进行评价,评价之后,订单处于finish 完成状态
以上状态都是一个接一个的,不能跳状态进行。
比较特殊的是,无论当前订单处于哪个状态,都可以进行删除操作。 像订单这样极其重要的业务数据,实际上是不允许真正从数据库中删除掉的,而是把状态标记为删除,以表示其被删掉了,所以在删除之后,订单处于 delete 已删除状态
这里,在写createOrder代码之前,我们还需在OrderService中新增方法add(Order c,List
看看它的实现方法
1.增加了事务管理,因为增加订单需要同时修改两个表,orderItem表和order表,如果在修改了orderItem表之后发生突发意外,没能修改到order表,会导致数据不一致,所以这里增加事务管理,当有异常时就回滚
2.传入ois是为了算出总价格,最后返回所有商品价格之和
@Override
@Transactional(propagation= Propagation.REQUIRED,rollbackForClassName="Exception")
public float add(Order o, List ois) {
float total = 0;
add(o);
if(false)
throw new RuntimeException();
for (OrderItem oi: ois) {
oi.setOid(o.getId());
orderItemService.update(oi);
total+=oi.getProduct().getPromotePrice()*oi.getNumber();
}
return total;
}
现在开始写createOrder代码
提交订单访问路径 /forecreateOrder, 导致ForeController.createOrder 方法被调用
1. 从session中获取user对象
2. 通过参数Order接受地址,邮编,收货人,用户留言等信息
3. 根据当前时间加上一个4位随机数生成订单号
4. 根据上述参数,创建订单对象
5. 把订单状态设置为等待支付
6. 从session中获取订单项集合 ( 在forebuy中,订单项集合被放到了session中 )
7. 把订单加入到数据库,并且遍历订单项集合,设置每个订单项的order,更新到数据库
8. 统计本次订单的总金额
9. 客户端跳转到确认支付页forealipay,并带上订单id和总金额
@RequestMapping("forecreateOrder")
public String createOrder( Model model,Order order,HttpSession session){
User user =(User) session.getAttribute("user");
String orderCode = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + RandomUtils.nextInt(10000);
order.setOrderCode(orderCode);
order.setCreateDate(new Date());
order.setUid(user.getId());
order.setStatus(OrderService.waitPay);
List ois= (List) session.getAttribute("ois");
float total =orderService.add(order,ois);
return "redirect:forealipay?oid="+order.getId() +"&total="+total;
}
支付页forealipay没有做什么事情,因为这里并没有真正使用到支付的功能~只是做了一个跳转。。。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
1. 在上一步确认访问按钮提交数据到/forepayed,导致ForeController.payed方法被调用
1.1 获取参数oid
1.2 根据oid获取到订单对象order
1.3 修改订单对象的状态和支付时间
1.4 更新这个订单对象到数据库
1.5 把这个订单对象放在model的属性"o"上
1.6 服务端跳转到payed.jsp
@RequestMapping("forepayed")
public String payed(int oid, float total, Model model) {
Order order = orderService.get(oid);
order.setStatus(OrderService.waitDelivery);
order.setPayDate(new Date());
orderService.update(order);
model.addAttribute("o", order);
return "fore/payed";
}
payed.jsp图
其中这里可以点击 查看交易详情,跳转到个人订单页面
/forebought导致ForeController.bought()方法被调用
1. 通过session获取用户user
2. 查询user所有的状态不是"delete" 的订单集合os
3. 为这些订单填充订单项
4. 把os放在model的属性"os"上
5. 服务端跳转到bought.jsp
@RequestMapping("forebought")
public String bought( Model model,HttpSession session) {
User user =(User) session.getAttribute("user");
List os= orderService.list(user.getId(),OrderService.delete);
orderItemService.fill(os);
model.addAttribute("os", os);
return "fore/bought";
}
}
bought.jsp