一套前后台全部开源的H5商城送给大家

博主给大家推荐一套全部开源的H5电商项目waynboot-mall。由博主在2020年开发至今,已有三年之久。那时候网上很多的H5商城项目都是半开源版本,要么没有H5前端代码,要么需要加群咨询,属实恶心。于是博主决定自己开发一套完整的移动端H5商城,包含一个管理后台、一个前台H5商城、一套后端接口。项目地址如下:

欢迎大家关注这个项目,点个Star让更多的人了解到这个项目。

一、简介

waynboot-mall是一套全部开源的微商城项目,实现了一个商城所需的首页展示、商品分类、商品详情、sku组合、商品搜索、购物车、结算下单、订单状态流转、商品评论等一系列功能。
技术上基于最新得Spring Boot3.0、Jdk17,整合了Redis、RabbitMQ、ElasticSearch等常用中间件,
贴近生产环境实际经验开发而来。


二、技术特点

  1. 订单金额计算使用BigDeciaml类型,支持小数点后两位
  2. 支持微信内JsApi支付、H5网页支付
  3. 商城接口代码清晰、注释完善、模块拆分合理
  4. 使用Spring-Security进行访问权限控制
  5. 使用jwt进行接口授权验证
  6. ORM层使用Mybatis Plus提升开发效率
  7. 添加全局异常处理器,统一异常处理
  8. 使用Spring Boot admin进行服务监控
  9. 集成七牛云存储配置,支持上传文件至七牛获取cdn下载链接
  10. 集成常用邮箱配置,方便发送邮件
  11. 添加策略模式使用示例,优化首页金刚区跳转逻辑
  12. 拆分出通用的数据访问模块,统一Redis & Elastic配置与访问
  13. 使用Elasticsearch高级客户端依赖对Elasticsearch进行操作
  14. 支持商品数据同步Elasticsearch操作以及中文分词搜索
  15. RabbitMQ生产者发送消息采用异步confirm模式,消费者消费消息时需手动确认确保消息不丢失
  16. 下单处理过程引入RabbitMQ,异步生成订单记录,提高系统下单处理能力

三、商城设计

文项目目录

|-- waynboot-monitor               // 监控模块
|-- 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.xml                        // maven父项目依赖,定义子项目依赖版本
|-- ...

技术亮点

2.1 库存扣减

库存扣减操作是在下单操作扣减还是在支付成功时扣减?(ps:扣减库存使用乐观锁机制 where goods_num - num >= 0

  1. 下单时扣减,这个方案属于实时扣减,当有大量下单请求时,由于订单数小于请求数,会发生下单失败,但是无法防止短时间大量恶意请求占用库存,
    造成普通用户无法下单
  2. 支付成功扣减,这个方案可以预防恶意请求占用库存,但是会存在多个请求同时下单后,在支付回调中扣减库存失败,导致订单还是下单失败并且还要退还订单金额(这种请求就是订单数超过了库存数,无法发货,影响用户体验)
  3. 还是下单时扣减,但是对于未支付订单设置一个超时过期机制,比如下单时库存减一,生成订单后,对于未在15分钟内完成支付的订单,
    自动取消超期未支付订单并将库存加一,该方案基本满足了大部分使用场景
  4. 针对大流量下单场景,比如一分钟内五十万次下单请求,可以通过设置虚拟库存的方式减少下单接口对数据库的访问。具体来说就是把商品库存缓存到redis中,
    下单时配合lua脚本原子的get和decr商品库存数量(这一步就拦截了大部分请求),执行成功后在扣减实际库存

2.2 首页查询

首页商品展示接口利用多线程技术进行查询优化,将多个sql语句的排队查询变成异步查询,接口时长只跟查询时长最大的sql查询挂钩

// 使用CompletableFuture异步查询
List<CompletableFuture<Void>> list = new ArrayList<>();
CompletableFuture<Void> f1 = CompletableFuture.supplyAsync(() -> iBannerService.list(Wrappers.lambdaQuery(Banner.class).eq(Banner::getStatus, 0).orderByAsc(Banner::getSort)), homeThreadPoolTaskExecutor).thenAccept(data -> {
    String key = "bannerList";
    redisCache.setCacheMapValue(SHOP_HOME_INDEX_HASH, key, data);
    success.add(key, data);
});
CompletableFuture<Void> f2 = CompletableFuture.supplyAsync(() -> iDiamondService.list(Wrappers.lambdaQuery(Diamond.class).orderByAsc(Diamond::getSort).last("limit 10")), homeThreadPoolTaskExecutor).thenAccept(data -> {
    String key = "categoryList";
    redisCache.setCacheMapValue(SHOP_HOME_INDEX_HASH, key, data);
    success.add(key, data);
});
list.add(f1);
list.add(f2);
// 主线程等待子线程执行完毕
CompletableFuture.allOf(list.toArray(new CompletableFuture[0])).join();

2.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);

2.4 订单编号

订单编号生成规则:秒级时间戳 + 加密用户ID + 今日第几次下单

  1. 秒级时间戳:时间递增保证唯一性
  2. 加密用户ID:加密处理,返回用户ID6位数字,可以防并发访问,同一秒用户不会产生2个订单
  3. 今日第几次下单:便于运营查询处理用户当日订单
/**
 * 返回订单编号,生成规则:秒级时间戳 + 加密用户ID + 今日第几次下单
 *
 * @param userId 用户ID
 * @return 订单编号
 */
public static String generateOrderSn(Long userId) {
        long now = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        return now + encryptUserId(String.valueOf(userId), 6) + countByOrderSn(userId);
}

/**
 * 计算该用户今日内第几次下单
 *
 * @param userId 用户ID
 * @return 该用户今日第几次下单
 */
public static int countByOrderSn(Long userId) {
        IOrderService orderService = SpringContextUtil.getBean(IOrderService.class);
        return orderService.count(new QueryWrapper<Order>().eq("user_id", userId)
        .gt("create_time", LocalDate.now())
        .lt("create_time", LocalDate.now().plusDays(1)));
}

/**
 * 加密用户ID,返回num位字符串
 *
 * @param userId 用户ID
 * @param num    长度
 * @return num位加密字符串
 */
private static String encryptUserId(String userId, int num) {
        return String.format("%0" + num + "d", Integer.parseInt(userId) + 1);
}

2.5 异步下单

下单流程处理过程,通过rabbitMQ异步生成订单,提高系统下单处理能力

  1. 用户点击提交订单按钮,后台生成订单编号和订单金额跳转到订单支付页面,并将订单编号等信息发送rabbitMQ消息(生成订单编号,还未生成订单)
  2. 订单消费者接受到订单消息后,获取订单编号生成订单记录(订单创建成功,用户待支付)
  3. 下单页面,前端根据订单编号轮询订单接口,订单已创建则跳转支付页面,否则提示下单失败(订单创建失败)
  4. 支付页面,用户点击支付按钮时,后台调用微信/支付宝下单接口后,前端唤醒微信/支付宝支付,用户输入密码
  5. 用户支付完成后在微信/支付宝下回调通知里更新订单状态为已支付(订单已支付)
  6. 用户支付完成后,返回支付状态查看页面。

2.6 设计模式

金刚区跳转使用策略模式进行代码编写

1.定义金刚位跳转策略接口以及跳转枚举类

public interface DiamondJumpType {

    List<Goods> getGoods(Page<Goods> page, Diamond diamond);

    Integer getType();
}

// 金刚位跳转类型枚举
public enum JumpTypeEnum {
    COLUMN(0),
    CATEGORY(1);

    private Integer type;

    JumpTypeEnum(Integer type) {
        this.type = type;
    }

    public Integer getType() {
        return type;
    }

    public JumpTypeEnum setType(Integer type) {
        this.type = type;
        return this;
    }
}

2.定义策略实现类,并使用@Component注解注入spring

// 分类策略实现
@Component
public 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();
    }
}

// 栏目策略实现
@Component
public 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获取对应策略实现类

@Component
public class DiamondJumpContext {

    private final 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.使用,注入DiamondJumpContext对象,调用getInstance方法传入枚举类型

@Autowired
private DiamondJumpContext diamondJumpContext;

@Test
public void test(){
    DiamondJumpType diamondJumpType=diamondJumpContext.getInstance(JumpTypeEnum.COLUMN.getType());
}

四、演示图

商城登陆一套前后台全部开源的H5商城送给大家 商城注册一套前后台全部开源的H5商城送给大家
商城首页一套前后台全部开源的H5商城送给大家 商城搜索一套前后台全部开源的H5商城送给大家
搜索结果展示一套前后台全部开源的H5商城送给大家 金刚位跳转一套前后台全部开源的H5商城送给大家
商品分类一套前后台全部开源的H5商城送给大家 商品详情一套前后台全部开源的H5商城送给大家
商品sku选择一套前后台全部开源的H5商城送给大家 购物车查看一套前后台全部开源的H5商城送给大家
确认下单一套前后台全部开源的H5商城送给大家 选择支付方式一套前后台全部开源的H5商城送给大家
商城我的页面一套前后台全部开源的H5商城送给大家 我的订单列表一套前后台全部开源的H5商城送给大家
添加商品评论一套前后台全部开源的H5商城送给大家 查看商品评论一套前后台全部开源的H5商城送给大家
后台登陆一套前后台全部开源的H5商城送给大家 后台首页一套前后台全部开源的H5商城送给大家
后台会员管理一套前后台全部开源的H5商城送给大家 后台评论管理一套前后台全部开源的H5商城送给大家
后台地址管理一套前后台全部开源的H5商城送给大家 后台添加商品一套前后台全部开源的H5商城送给大家
后台商品管理一套前后台全部开源的H5商城送给大家 后台banner管理一套前后台全部开源的H5商城送给大家
后台订单管理一套前后台全部开源的H5商城送给大家 后台分类管理一套前后台全部开源的H5商城送给大家
后台金刚区管理一套前后台全部开源的H5商城送给大家 后台栏目管理一套前后台全部开源的H5商城送给大家

五、在线体验

演示地址:http://121.4.124.33/mall

最后说两句waynboot-mall作为博主的开源项目集大成者,对于没有接触过商城项目的小伙伴来说是非常具有帮助和学习价值的。看完这个项目你能了解到一个商城项目的基本全貌,提前避坑。

感谢大家阅读,希望这篇文章能为你提供价值。公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力?。

原文链接:https://www.cnblogs.com/waynaqua/p/17375805.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一套前后台全部开源的H5商城送给大家 - Python技术站

(0)
上一篇 2023年5月6日
下一篇 2023年5月6日

相关文章

  • 在jsp页面中响应速度提高的7种方法分享

    “在jsp页面中响应速度提高的7种方法分享”是一篇介绍如何提升jsp页面响应速度的文章。下面我们来逐一讲解这7条方法。 1. 压缩页面 在jsp页面中,压缩页面可以减少文件大小,从而减少传输时间,提高页面加载速度。可以使用GZIP、BZIP等压缩技术进行压缩。在jsp中,可以使用filter过滤器来实现页面压缩。以下是一个示例: public class C…

    Java 2023年6月15日
    00
  • Java线程间共享实现方法详解

    Java线程间共享实现方法详解 什么是线程间共享 在Java中,线程是运行在同一个进程中的多个子任务。这些子任务可以共享代码、数据和资源。线程间共享就是指多个线程访问同一个数据和资源的过程。 在多线程编程中,线程间共享常用于实现任务之间的通信和协作,例如,生产者消费者模式、读写锁等场景。 线程间共享实现方法 Java提供了多种实现线程间共享的方式,常用的包括…

    Java 2023年5月19日
    00
  • 基于Spring的Maven项目实现发送邮件功能的示例

    这里是一份基于Spring的Maven项目实现发送邮件功能的攻略,包含了完整的步骤和示例,帮助你了解如何在项目中实现发送邮件的功能。 1. 配置POM文件 首先,咱们需要在pom.xml文件中添加以下依赖: <dependency> <groupId>org.springframework</groupId> <ar…

    Java 2023年6月15日
    00
  • Spring Boot Cache使用方法整合代码实例

    下面我将详细讲解“Spring Boot Cache使用方法整合代码实例”的完整攻略。 一、什么是Spring Boot Cache Spring Boot Cache是Spring Boot中的缓存框架,它提供了一种简单的方式来缓存数据的读取结果,从而减少不必要的计算并提升应用程序的性能。 二、Spring Boot Cache使用方法 1. 引入依赖 在…

    Java 2023年5月31日
    00
  • Java实战之兼职平台系统的实现

    Java实战之兼职平台系统的实现——完整攻略 前言 本文将介绍如何使用Java实现一个兼职平台系统,其中包括如何搭建项目框架、如何设计数据库、如何实现用户注册、登录、发布任务、接受任务等功能。 项目框架搭建 在开始实现具体功能之前,我们需要先搭建好项目的框架。我们推荐使用Spring Boot作为项目框架,因为它具有快速开发、易于维护等优点。下面是搭建项目框…

    Java 2023年5月18日
    00
  • Java基础之简单介绍一下Maven

    Java基础之简单介绍一下Maven 概述 Maven是Apache基金会的一个开源项目管理和构建工具。它可以自动化地构建、测试和部署Java项目,并且可以自动下载依赖的库。 安装Maven Maven可以在官方下载页面https://maven.apache.org/download.cgi 上下载,选择适合自己操作系统的Maven版本下载,然后解压。 在…

    Java 2023年5月19日
    00
  • SpringAop @Aspect织入不生效,不执行前置增强织入@Before方式

    在Spring AOP中,我们可以使用@Aspect注解来定义切面,并使用@Before注解来定义前置增强。但是有时候,我们可能会遇到@Aspect织入不生效的问题,即前置增强不执行。本文将详细介绍如何解决@Aspect织入不生效的问题,并提供两个示例说明。 1. 解决@Aspect织入不生效的问题 在解决@Aspect织入不生效的问题时,我们可以采取以下措…

    Java 2023年5月18日
    00
  • Java数据结构之位图的简单实现和使用

    Java数据结构之位图的简单实现和使用 随着数据量的快速增长,数据结构的高效率已经变得越来越重要。而位图是一个简单而高效率的用于数据存储与查询的数据结构。本文将详细介绍位图的概念、实现过程以及使用方法。 什么是位图? 位图(Bit Map) 是一种非常简单的存储数据结构,它使用一个或多个二进制位来表示一个数据的状态。位图的本质是一个大整数,其中的每个二进制位…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部