SSM商城项目(十三)
1. 学习计划
1、订单系统
2、提交订单
3、MyCAT
2. 订单系统
2.1. 功能分析
1、在购物车页面点击“去结算”按钮跳转到订单确认页面。
a) 展示商品列表
b) 配送地址列表
c) 选择支付方式
2、展示订单确认页面之前,应该确认用户身份。
a) 使用拦截器实现。
b) Cookie中取token
c) 取不到token跳转到登录页面
d) 取到token,根据token查询用户信息。
e) 如果没有用户信息,登录过期跳转到登录页面
f) 取到用户信息,放行。
3、提交订单
a) 生成订单
b) 展示订单提交成功页面。
订单系统系统:订单确认页面、订单提交成功页面。
订单服务系统
2.2. 工程搭建
创建一个订单服务系统:
e3-order
|--e3-order-interface(jar)
|--e3-order-Service(war)
e3-order-web(war)
导入静态页面
2.3. 展示订单确认页面
2.3.1. 功能分析
1、在购物车页面点击“去结算”按钮跳转到订单确认页面。
2、请求的url:
/order/order-cart
3、参数:没有参数。
4、购物车商品数据从cookie中取出来的。可以在订单系统中取到cookie中的购物车数据。
5、配送地址列表,需要用户登录。需要根据用户id查询收货地址列表。静态数据。
6、支付方式。静态数据。
7、返回值:逻辑视图String,展示订单确认页面。
2.3.2. Dao层、Service层(没有)
引入其他工程接口即可。
2.3.3. 表现层
引入服务
<dubbo:reference interface="cn.e3mall.car.service.CartService" id="cartService" />
@Controller public class OrderController { @Autowired private CartService cartService; @RequestMapping("/order/order-cart") public String showOrderCart(HttpServletRequest request){ //取用户id TbUser user= (TbUser) request.getAttribute("user"); //根据用户id取收货地址列表 //使用静态数据。。。 //取支付方式列表 //静态数据 //根据用户id取购物车列表 List<TbItem> cartList = cartService.getCartList(user.getId()); //把购物车列表传递给jsp request.setAttribute("cartList", cartList); return "order-cart"; } }
2.4. 用户身份认证
在展示订单确认页面之前,需要对用户身份进行认证,要求用户必须登录。
2.4.1. 功能分析
1、使用springmvc的拦截器实现。需要实现一个接口HandlerInterceptor接口。
2、业务逻辑
a) 从cookie中取token。
b) 没有token,需要跳转到登录页面。
c) 有token。调用sso系统的服务,根据token查询用户信息。
d) 如果查不到用户信息。用户登录已经过期。需要跳转到登录页面。
e) 查询到用户信息。放行。
3、在springmvc.xml中配置拦截器。
2.4.2. 拦截器实现
springmvc.xml
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.e3mall.order.interceptor.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
LoginInterceptor.java
public class LoginInterceptor implements HandlerInterceptor{ @Autowired private TokenService tokenService; @Autowired private CartService cartService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //从cookie中取token String token=CookieUtils.getCookieValue(request, "token"); //判断token是否存在 if(StringUtils.isBlank(token)){ //如果token不存在,跳转到登录页面 response.sendRedirect("http://localhost:8089/page/login?redirect="+request.getRequestURL()); //拦截 return false; } //如果token存在,根据token取用户信息 E3Result e3Result = tokenService.getUserByToken(token); //如果取不到,表示已经过期,需要重新登录 if(e3Result.getStatus()!=200){ //如果token不存在,跳转到登录页面 response.sendRedirect("http://localhost:8089/page/login?redirect="+request.getRequestURL()); //拦截 return false; } //如果取到,已经登录,把用户信息写入request TbUser user = (TbUser) e3Result.getData(); request.setAttribute("user", user); //判断cookie中是否有购物车数据,如果有就合并 String string = CookieUtils.getCookieValue(request, "car",true); if(StringUtils.isNoneBlank(string)){ cartService.mergeCart(user.getId(), JsonUtils.jsonToList(string, TbItem.class)); } //放行 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } }
2.4.3. 实现sso系统的回调
3. 提交订单
3.1. 功能分析
1、在订单确认页面点击“提交订单”按钮生成订单。
2、请求的url:/order/create
3、参数:提交的是表单的数据。保存的数据:订单、订单明细、配送地址。
a) 向tb_order中插入记录。
- 订单号需要手动生成。
要求订单号不能重复。
订单号可读性号。
可以使用redis的incr命令生成订单号。订单号需要一个初始值。
- Payment:表单数据
- payment_type:表单数据
- user_id:用户信息
- buyer_nick:用户名
- 其他字段null
b) 向tb_order_item订单明细表插入数据。
- Id:使用incr生成
- order_id:生成的订单号
- 其他的都是表单中的数据。
c) tb_order_shipping,订单配送信息
- order_id:生成的订单号
- 其他字段都是表单中的数据。
d) 使用pojo接收表单的数据。
可以扩展TbOrder,在子类中添加两个属性一个是商品明细列表,一个是配送信息。
把pojo放到e3-order-interface工程中。
public class OrderInfo extends TbOrder implements Serializable{ private List<TbOrderItem> orderItems; private TbOrderShipping orderShipping; public List<TbOrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<TbOrderItem> orderItems) { this.orderItems = orderItems; } public TbOrderShipping getOrderShipping() { return orderShipping; } public void setOrderShipping(TbOrderShipping orderShipping) { this.orderShipping = orderShipping; } }
业务逻辑:
1、接收表单的数据
2、生成订单id
3、向订单表插入数据。
4、向订单明细表插入数据
5、向订单物流表插入数据。
6、返回e3Result。
3.2. Dao层
可以使用逆向工程。
3.3. Service层
参数:OrderInfo
返回值:e3Result
@Service public class OrderServiceImpl implements OrderService{ @Autowired private TbOrderMapper orderMapper; @Autowired private TbOrderItemMapper orderItemMapper; @Autowired private TbOrderShippingMapper orderShippingMapper; @Autowired private JedisClient jedisClient; @Override public E3Result createOrder(OrderInfo orderInfo) { // 生成订单号 if(!jedisClient.exists("ORDER_ID_GEN")){ jedisClient.set("ORDER_ID_GEN","11500"); } String orderId = jedisClient.incr("ORDER_ID_GEN").toString(); //补全orderInfo属性 orderInfo.setOrderId(orderId); //1、未付款,2、已付款,3、未发货,4、已发货,5、交易成功,6、交易关闭 orderInfo.setStatus(1); Date date = new Date(); orderInfo.setCreateTime(date); orderInfo.setUpdateTime(date); //插入订单表 orderMapper.insert(orderInfo); //向订单明细表插入数据 List<TbOrderItem> orderItems = orderInfo.getOrderItems(); for (TbOrderItem tbOrderItem : orderItems) { //生成明细id Long orderItemId = jedisClient.incr("ORDER_ITEM_ID_GEN"); tbOrderItem.setId(orderItemId.toString()); tbOrderItem.setOrderId(orderId); //插入数据 orderItemMapper.insert(tbOrderItem); } //向订单物流表插入数据 TbOrderShipping orderShipping = orderInfo.getOrderShipping(); orderShipping.setOrderId(orderId); orderShipping.setCreated(date); orderShipping.setUpdated(date); orderShippingMapper.insert(orderShipping); //返回 return E3Result.ok(orderId); } }
发布服务
<dubbo:service interface="cn.e3mall.order.service.OrderService" ref="orderServiceImpl" timeout="600000"/>
3.4. Controller
引用服务
<dubbo:reference interface="cn.e3mall.order.service.OrderService" id="orderService" />
请求的url:/order/create
参数:使用OrderInfo接收
返回值:逻辑视图。
@RequestMapping(value="/order/create", method=RequestMethod.POST) public String createOrder(OrderInfo orderInfo, HttpServletRequest request) { // 取用户信息 TbUser user = (TbUser) request.getAttribute("user"); // 把用户信息添加到orderInfo中 orderInfo.setUserId(user.getId()); orderInfo.setBuyerNick(user.getUsername()); // 调用Service创建订单。 E3Result result = orderService.createOrder(orderInfo); //如果订单生成成功,需要删除购物车 if(result.getStatus()==200){ cartService.clearCartItem(user.getId()); } //把订单号传递给页面 request.setAttribute("orderId", result.getData()); request.setAttribute("payment", orderInfo.getPayment()); // 返回逻辑视图展示成功页面 return "success"; }
调用CartServiceImpl.java
//清空购物车 @Override public E3Result clearCartItem(long userId) { jedisClient.del("Cart:" + userId); return E3Result.ok(); }
4. MyCAT介绍
4.1. 什么是MyCAT?
简单的说,MyCAT就是:
一个彻底开源的,面向企业应用开发的“大数据库集群”
支持事务、ACID、可以替代Mysql的加强版数据库
一个可以视为“Mysql”集群的企业级数据库,用来替代昂贵的Oracle集群
一个融合内存缓存技术、Nosql技术、HDFS大数据的新型SQL Server
结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
一个新颖的数据库中间件产品
MyCAT的目标是:低成本的将现有的单机数据库和应用平滑迁移到“云”端,解决数据存储和业务规模迅速增长情况下的数据瓶颈问题。
4.2. MyCAT的关键特性
支持 SQL 92标准
支持Mysql集群,可以作为Proxy使用
支持JDBC连接ORACLE、DB2、SQL Server,将其模拟为MySQL Server使用
支持galera for mysql集群,percona-cluster或者mariadb cluster,提供高可用性数据分片集群
自动故障切换,高可用性
支持读写分离,支持Mysql双主多从,以及一主多从的模式
支持全局表,数据自动分片到多个节点,用于高效表关联查询
支持独有的基于E-R 关系的分片策略,实现了高效的表关联查询
多平台支持,部署和实施简单
4.3. MyCAT架构
如图所示:MyCAT使用Mysql的通讯协议模拟成了一个Mysql服务器,并建立了完整的Schema(数据库)、Table (数据表)、User(用户)的逻辑模型,并将这套逻辑模型映射到后端的存储节点DataNode(MySQL Instance)上的真实物理库中,这样一来,所有能使用Mysql的客户端以及编程语言都能将MyCAT当成是Mysql Server来使用,不必开发新的客户端协议。
5. Mycat解决的问题
l 性能问题
l 数据库连接过多
l E-R分片难处理
l 可用性问题
l 成本和伸缩性问题
5.1. Mycat对多数据库的支持
6. 分片策略
MyCAT支持水平分片与垂直分片:
水平分片:一个表格的数据分割到多个节点上,按照行分隔。
垂直分片:一个数据库中多个表格A,B,C,A存储到节点1上,B存储到节点2上,C存储到节点3上。
MyCAT通过定义表的分片规则来实现分片,每个表格可以捆绑一个分片规则,每个分片规则指定一个分片字段并绑定一个函数,来实现动态分片算法。
1、Schema:逻辑库,与MySQL中的Database(数据库)对应,一个逻辑库中定义了所包括的Table。
2、Table:表,即物理数据库中存储的某一张表,与传统数据库不同,这里的表格需要声明其所存储的逻辑数据节点DataNode。在此可以指定表的分片规则。
3、DataNode:MyCAT的逻辑数据节点,是存放table的具体物理节点,也称之为分片节点,通过DataSource来关联到后端某个具体数据库上
4、DataSource:定义某个物理库的访问地址,用于捆绑到Datanode上