本文主要是介绍分布式电商系统核心模块实现说明,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
分布式电商系统核心模块
- es全文检索
- redis商品详情页
- cookie购物车
- 基于jwt的SSO单点登录
- 社交登录【微博】
- 支付-订单-库存
es全文检索
- 定义es的索引结构
就是将数据库里的 skuId,skuName,skuDesc,catalog3Id,price,skuDefaultImg,productId,skuAttrValueList【颜色,尺寸】数据查出来之后封装成一张表/对象PmsSearchSkuInfo,放到es的索引/库里面存着
PUT gmall0105
{"mappings": {"PmsSkuInfo":{"properties": {"id":{"type": "keyword","index": true},"skuName":{"type": "text","analyzer": "ik_max_word"},"skuDesc":{"type": "text", "analyzer": "ik_smart"},"catalog3Id":{"type": "keyword"},"price":{"type": "double"},"skuDefaultImg":{"type": "keyword","index": false},"hotScore":{"type": "double"},"productId":{"type": "keyword"},"skuAttrValueList":{"properties": {"attrId":{"type":"keyword"},"valueId":{"type":"keyword"}}}} }}
}
- 使用es的java客户端代码批量导入数据到es中
for (PmsSearchSkuInfo pmsSearchSkuInfo : pmsSearchSkuInfos) {Index put = new Index.Builder(pmsSearchSkuInfo).index("gmall0105").type("PmsSkuInfo").id(pmsSearchSkuInfo.getId()+"").build();jestClient.execute(put);
}获取页面传来的查询参数,并构造所需的dsl语句字符串
boolQueryBuilder.filter(termQueryBuilder); //精准匹配后过滤
boolQueryBuilder.must(matchQueryBuilder); //模糊匹配
searchSourceBuilder.sort("id", SortOrder.DESC);//排序 searchSourceBuilder.aggregation(); //聚集
searchSourceBuilder.query(boolQueryBuilder); //查询结构,构造完成
Search search = new Search.Builder(searchSourceBuilder).addIndex("gmall0105").addType("PmsSkuInfo").build();
execute = jestClient.execute(search); 发起查询
List<SearchResult.Hit> hits=execute.getHits()得到查询结果
for(){pmsSearchSkuInfos.add(hit.source);} 转成java对象for(pmsSearchSkuInfos.skuAttrValueList){ 得到对应的 attrList } 根据商品的sku销售属性,得到商品所对应的平台属性,因为需要实现面包屑的效果【需要在整个的平台属性中减去查询出来的商品的平台属性】
将从es中查询到skuInfoList返回给页面
最后测试网址:
http://localhost/listes?catalog3Id=61&valueId=39&valueId=43&valueId=48&keyword=硅谷
- 各查询参数说明【平台属性,关键字,三级分类ID】
catalog3Id=61------手机/手机通讯/手机
valueId=(39,43,48)----屏幕尺寸:4寸以下,内存容量:16G,尺寸:z1
keyword=硅谷 -----搜索框里输入硅谷进行全文检索
redis商品详情页
在高并发下缓存击穿,穿透,雪崩问题的解决
cookie购物车
//存---使用redis的hash数据结构存储购物车数据jedis.hmset("user:"+memberId+":cart",map);map.put(OmsCartItem.getProductSkuId(), JSON.toJSONString(OmsCartItem));
//取---List< String> hvals = jedis.hvals("user:" + userId + ":cart");OmsCartItem omsCartItem = JSON.parseObject(hval, OmsCartItem.class);
注意点:
1、redis中取出来要进行反序列化
2、redis的hash结构是无序的,要进行排序(可以用时间戳或者主键id,倒序排序)
3、如果redis中没有要从数据库中查询,要连带把最新的价格也取出来,默认要显示最新价格而不是当时放入购物车的价格,如果考虑用户体验可以把两者的差价提示给用户。
4、加载入缓存时一定要设定失效时间,保证和用户信息的失效时间一致即可。
5、由于加入购物车时,用户可能存在登录和未登录两种情况,登录前在cookie中保存了一部分购物车信息,如果用户登录了,那么对应的要把cookie中的购物车合并到数据库中,并且刷新缓存。【登陆时:合并购物车,刷新缓存】
6、从对应域名下找cookie
测试网址: http://localhost:9999/addToCart?skuId=25&quantity=2 添加购物车
http://localhost:9999/cartList 查询购物车
1 购物车在不登陆的情况下,也可以使用
需要引入对浏览器cookie的操作2 购物车在登录的情况下,需要使用mysql和redis来存储数据
Redis作为购物车的缓存3 在缓存情况下,或者用户已经添加购物车后,允许购物车中的数据和原始商品数据的不一致性4 购物车同步问题
什么时候同步(结算、登录)
同步购物车后,是否需要删除cookie数据5 用户在不同的客户端同时登录
如何处理购物车数据
基于jwt的SSO单点登录
服务应该是并行的,而非串行的【购物车挂了,后续流程都挂】
- 为何要使用sso单点登录
因为分布式下session无法跨顶级域名共享,且安全性不高,保存在cookie里面的session的唯一标识jsseionId容易被截取。
//京东的首页,商品页,商品详情页,全文检索页,购物车等每个模块都是一个独立的系统,有自己的域名
https://www.jd.com/ 京东首页https://list.jd.com/list.html?cat=9987,653,655 三级分类下的商品页【9987一级分类,653二级分类,655三级分类】https://item.jd.com/49405721859.html 商品详情页【sku=49405721859】https://search.jd.com/search?keyword=电脑&cid3=672 全文检索页【关键词电脑,平台属性cid3为672------》电脑/笔记本】https://cart.jd.com/cart.action?r=0.35325141737280386 购物车【当前是未登录状态,使用cookie保存了购物车的信息】所以我在实现的时候,这几个模块的controller不应该放在同一个web模块下。
社交登录【微博】
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
1、【微博开放平台创建应用(填写用户授权后的网页授权回调地址)】点击微博登录—》跳转到
https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
2、用户同意授权【输入用户的微博号和密码,对接微博登录成功】,页面跳转至 xxx/?code=CODE
http://www.gulishop.com/success?code=fef987b3f9ad1169955840b467bfc661
3、使用返回的code,换取access token
4、使用access token调用开发API获取用户信息(去访问任何微博开放平台的API)
1)、code用后即毁
2)、access_token在几天内是一样的
社交登录开发完毕
String show_user_url = "https://api.weibo.com/2/users/show.json?access_token="+access_token+"&uid="+uid;String user_json = HttpclientUtil.doGet(show_user_url); //对接微博,获取用户信息Map<String,Object> user_map = JSON.parseObject(user_json,Map.class);// 将用户信息保存数据库,用户类型设置为微博用户UmsMember umsMember = new UmsMember();umsMember.setSourceType("2");umsMember.setAccessCode(code);umsMember.setAccessToken(access_token);umsMember.setNickname((String)user_map.get("screen_name"));
支付-订单-库存
从购物车用户点击结算,生成订单,订单状态是未付款,用户确认订单后点击付款,此时锁定库存,再发消息给订单,更新订单状态为库存已锁定,然后发消息给支付,开始支付,支付成功,就更新订单状态为已支付,假如在支付的时候,由于账户不足,导致支付失败,就进行消息回滚,其实更新订单,锁定库存事务也会进行回滚
//提交订单
@RequestMapping("alipay/submit")@LoginRequired(loginSuccess = true)@ResponseBodypublic String alipay(String outTradeNo, BigDecimal totalAmount, 数量){alipayClient.pageExecute(alipayRequest【订单号,购买数量】).getBody(); //调用SDK生成表单// // 向消息中间件发送一个检查支付状态(支付服务消费)的延迟消息队列----起点paymentService.sendDelayPaymentResultCheckQueue(outTradeNo,5);//发消息到一个队列里面return form; //跳转到阿里的扫码支付页面
}Queue payhment_success_queue = session.createQueue("PAYMENT_CHECK_QUEUE");MessageProducer producer = session.createProducer(payhment_success_queue);
// 为消息加入延迟时间mapMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,1000*60); producer.send(mapMessage);@JmsListener(destination = "PAYMENT_CHECK_QUEUE",containerFactory = "jmsQueueListener")public void consumePaymentCheckResult(MapMessage mapMessage) throws JMSException {
// 调用paymentService的支付宝检查接口
paymentService.updatePayment(paymentInfo);====Queue payhment_success_queue = session.createQueue("PAYHMENT_SUCCESS_QUEUE");MessageProducer producer = session.createProducer(payhment_success_queue);
producer.send(mapMessage);@JmsListener(destination = "PAYHMENT_SUCCESS_QUEUE",containerFactory = "jmsQueueListener")public void consumePaymentResult(MapMessage mapMessage) throws JMSException { orderService.updateOrder(omsOrder);【已支付】=====》Queue payhment_success_queue = session.createQueue("ORDER_PAY_QUEUE"); @JmsListener(destination = "ORDER_PAY_QUEUE", containerFactory = "jmsQueueListener")public void receiveOrder(TextMessage textMessage) throws JMSException { // 库存削减【锁库存】gwareService.lockStock(orderTask);//库存正常就锁库存,异常就 //库存超卖 记录日志,返回错误状态wareSkuMapper.incrStockLocked(wareSku);
}
1. 假如库存中有10件商品,a提交完订单(数量9件),此时(锁定数量9),此时还没调用付款,b马上提交了订单数量为2件,此时锁定数量为11就超过了库存数量,b操作失败【页面提示库存不足】2. 库存中有10件商品,A,锁定的3件,隔段时间b锁定了4件,总的锁定就为7件,此时假设仓库管理员把a的商品3件出库了,此时出库数量为3,总锁定数量为4件,库存数量变为73. 库存中有10件商品,A,锁定了3件,但此时由于a的卡上余额不足,则进行回退撤销a锁定的3键,锁定数量变为零,支付失败。
1. 提交订单时,先校验数据库中的价格是否有变动?库存,比如你购买了十件,他会去查看仓库里是否有十件货,【验证库存数量--数量不足就return null】,而不是锁库存的数量,也不是库存减去所库存的数量2. 此时订单创建完毕,就跳转到支付3. 支付--》订单--》库存要保证分布式事务的一致性,如果抛异常就进行事务的回滚向支付成功队列里面发送支付成功的消息,然后提交事务订单业务收到消息更新订单状态为已付款..向库存发消息库存系统进行锁库存【更新库存锁定数量字段比如为2】,比如说库存数量更新为二,更新订单状态为商品准备出库,比如库存只有十件,但是我库存却出现了20件这时候数据库里所库存阶段维持,然后抛出错误,抄卖一场的日志到日志文件里面订单更新完状态之后又会通知物流系统,实时物流系统会发送物品及物品出库此时库存数量会减,然后以出库数量会增加然后订单状态更新为已出库
我们正常不可能在同一时间点击按钮多次,但是黑客通过程序可以实现在同一时间发送多个请求,并且用同一个交易码,这就可能导致高并发下(通过一个交易码多次提交订单,正常情况下是一个交易码,只能提交一次订单
购物车里有五件商品,也相当于有五个订单,你提交的时候,如果一个商品及其中的一个订单,他没有库存了,那你如果把所有的五个订单都回滚,用户体验不太好(京东是直接替用户做决定的,把你不合格的那个订单直接给他删了在购物车里给他删了这其实是不好的你应该直接让用户重新操作,重新去修改订单,直到他教研通过为止)
交易订单号是自己程序,可以选择生成的,可以用你的私钥加上当前时间出来申请订单号,然后再用支付宝借口的时候传入订单号
saveorder,在保存订单时删除购物车的数据
使用dubbo的时候可以指令协议,默认的是dubbo协议
服务器和服务器之间可以通过一步进行信息的交流,如支付宝直接对支付服务器发送银行转账已完成的这种消息
实际库存数量就是仓库里实际有的商品
锁定库存数量就是当前用户下单,付完钱之后买了两件商品
决定要不要拆单试看一个sku能不能在库存表里面一次完成?鸡你买了十个红色手机,那这十个红色手机能否在一个库存,里面取出来,因为我们的仓库可能有多个,比如北京有一个仓库,南京有一个仓库,可能北京的仓库里面有三个红色手机,南京的仓库里面有七个红色手机,那就需要进行三把十个手机分成3和7,然后从三号上号上面
该订单的商品都在一个库存里面,所以直接调用首库存就可以了,如果发生拆单,那就循环进行锁库存
这篇关于分布式电商系统核心模块实现说明的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!