天天看点

练手项目1笔记 day17

目标

  • 掌握跨域请求CORS解决方案
  • 完成结算页收货人地址选择功能
  • 完成结算页支付方式选择
  • 完成结算页商品清单功能
  • 完成保存订单功能

1. 商品详情页跨域请求

1. 需求分析

从商品详情页点击“加入购物车”按钮,将当前商品加入购物车,并跳转到购物车页面

2. JS跨域请求

1. 什么是域

当两个应用协议、主机地址(或域名)、端口其中有一项不同,就认为他们的域不同。

这里的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面的不同域的框架中(iframe)的数据。

3. 跨域调用测试

从page-web的itemController.js,引入$http服务,修改addToCart()方法

//添加商品到购物车
$scope.addToCart=function(){
  // alert('SKUID:'+$scope.sku.id );
  $http.get("http://localhost:9107/cart/addGoodsToCartList.do?itemId="
            +$scope.sku.id+"&num="+$scope.num).success(
    function (response) {
      if(response.success){
        location.href="http://localhost:9107/cart";
      }else{
        alert(response.message);
      }
    }
  );
}
           

发现无法跨域,

XMLHttpRequest cannot load

4. 跨域解决方案CORS

CORS是一个w3c标准,全称为“跨域资源共享”(Cross-origin response sharing)。CORS需要浏览器和服务器同时支持。目前所有浏览器都支持,IE不能低于IE10

允许浏览器向跨源服

务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

请求过程如下:

练手项目1笔记 day17

Preflight Request:

练手项目1笔记 day17

然后服务器端给我们返回一个Preflight Response

练手项目1笔记 day17

下面开始让购物车工程接收跨域请求

  1. 修改cart-web的CartController.java的addGoodsToCartList方法。添加代码
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9105");
response.setHeader("Access-Control-Allow-Credentials", "true");
           

Access-Control-Allow-Origin是HTML5定义的一种解决资源跨域的策略

用过服务器端返回带有Access-Control-Allow-Origin表示的ResponseHeader,解决资源的跨域权限问题。

在response添加Access-Control-Allow-Origin,也可以设置为

*

表示该资源谁都可以用

  1. 修改item-web的itemController.js
//添加商品到购物车
$scope.addToCart=function(){
  // alert('SKUID:'+$scope.sku.id );
  $http.get("http://localhost:9107/cart/addGoodsToCartList.do?itemId="
            +$scope.sku.id+"&num="+$scope.num,{"withCredentials":true}).success(
    function (response) {
      if(response.success){
        location.href="http://localhost:9107/cart";
      }else{
        alert(response.message);
      }
    }
  );
}
           

调用测试,可以实现跨域。

CORS请求默认不发送cookie和HTTP认证信息。如果要把cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials,另一方面,需要在ajax请求中打开withCredentials属性。

5. SpringMVC跨域注解

4.2及以上版本可以使用注解跨域。需要跨域的方法上添加注解

@CrossOrigin

后面的allowCredentials="true"可以省略

2. 结算页- 收件人地址选择

1. 需求和数据库分析

1. 需求描述

在结算页实现收件人地址选择功能

2. 数据库表结构分析

Tb_address为地址表,user_id其实就是user表的username

2. 准备工作

1. 生成代码

  1. AddressService接口拷入user-interface
  2. AddressServiceImpl拷入user-service
  3. cart-web引入user-interface依赖,AddressController拷入cart-web

2. 拷贝页面资源

getOrderInfo.html拷贝到cart-web的webapp下

3. 实现地址列表

1. 后端代码

  1. 修改user-interface的AddressService.java
  1. 修改user-service的AddressServiceImpl.java
@Override
public List<TbAddress> findListByUserId(String userId) {
  TbAddressExample example = new TbAddressExample();
  Criteria criteria = example.createCriteria();
  criteria.andUserIdEqualTo(userId);
  return addressMapper.selectByExample(example);
}
           
  1. 修改cart-web的AddressController.java
@RequestMapping("/findListByLoginUser")
public List<TbAddress> findListByLoginUser(){
  // 获取登录用户
  String username = SecurityContextHolder.getContext().getAuthentication().getName();
  return addressService.findListByUserId(username);
}
           

2. 前端代码

  1. cart-web的cartService.js
// 获取地址列表
this.findAddressList = function () {
  return $http.get('address/findListByLoginUser.do');
}
           
  1. cart-web的cartController.js
// 获取当前用户的地址列表
$scope.findAddressList = function () {
  cartService.findAddressList().success(
    function (response) {
      $scope.addressList = response;
    }
  );
}
           
  1. 修改getOrderInfo.html
<ul class="addr-detail">
  <li class="addr-item">

    <div ng-repeat="address in addressList">
      <div class="con name "><a href="javascript:;" >{{address.contact}}<span title="点击取消选择">&nbsp;</a></div>
        <div class="con address">{{address.address}} <span>{{address.mobile}}</span>
          <span class="base" ng-if="address.isDefault=='1'">默认地址</span>
          <span class="edittext"><a data-toggle="modal" data-target=".edit" data-keyboard="false" >编辑</a>&nbsp;&nbsp;<a href="javascript:;">删除</a></span>
        </div>
        <div class="clearfix"></div>
      </div>
      </li>
</ul>
           
<script type="text/javascript" src="plugins/angularjs/angular.min.js">  </script>
<script type="text/javascript" src="js/base.js">  </script>
<script type="text/javascript" src="js/service/cartService.js">  </script>
<script type="text/javascript" src="js/controller/cartController.js">  </script>
           

4. 地址的选择

  1. 在orderInfoController.js增加代码
// 选择地址
$scope.selectAddress = function (address) {
  $scope.address = address;
}

// 判断某地址对象是不是当前选择的地址
$scope.idSelectedAddress = function (address) {
  if(address===$scope.address){
    return true;
  }else{
    return false;
  }
}
           
  1. 修改页面-点击选择
  1. 修改页面,显示选择的地址

5. 默认地址显示

修改orderInfoController.js

// 获取当前用户的地址列表
$scope.findAddressList = function () {
  cartService.findAddressList().success(
    function (response) {
      $scope.addressList = response;
      for(var i=0;i<$scope.addressList.length;i++){
        if($scope.addressList[i].isDefault==='1'){
          $scope.address = $scope.addressList[i];
          break;
        }
      }
    }
  );
}
           

6. 收件人地址增删改

TODO

3. 结算页-支付方式选择

1. 需求分析

实现支付方式的选择,品优购支持两种支付方式:微信支付和货到付款

2. 支付方式的选择

1. 前端控制层

cartController.js

$scope.order = {paymentType:'1'};//订单对象

// 选择支付方式
$scope.selectPayType = function (type) {
  $scope.order.paymentType = type;
}
           

2. 页面

getOrderInfo.html

<li class="selected" ng-click="selectPayType('1')">微信付款<span title="点击取消选择"></span></li>
<li  ng-click="selectPayType('2')">货到付款<span title="点击取消选择"></span></li>
           

4. 结算页-商品清单与金额显示

1. 需求分析

显示购物车的商品清单和合计数量、金额

2. 显示商品清单

  1. 页面getOrderInfo.html上初始化调用
  1. 循环显示商品清单
<ul class="send-detail"  ng-repeat="orderItem in cart.orderItemList">
  <li>
    <div class="sendGoods">

      <ul class="yui3-g">
        <li class="yui3-u-1-6">
          <span><img width="100px" height="100px" src="{{orderItem.picPath}}"/></span>
        </li>
        <li class="yui3-u-7-12">
          <div class="desc">{{orderItem.title}}</div>
          <div class="seven">7天无理由退货</div>
        </li>
        <li class="yui3-u-1-12">
          <div class="price">¥{{orderItem.price.toFixed(2)}}</div>
        </li>
        <li class="yui3-u-1-12">
          <div class="num">X{{orderItem.num}}</div>
        </li>
        <li class="yui3-u-1-12">
          <div class="exit">有货</div>
        </li>
      </ul>
    </div>
  </li>
</ul>
           

3. 显示合计金额

修改getOrderInfo.html

<div class="order-summary">
  <div class="static fr">
    <div class="list">
      <span><i class="number">{{totalValue.totalNum}}</i>件商品,总商品金额</span>
      <em class="allprice">¥{{totalValue.totalMoney.toFixed(2)}}</em>
    </div>
    <div class="list">
      <span>返现:</span>
      <em class="money">0.00</em>
    </div>
    <div class="list">
      <span>运费:</span>
      <em class="transport">0.00</em>
    </div>
  </div>
</div>
<div class="clearfix trade">
  <div class="fc-price">应付金额: <span class="price">¥{{totalValue.totalMoney.toFixed(2)}}</span></div>
           

5. 保存订单

1. 需求分析

1. 需求描述

点击订单结算页的提交订单,将购物车保存到订单表和订单明细表,并将购物车数据清除。

2. 数据库表结构分析

Tb_order为订单主表

2. 准备工作

1. 搭建框架

  1. 创建order-interface 引入依赖pojo
  2. 创建order-service,参见content-service,添加各种配置,dubbox端口20888,tomcat端口9008
  3. cart-web引入依赖order-interface

2. 生成代码

根据之前生成的user相关的代码,拷贝到工程order-interface、order-service和cart-web

3. 分布式ID生成器

为什么不让order表的主键自增?

对于互联网应用,可能某个表会占用很大的存储空间,比如订单表,每天都会生成很多订单,如果服务器的硬盘满了怎么办?

采用数据库分片,把一个数据库进行拆分,通过数据库中间件进行连接

如果采用数据自增,可能会产生重复的ID

分布式ID生成解决方案:
  1. UUID(不用的理由:1. 太长;2. 没办法排序)
  2. Redis(产生自增的序号 主键的生成需要访问redis,对redis有依赖)
  3. Oracle 数据库对象-序列(与表无关) 只有数据库用Oracle才行
  4. 程序自己写算法(不重复)

为什么无需的UUID导致入库性能变差呢?

涉及到B+树索引的分类,关系型数据库的索引大多是B+树。

采用开源的Twitter的snowflake算法
练手项目1笔记 day17
  1. 第一位:占用1bit,值为0,没有实际作用
  2. 时间戳:占用41bit,精确到毫秒,总共可容纳约69年时间
  3. 工作机器id:占用10bit,高位5bit是数据中心id,低位5bit为工作节点id,最多容纳1024个节点
  4. 序列号:占用12bit,该值在同一毫秒同一节点从0开始不断累加,最多累加到4095。

雪花算法在同一毫秒最多可生成1024*4096=42万左右个id

3. 后端代码

1. 服务实现层

order-service的OrderServiceImpl.java

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private IdWorker idWorker;

@Autowired
private TbOrderItemMapper orderItemMapper;

@Override
public void add(TbOrder order) {
  // 1. 从redis中提取购物车列表
  List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());
  // 2. 循环购物车列表添加订单

  for (Cart cart : cartList) {
    TbOrder tbOrder = new TbOrder();
    long orderId = idWorker.nextId();
    tbOrder.setOrderId(orderId);
    tbOrder.setPaymentType(order.getPaymentType());// 付款方式
    tbOrder.setStatus("1");//未付款
    tbOrder.setCreateTime(new Date());
    tbOrder.setUpdateTime(new Date());
    tbOrder.setUserId(order.getUserId());
    tbOrder.setReceiverAreaName(order.getReceiverAreaName());//收货人地址
    tbOrder.setReceiverMobile(order.getReceiverMobile());//收货人电话
    tbOrder.setReceiver(order.getReceiver());// 收货人
    tbOrder.setSourceType(order.getSourceType());//订单来源
    tbOrder.setSellerId(order.getSellerId());//商家id

    double money = 0;//合计数
    // 循环购物车中每条明细记录
    for (TbOrderItem orderItem : cart.getOrderItemList()) {
      orderItem.setId(idWorker.nextId());
      orderItem.setOrderId(orderId);//订单编号
      // 注意:商家id来自购物车
      orderItem.setSellerId(cart.getSellerId());//商家id
      orderItemMapper.insert(orderItem);
      money += orderItem.getTotalFee().doubleValue();
    }
    tbOrder.setPayment(new BigDecimal(money));//合计
    orderMapper.insert(tbOrder);
  }
  // 3. 清除redis中的购物车
  redisTemplate.boundHashOps("cartList").delete(order.getUserId());
}
           

记得在类上添加事务注解

在spring的配置applicationContext-service中添加idWorker

<bean id="idWorker" class="util.IdWorker">
  <!--进程id-->
  <constructor-arg index="0" value="0"/>
  <!--数据中心id-->
  <constructor-arg index="1" value="0"/>
</bean>
           

3. 控制层

修改cart-web的OrderController.java

修改add方法

@RequestMapping("/add")
public Result add(@RequestBody TbOrder order){

  // 获取当前登录人账号
  String username = SecurityContextHolder.getContext().getAuthentication().getName();
  order.setUserId(username);
  order.setSourceType("2");//订单来源 PC
  try {
    orderService.add(order);
    return new Result(true, "增加成功");
  } catch (Exception e) {
    e.printStackTrace();
    return new Result(false, "增加失败");
  }
}
           

4. 前端代码

1. 服务层

修改cart-web的cartService.js

// 提交订单
this,submitOrder = function (order) {
  return $http.post('order/add.do',order);
}
           

2. 控制层

修改cartController.js

// 保存订单
$scope.submitOrder = function () {

  $scope.order.receiverAreaName=$scope.address.address;
  $scope.order.receiverMobile = $scope.address.mobile;
  $scope.order.receiver = $scope.address.contact;//联系人

  cartService.submitOrder($scope.order).success(
    function (response) {
      alert(response.message);
    }
  );
}
           

3. 测试

最终需要跑cart-web,依赖user-service cart-service order-service

中间小bug,开始时不出现收货地址和收货手机号,发现controller写错了,receiverAreaName和receiverMobile

后来发现数据库没有存sellerid,发现sellerid应该是来自cart,写成了order

4. 完善

// 保存订单
$scope.submitOrder = function () {

  $scope.order.receiverAreaName=$scope.address.address;
  $scope.order.receiverMobile = $scope.address.mobile;
  $scope.order.receiver = $scope.address.contact;//联系人

  cartService.submitOrder($scope.order).success(
    function (response) {
      if(response.success){
        if($scope.order.paymentType==='1'){//微信支付,跳转到支付页面
          location.href="pay.html";
        }else{
          location.href="paysuccess.html";
        }
      }else{
        alert(response.message);//也可以跳转提示页面
      }

    }
  );
}