一套前後臺全部開源的H5商城送給大家

来源:https://www.cnblogs.com/waynaqua/archive/2023/05/06/17375805.html
-Advertisement-
Play Games

博主給大家推薦一套全部開源的H5電商項目waynboot-mall。由博主在2020年開發至今,已有三年之久。那時候網上很多的H5商城項目都是半開源版本,要麼沒有H5前端代碼,要麼需要加群咨詢,屬實噁心。於是博主決定自己開發一套完整的移動端H5商城,包含一個管理後臺、一個前臺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());
}

四、演示圖

商城登陸 商城註冊
商城首頁 商城搜索
搜索結果展示 金剛位跳轉
商品分類 商品詳情
商品sku選擇 購物車查看
確認下單 選擇支付方式
商城我的頁面 我的訂單列表
添加商品評論 查看商品評論
後臺登陸 後臺首頁
後臺會員管理 後臺評論管理
後臺地址管理 後臺添加商品
後臺商品管理 後臺banner管理
後臺訂單管理 後臺分類管理
後臺金剛區管理 後臺欄目管理

五、線上體驗

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

最後說兩句waynboot-mall作為博主的開源項目集大成者,對於沒有接觸過商城項目的小伙伴來說是非常具有幫助和學習價值的。看完這個項目你能瞭解到一個商城項目的基本全貌,提前避坑。

感謝大家閱讀,希望這篇文章能為你提供價值。公眾號【waynblog】每周分享技術乾貨、開源項目、實戰經驗、高效開發工具等,您的關註將是我的更新動力

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 此文為系列文章第一篇,為淺嘗輒止的引入,目的是為了讓前端從業人員及非從業但是對此領域感興趣的人對於”前端“是乾什麼的這個話題有個無門檻的瞭解。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 原型鏈 溫故而知新: 構造函數、原型和實例的關係: 每個構造函數都有一個原型對象,原型有一個屬性指回構造函數,實例有一個內部指針指向原型。 思考:如果原型是另一個類型的實例呢? 那就意味著這個原型本身有一個內部指針指向另一個原型,相 ...
  • 在HTML中, link 標簽是一個自閉合元素,通常位於文檔的 head 部分。它用於建立與外部資源的關聯,如樣式表、圖標等。 link 標簽具有多個屬性,其中 rel 和 href 是最常用的。 rel 屬性定義了當前文檔與鏈接資源之間的關係。常見的 rel 屬性值有: - stylesheet ...
  • 某次遇到一個從0到1的大型項目,該項目涉及兩個端,除了鑒權和部分業務邏輯不同外,頁面UI和其餘邏輯幾乎一致,遇到這種項目,該如何架構?既能保證項目順利開發完成,又能保證後期的迭代、維護、可擴展? ...
  • 本文從設計模式與編程語言的關係,設計模式與架構模式的區別,設計原則和設計模式的關係等幾個維度進行了分析和解答。關於設計模式應該如何學習和應用的問題,給出了學習意見和實踐心得。 ...
  • 前段時間,隨著阿裡集團CEO張勇的公開信發佈,阿裡集團也做出了歷史上最大的一次組織調整。 隨著新的1+6+N的組織陣型的調整和落地,阿裡曾經的中台戰略,變得有點非常的尷尬了,似乎成為了一個巨大的爭議。 ...
  • 回顧工廠方法設計模式的不足:具體產品增加時,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度 模式動機 產品等級結構:產品等級結構即產品的繼承結構,即抽象產品與具體產品 產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不同產品等級結構中的一組產品 模式定義 提供一個創建一系列相關 ...
  • 簡介 訪問者模式(Visitor Pattern)是一種行為型模式。它封裝一個訪問者類,把各元素類的操作集合起來,目的是將數據結構與數據操作分離。在不改變原有元素類數據結構的前提下,改變了元素類的執行演算法。 當某些較為穩定的東西(數據結構或演算法),不想直接被改變但又想擴展功能,這時候適合用訪問者模式 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...