waynboot-mall项目
觉得有用的铁子们给个star就行了,求求你们啦😘😍
waynboot-mall是一套全部开源的微商城项目,包含一个运营后台、h5商城和后台接口。
实现了一个商城所需的首页展示、商品分类、商品详情、sku详情、商品搜索、加入购物车、结算下单、订单状态流转、商品评论等一系列功能。
技术上基于Springboot2.0,整合了Redis、RabbitMQ、ElasticSearch等常用中间件,
贴近生产环境实际经验开发而来不断完善、优化、改进中。
后台接口项目
运营后台项目
h5商城项目
waynboot-mall接口项目
- 商城接口代码清晰、注释完善、模块拆分合理
- 使用Spring-Security进行访问权限控制
- 使用jwt进行接口授权验证
- ORM层使用Mybatis Plus提升开发效率
- 添加全局异常处理器,统一异常处理
- 添加https配置代码,支持https访问
- 集成七牛云存储配置,上传文件至七牛
- 集成常用邮箱配置,方便发送邮件
- 集成druid连接池,进行sql监控
- 集成swagger,管理接口文档
- 添加策略模式使用示例,优化首页金刚区跳转逻辑
- 拆分出通用的数据访问模块,统一redis & elastic配置与访问
- 使用elasticsearch-rest-high-level-client客户端对elasticsearch进行操作
- 支持商品数据同步elasticsearch操作以及elasticsearch商品搜索
- RabbitMQ生产者发送消息采用异步confirm模式,消费者消费消息时需手动确认
- 下单处理过程引入rabbitMQ,异步生成订单记录,提高系统下单处理能力
- ...
商城难点整理
1. 库存扣减操作是在下单操作扣减还是在支付成功时扣减?(ps:扣减库存使用乐观锁机制 where goods_num - num >= 0
)
- 下单时扣减,这个方案属于实时扣减,当有大量下单请求时,由于订单数小于请求数,会发生下单失败,但是无法防止短时间大量恶意请求占用库存,
造成普通用户无法下单 - 支付成功扣减,这个方案可以预防恶意请求占用库存,但是会存在多个请求同时下单后,在支付回调中扣减库存失败,导致订单还是下单失败并且还要退还订单金额(这种请求就是订单数超过了库存数,无法发货,影响用户体验)
- 还是下单时扣减,但是对于未支付订单设置一个超时过期机制,比如下单时库存减一,生成订单后,对于未在15分钟内完成支付的订单,
自动取消超期未支付订单并将库存加一,该方案基本满足了大部分使用场景 - 针对大流量下单场景,比如一分钟内五十万次下单请求,可以通过设置虚拟库存的方式减少下单接口对数据库的访问。具体来说就是把商品实际库存保存到redis中,
下单时配合lua脚本原子的get和decr商品库存数量(这一步就拦截了大部分请求),执行成功后在扣减实际库存
2. 首页商品展示接口利用多线程技术进行查询优化,将多个sql语句的排队查询变成异步查询,接口时长只跟查询时长最大的sql查询挂钩
# 1. 通过创建子线程继承Callable接口Callable<List<Banner>> bannerCall = () -> iBannerService.list(new QueryWrapper<Banner>().eq("status", 0).orderByAsc("sort"));# 2. 传入Callable的任务给FutureTaskFutureTask<List<Banner>> bannerTask = new FutureTask<>(bannerCall);# 3. 放入线程池执行threadPoolTaskExecutor.submit(bannerTask);# 4. 最后可以在外部通过FutureTask的get方法异步获取执行结果 List<Banner> list = bannerTask.get()
3. ElasticSearch
查询操作,查询包含搜索关键字并且是上架中的商品,在根据指定字段进行排序,最后分页返回
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();MatchQueryBuilder matchFiler = QueryBuilders.matchQuery("isOnSale", true);MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", keyword);MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("keyword", keyword);boolQueryBuilder.filter(matchFiler).should(matchQuery).should(matchPhraseQueryBuilder).minimumShouldMatch(1);searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));// 按是否新品排序if (isNew) { searchSourceBuilder.sort(new FieldSortBuilder("isNew").order(SortOrder.DESC));}// 按是否热品排序if (isHot) { searchSourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));}// 按价格高低排序if (isPrice) { searchSourceBuilder.sort(new FieldSortBuilder("retailPrice").order("asc".equals(orderBy) ? SortOrder.ASC : SortOrder.DESC));}// 按销量排序if (isSales) { searchSourceBuilder.sort(new FieldSortBuilder("sales").order(SortOrder.DESC));}// 筛选新品if (filterNew) { MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isNew", true); boolQueryBuilder.filter(filterQuery);}// 筛选热品if (filterHot) { MatchQueryBuilder filterQuery = QueryBuilders.matchQuery("isHot", true); boolQueryBuilder.filter(filterQuery);}searchSourceBuilder.query(boolQueryBuilder);searchSourceBuilder.from((int) (page.getCurrent() - 1) * (int) page.getSize());searchSourceBuilder.size((int) page.getSize());List<JSONObject> list = elasticDocument.search("goods", searchSourceBuilder, JSONObject.class);
4. 订单编号生成规则:秒级时间戳 + 加密用户ID + 今日第几次下单
- 秒级时间戳:时间递增保证唯一性
- 加密用户ID:加密处理,返回用户ID6位数字,可以防并发访问,同一秒用户不会产生2个订单
- 今日第几次下单:便于运营查询处理用户当日订单
5. 下单流程处理过程,通过rabbitMQ异步生成订单,提高系统下单处理能力
- 用户点击提交订单按钮,后台生成订单编号和订单金额跳转到订单支付页面,并发送rabbitMQ消息(包含订单编号等信息)
- 订单消费者接受到订单消息后生成订单记录(未支付)
- 用户点击支付按钮时,前端根据订单编号轮询订单信息查询接口,如果订单编号记录已经入库则进行后续支付操作,如果订单编号未入库则返回错误信息(订单异常)
- 用户支付完成后在回调通知里更新订单状态为已支付
6. 金刚区跳转使用策略模式
# 1. 定义金刚位跳转策略接口public interface DiamondJumpType { List<Goods> getGoods(Page<Goods> page, Diamond diamond); Integer getType();}# 2. 定义策略实现类,并使用@Component注解注入spring@Componentpublic class CategoryStrategy implements DiamondJumpType { @Autowired private GoodsMapper goodsMapper; @Override public List<Goods> getGoods(Page<Goods> page, Diamond diamond) { List<Long> cateList = Arrays.asList(diamond.getValueId()); return goodsMapper.selectGoodsListPageByl2CateId(page, cateList).getRecords(); } @Override public Integer getType() { return JumpTypeEnum.CATEGORY.getType(); }}@Componentpublic class ColumnStrategy implements DiamondJumpType { @Autowired private IColumnGoodsRelationService iColumnGoodsRelationService; @Autowired private IGoodsService iGoodsService; @Override public List<Goods> getGoods(Page<Goods> page, Diamond diamond) { List<ColumnGoodsRelation> goodsRelationList = iColumnGoodsRelationService.list(new QueryWrapper<ColumnGoodsRelation>() .eq("column_id", diamond.getValueId())); List<Long> goodsIdList = goodsRelationList.stream().map(ColumnGoodsRelation::getGoodsId).collect(Collectors.toList()); Page<Goods> goodsPage = iGoodsService.page(page, new QueryWrapper<Goods>().in("id", goodsIdList).eq("is_on_sale", true)); return goodsPage.getRecords(); } @Override public Integer getType() { return JumpTypeEnum.COLUMN.getType(); }}# 3. 定义策略上下文,通过构造器注入spring,定义map属性,通过key获取对应策略实现类@Componentpublic class DiamondJumpContext { private Map<Integer, DiamondJumpType> map = new HashMap<>(); /** * 由spring自动注入DiamondJumpType子类 * * @param diamondJumpTypes 金刚位跳转类型集合 */ public DiamondJumpContext(List<DiamondJumpType> diamondJumpTypes) { for (DiamondJumpType diamondJumpType : diamondJumpTypes) { map.put(diamondJumpType.getType(), diamondJumpType); } } public DiamondJumpType getInstance(Integer jumpType) { return map.get(jumpType); }}# 4.使用@Autowiredprivate DiamondJumpContext diamondJumpContext;@Testpublic void test(){ DiamondJumpType diamondJumpType = diamondJumpContext.getInstance(1);}
- todo
文件目录
|-- waynboot-admin-api // 运营后台api模块,提供后台项目api接口|-- waynboot-common // 通用模块,包含项目核心基础类|-- waynboot-data // 数据模块,通用中间件数据访问| |-- waynboot-data-redis // redis访问配置模块| |-- waynboot-data-elastic // elastic访问配置模块|-- waynboot-generator // 代码生成模块|-- waynboot-message-consumer // 消费者模块,处理订单消息和邮件消息|-- waynboot-message-core // 消费者核心模块,队列、交换机配置|-- waynboot-mobile-api // h5商城api模块,提供h5商城api接口|-- pom.
开发部署
# 1. 克隆项目git clone git@github.com:wayn111/waynboot-mall.git# 2. 导入项目依赖将waynboot-mall目录用idea打开,导入maven依赖# 3. 安装Mysql8.0+、Redis3.0+、RabbitMQ3.0+、ElasticSearch7.0+到本地# 4. 导入sql文件在项目根目录下,找到`wayn_shop_*.sql`文件,新建mysql数据库wayn_shop,导入其中# 5. 修改Mysql、Redis、RabbitMQ、Elasticsearch连接配置修改`application-dev.yml`以及`application.yml`文件中数据连接配置相关信息# 6. 启动项目后台api: 进入waynboot-admin-api子项目,找到AdminApplication文件,右键`run AdminApplication`,启动后台项目h5商城api: 进入waynboot-mobile-api子项目,找到MobileApplication文件,右键`run MobileApplication`,启动h5商城项目
在线体验
- 注册一个账号
- 然后登陆
演示地址 id="演示图">演示图
商城登陆 | 商城注册 |
商城首页 | 商城搜索 |
搜索结果展示 | 金刚位跳转 |
商品分类 | 商品详情 |
商品sku选择 | 购物车查看 |
确认下单 | 选择支付方式 |
商城我的页面 | 我的订单列表 |
添加商品评论 | 查看商品评论 |
后台登陆 | 后台首页 |
后台会员管理 | 后台评论管理 |
后台地址管理 | 后台添加商品 |
后台商品管理 | 后台banner管理 |
后台订单管理 | 后台分类管理 |
后台金刚区管理 | 后台栏目管理 |
waynboot-mall交流群
QQ群:
有问题可以先提issue😁
todo
- [ ] 支持多店铺
- [ ] 订单详情页面
- [ ] 秒杀专区
- [ ] 优惠卷使用
- [ ] 团购下单
- [ ] ...
感谢
- panda-mall
- litemall
- vant-ui
原文转载:http://www.shaoqun.com/a/744806.html
e票联:https://www.ikjzd.com/w/1452
myyearbook:https://www.ikjzd.com/w/726
waynboot-mall项目觉得有用的铁子们给个star就行了,求求你们啦😘😍waynboot-mall是一套全部开源的微商城项目,包含一个运营后台、h5商城和后台接口。实现了一个商城所需的首页展示、商品分类、商品详情、sku详情、商品搜索、加入购物车、结算下单、订单状态流转、商品评论等一系列功能。技术上基于Springboot2.0,整合了Redis、RabbitMQ、ElasticSea
点通:https://www.ikjzd.com/w/1913
zen cart:https://www.ikjzd.com/w/1282
灯塔计划:https://www.ikjzd.com/w/1281
老婆为了偷情 在家不生孩子:http://lady.shaoqun.com/m/a/272846.html
2月份开始生效,亚马逊放宽了交付要求:https://www.ikjzd.com/home/139735
猪年开启抢人大战?傲基公寓拎包入住,公狼招250人!:https://www.ikjzd.com/home/16566
没有评论:
发表评论