1. 掌握跨域请求CORS解决方案
2. 完成结算页收货人地址选择功能
3. 完成结算页支付方式选择
4. 完成结算页商品清单功能
5. 完成保存订单功能
1. 需求分析:
* 从商品详细页点击“加入购物车”按钮,将当前商品加入购物车,并跳转到购物车页面。
2. JS跨域请求:
* 这里说的 js 跨域是指通过 js 在不同的域之间进行数据传输或通信,比如用 ajax 向一个不同的域请求数据,或者通过 js 获取页面中不同域的框架中(iframe)的数据。只要协议、域名、端口有任何一个不同,都被当作是不同的域。
3. 跨域:
1. 定义:
* 当两个应用协议、主机地址(或域名)、端口其中有一项不同,认为他们的域不同的
2. 举例:
1. 域名不同:
* http://cas.baidu.com
* https://cas.sina.com
2. 应用协议不同
* http://cas.baidu.com
* htpps://cas.baidu.com
4. 主机地址不同:
* http://192.168.0.130/aaa
* http://192.168.0.135/aaa
5. 端口不同:
* http://192.168.0.130/8080
* http://192.168.0.80
6. 注意:
* 同域
* http://192.168.0.130/aaa
* http://192.168.0.130.80
4. js跨域
1. 两个不同的域:a b ,在a 的应用的js脚本调用过了b的后端地址 [可以使用ajax请求的方式调用]
2. 跨域代码演示:
//添加购物车 跨域请求
$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.html"
}else{
alert(response.message);
}
}
)
}
3. 默认情况下,js跨域是不能完成的[考虑安全问题],运行后前端会报"Access-COntrol-Allow-Origin"异常,需要进行一些处理后才能进行跨域; [Access-Control-Allow-Origin 是 HTML5 中定义的一种解决资源跨域的策略。他是通过服务器端返回带有 Access-Control-Allow-Origin 标识的 Response header,用来解决资源的跨域权限问题。]
5. 跨域解决方案
1. JSONP :比较古老的,民间解决方案,会对服务端客户端造成侵入式修改,不建议使用
2. CORS : 官方解决方案:既简单又流行的,推荐使用
6. 跨域解决方案CORS:
1. 介绍:
* CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
* 它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
2. 流程:
1. 浏览器先发送预请求信息,然后服务器回复,然后浏览器发送Real Request,服务器再发送Real Response
2. 修改:
1. 在购物车结算按钮的js页面的addGoodsToCartList方法添加这两个代码以实现发送跨域且携带cookie值的请求:
//添加请求跨域头信息
//这里的项目如果安全性要求如果不高的话value的值可以为* 表示任何ip地址过来的都能发送跨域请求
response.setHeader("Access-Control-Allow-Origin","http://localhost:9105"); //可以访问的域
//如果需要操作cookie需要添加下面的内容:Allow-Credentials:允许携带凭证,凭证就是cookie
//这里操作cookie的话,上面的value不能为* ,如果它为* ,cookie就不能与域绑定
response.setHeader("Access-Control-Allow-Credentials","true");
2. 客户端代码修改:
//添加购物车 跨域请求
$scope.addToCart=function(){
//alert('SKUID:'+$scope.sku.id);
//'withCredentials':true :是否允许携带凭证,允许
$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.html"
}else{
alert(response.message);
}
}
)
}
* 关键代码:{'withCredentials':true}
3. SpringMVC:跨域注解:
1. 解释:springMVC 的版本在 4.2 或以上版本,可以使用注解实现跨域, 我们只需要在需要跨域的方法上添加注解@CrossOrigin 即可
2. 代码:
@CrossOrigin(origins="http://localhost:9105",allowCredentials="true")
3. allowCredentials="true" 可以省略
4. 总结:
CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段。另一方面,开发者必须在 AJAX 请求中打开 withCredentials 属性。否则,即使服务器同意发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会处理
1. 为什么不让我们的order[订单]表自增?
* 对于互联网应用,可能某个表会占用很大的储存空间,让你的服务器硬盘满了怎么办?
- 数据库分片,把一个数据库进行拆分,通过数据库中间件进行连接;
* 因为采用数据库分片,那么数据是分布式的,每个都如果设置为自增,有可能会产生重复的ID值;
2. 分布式ID生成解决方案:
1. UUID
* 不用它的理由:1. 太长 2. 没办法排序
2. Redis:
* 不用它的理由:产生自增的序号,主键的生成需要访问Redis,对reids有依赖
3. Oracle
* 不用它的理由:只有数据库用oracle才能用。 [数据库对象-序列(与表无关)]
4. 程序自己写算法(不重复)
1. order-interface:
1. 分析:
* 首先提交订单肯定会涉及到收货地址,而其他的地方也会涉及这一方面,所以我们应该提取出来单独建一个项目,提高程序的可复用性;所以有最基本的增删查改等功能; 【有一些功能目前可能用不到,我们实现是为了以后扩展其他功能】
2. 方法:
1. 返回分页列表
2. 增加
3. 修改
4. 根据id获取实体
5. 批量删除
6. 分页
2. order-service:
1. 分析:
* 实现接口的所有方法,但需要注意的是,在添加订单的时候应该给每个订单给上id值,我们可以通过工具类:IdWorker [推特雪花算法]生成订单号循环遍历订单给订单添加id
2. 关键方法:add
3. 跨域请求:
1. 分析:参照前面已经说过的配置,付款流程是先是到商品详情页然后选择具体的订单,确定好数量之后,添加进购物车,在购物车确定好之后选择微信付款或者货到付款点击提交订单;在提交订单这里会用到跨域请求,参照前面代码在controller.js中的进行配置即可;
2. 关键方法:$scope.addToCart
4. Controller:
1. OrderController.java:
* 实现增删查改,在增加的时候可以设置订单来源[通过获取当前登录人账号获取订单来源和订单购买者角色]
2. AddressController.java:
* 实现接口所有方法
* 方法列表:
1. 返回全部列表
2. 返回分页全部列表
3. 增加
4. 修改
5. 获取实体
6. 批量删除
7. 查询+分页
5. 总结:
* 架构:在整体架构上讲,地址的查询具有可重用性,我们将其重新提取出来新建一个项目,而在用户选择商品进行购物的时候需要显示收货地址,这个消费者我们可以将其放在cart购物车module里,因为这里的查询收货地址是添加商品后结算时需要显示的,不是通用的;
* 前端页面:在前端我们使用angularJS进行绑定和遍历操作,我们设置了一个address对象,将收货地址,手机号,姓名等信息封装到里面进行绑定显示
* 前端控制层1 :在数据库的设计的时候应该有一个"isDefult"列,进行判断哪个收货地址是否是默认收货地址,在控制层可以进行遍历所有收货地址进行遍历操作,如果有一个收货地址的isDefult与之对应则初始化时自动选择
* 前端控制层2 :应该创建两个方法,一个是选择地址,一个是判断某地址是不是当前选择的地址,选择地址是每当用户点击地址的时候,应该传入一个地址,然后在“判断某地址是不是当前选择的地址”方法中进行遍历判断是不是选择的地址,如果是选择的则进行标记,这个是当用户不选择默认收货地址而是选择其他收货地址的时候进行的一个点击显示动态效果的解决办法;
* 前端控制层3 :应该创建两个方法,一个是选择支付类型,一个是保存订单,如果用户点击了微信支付或者货到付款,应该给它一个状态值,在保存订单中应该对状态值进行判断,如果是微信支付则跳转到微信支付页面,如果是货到付款便跳转到货到付款单的页面;
* 示例图:
![](https://i.imgur.com/nDd9E8U.png)
1. 雪花算法工具类:[可加入bean方便注入]
package util;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* 名称:IdWorker.java
* 描述:分布式自增长ID
*
* Twitter的 Snowflake JAVA实现方案
*
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
*
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
*
* 获取 maxWorkerId
*
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
*
* 数据标识id部分
*
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
/*
//测试
public static void main(String[] args) {
IdWorker idWorker=new IdWorker(0,0);
for (int i=0; i < 100; i++) {
long nextId=idWorker.nextId();
System.out.println(nextId);
}
}
*/
}
2. 跨域调用:
1. 添加订单方法后端:
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9105");
response.setHeader("Access-Control-Allow-Credentials", "true");
* 也可以在需要跨域的方法上添加此注解,allowCredentials的可以省略:
@CrossOrigin(origins="http://localhost:9105",allowCredentials="true")
2. 添加订单方法前端:
//添加商品到购物车
$scope.addToCart=function(){
$http.get('http://localhost:9107/cart/addGoodsToCartList.do?itemId='
+ $scope.sku.id +'&num='+$scope.num,{'withCredentials':true}).success(
function(response){
.......
}
);
}
3. 展示默认地址关键代码:html前端页面
4. orderInfoController.js增加代码[修改收货地址动态显示]
$scope.selectAddress=function(address){
$scope.address=address;
}
//判断是否是当前选中的地址
$scope.isSelectedAddress=function(address){
if(address==$scope.address){
return true;
}else{
return false;
}
}
5. 页面点击选择关键代码:
6. 控制层controller.js默认地址的相关代码:
//查询当前登录人的地址列表
$scope.findAddressList=function(){
addressService.findListByLoginUser().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;
}}});}
7. 支付页判断:
1. 后端
$scope.order={paymentType:'1'}; //初始化状态值
2. 前端绑定:
微信付款
货到付款
8. 支付跳转
//保存订单
$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); //也可以跳转到提示页面
}});}