今天教大家如何設計一個服裝商城?, 基于目前主流的技術:前端vue3,后端springboot。
同時還帶來的項目的部署教程。
系統最大的亮點是使用了兩個推薦算法:
1. 基于Jaccard算法的用戶瀏覽歷史推薦。
2. 基于用戶的協同過濾算法個性化推薦。
還有核心的商城保證庫存不超賣,訂單,運費等基本商城系統設計。下面我會詳細描述下這些設計思路。?
視頻演示
用戶協同過濾算法服裝商城
圖片演示
系統概述
商城是一款比較龐大的系統,需要有商品中心,庫存中心,訂單中心,收貨地址和運費管理。先看下我們要實現的商城有哪些功能:
1. 商品分類管理。
2. 商品管理。
3.庫存管理。
4.訂單管理。?
5. 評價管理。
6.用戶管理。
7.運費和運費模板管理。
8. 系統公告管理。
9.首頁輪播圖管理。
10. 用戶購物車。
核心功能實現思想
用戶協同過濾算法的設計
協同過濾(Collaborative Filtering, CF)是推薦系統中最經典的算法之一,其核心思想是通過用戶的歷史行為數據(如評分、點擊、購買等)發現用戶或物品的相似性,并基于這種相似性進行推薦。協同過濾分為兩大類:基于用戶的協同過濾和基于物品的協同過濾。
算法的步驟
1.?獲取所有用戶行為數據,構建用戶-物品評分矩陣。
2. 目標用戶與其它用戶的相似度計算: 將用戶對商品的評分視為向量,計算余弦相似度。
3.?選取與目標用戶相似度最高的?k?個用戶作為鄰居 。
4.?通過鄰居用戶的評分進行加權平均預測(權重為用戶相似度)。
5.?將預測評分按降序排序,選擇評分最高的N個物品作為推薦結果。
舉例說明
用戶評分矩陣的構建
需要借助
?是 Apache Commons Math 庫中的一個類,用于表示二維實數矩陣,并提供矩陣運算功能。Array2DRowRealMatrix算法工具,
Array2DRowRealMatrix
maven依賴如下:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-math3</artifactId><version>3.6.1</version></dependency>
構建評分矩陣的代碼:
// 獲取所有用戶的行為數據,用于構建用戶-物品評分矩陣List<UserBehavior> allBehaviors = userBehaviorRepository.selectList(null);if(CollectionUtils.isEmpty(allBehaviors)) {return Collections.emptyList();}// 構建用戶和物品的索引映射,方便后續構建評分矩陣Map<Long, Integer> userIndex = new HashMap<>();Map<Long, Integer> itemIndex = new HashMap<>();// 提取用戶idList<Long> users = allBehaviors.stream().map(UserBehavior::getUserId).distinct().collect(Collectors.toList());// 提取物品idList<Long> items = allBehaviors.stream().map(UserBehavior::getItemId).distinct().collect(Collectors.toList());for (int i = 0; i < users.size(); i++) {userIndex.put(users.get(i), i);}for (int i = 0; i < items.size(); i++) {itemIndex.put(items.get(i), i);}// 初始化評分矩陣,行表示用戶,列表示物品 一個 users.size() x items.size() 大小的矩陣RealMatrix ratingMatrix = new Array2DRowRealMatrix(users.size(), items.size());// 根據用戶行為數據填充評分矩陣for (UserBehavior behavior : allBehaviors) {if (behavior.getRating() != null) {int uIndex = userIndex.get(behavior.getUserId());int iIndex = itemIndex.get(behavior.getItemId());// 設置 矩陣的 行,列 值 為 評分ratingMatrix.setEntry(uIndex, iIndex, behavior.getRating());}}
余弦相似度計算
/*** 計算兩個向量的余弦相似度* 余弦相似度用于衡量兩個用戶的評分模式的相似程度* @param vector1 第一個用戶的評分向量* @param vector2 第二個用戶的評分向量* @return 相似度值,范圍[-1,1],值越大表示越相似*/private double calculateCosineSimilarity(double[] vector1, double[] vector2) {double dotProduct = 0.0;double norm1 = 0.0;double norm2 = 0.0;for (int i = 0; i < vector1.length; i++) {dotProduct += vector1[i] * vector2[i];norm1 += vector1[i] * vector1[i];norm2 += vector2[i] * vector2[i];}if (norm1 == 0 || norm2 == 0) return 0;return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));}
根據余弦相似度計算取5個相似的用戶作為鄰居
// 計算目標用戶與其他用戶的相似度int userIdx = userIndex.get(user.getId());Map<Integer, Double> userSimilarities = new HashMap<>();for (int i = 0; i < users.size(); i++) {if (i != userIdx) {// 計算 當前用戶 與其他的每一個用戶的評分向量的 余弦相似度double similarity = calculateCosineSimilarity(ratingMatrix.getRow(userIdx), ratingMatrix.getRow(i));userSimilarities.put(i, similarity);}}// 選擇最相似的5個用戶作為鄰居用戶List<Integer> similarUsers = userSimilarities.entrySet().stream()// 按相似度值降序排序.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())// 取前5個最相似用戶.limit(5)// 提取用戶索引.map(Map.Entry::getKey).collect(Collectors.toList());
最后是計算加權平均,當中還需要進行?歸一化處理,?來避免了因用戶群體整體相似度偏高/偏低導致的預測偏差,使得推薦結果更貼近用戶的真實偏好。
整體代碼較長,我就不貼了。
庫存系統的設計
庫存最大的問題就是超賣,也就是說有多個人同時并發下單,庫存需要保持一致性,不會扣減到小于0的情況。普通的設計就是加一個全局鎖。每個人下單都需要等待上一個人下單完成。
這樣嚴重影響效率。這里我們庫存的設計流程如下:
1. 首先我們將庫存分為?數據庫庫存?和?銷售庫存。 數據庫庫存就是存儲到數據庫的商品庫存值,銷售庫存就是用戶下單,頁面所在的庫存值。
2. 后臺管理上架商品時,會設置一個初始庫存,我們將初始庫存存儲到數據庫庫存?和?銷售庫存 。
3.當用戶下單時,不是直接扣減的數據庫庫存,而是通過redis的?decrement 方法,對銷售庫存進行扣減。但是redis的扣減操作這里還不是一個原子性操作,需要先從redis查出庫存,然后進行decrment操作。這兩步操作我們用reddsion的分布式鎖來控制原子性,同時,我們將加鎖的維度控制到了商品id。這樣大大提高了并發效率。
3. 庫存扣減后,我們又通過redis消費隊列,實現了對數據庫庫存的同步。這樣保持了redis庫存和數據庫庫存的一致性。
4. 后臺我們設計的是對商品只能加加庫存,和減少庫存的操作,而不是直接修改庫存值。如果你直接修改庫存值,就有可能會導致庫存數據不一致,難以跟蹤。
5. 我們還設計了庫存的扣減,新增日志,方便對庫存進行跟蹤管理。
庫存扣減的部分代碼:
/*** 扣減庫存(使用Redisson分布式鎖)* @param quantity 扣減數量* @return true-扣減成功,false-扣減失敗(庫存不足)*/public boolean deductInventory(Integer spuId, int quantity) {String lockKey = "lock:inventory:" + spuId;String inventoryKey = "inventory:" + spuId;RLock lock = redissonClient.getLock(lockKey);try {// 嘗試加鎖,最多等待10秒,鎖過期時間30秒boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);if (locked) {String stock = (String) redisTemplate.opsForValue().get(inventoryKey);if (StringUtils.isEmpty(stock)) {return false;}if (Integer.parseInt(stock) < quantity) {return false;}// 扣減庫存redisTemplate.opsForValue().decrement(inventoryKey, quantity);return true;}return false;} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;} finally {// 釋放鎖if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
商品系統的設計
商品包含了很多屬性,這里我設計的商品表如下:
CREATE TABLE `product` (`product_id` int NOT NULL AUTO_INCREMENT COMMENT '商品id',`product_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名稱',`category_id` int NOT NULL COMMENT '類目id',`product_title` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品標題',`product_intro` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品詳情',`product_picture` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品封面圖',`spu0_price` double NOT NULL COMMENT '參考價,商品第一個spu的價格',`product_sales` int NOT NULL COMMENT '銷量',`state` tinyint DEFAULT '0' COMMENT '0-上架 1- 下架',PRIMARY KEY (`product_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='商品';
商品關聯的spu規格表:
CREATE TABLE `product_spu` (`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',`product_id` int NOT NULL COMMENT '商品id',`spu_name` varchar(100) COLLATE utf8mb4_bin NOT NULL COMMENT '規格的key名稱,比如尺碼',`spu_value` varchar(100) COLLATE utf8mb4_bin NOT NULL COMMENT '規格的key的值,比如尺碼的大小是S',`spu_price` double NOT NULL COMMENT '商品售賣價',`spu_stock` int NOT NULL COMMENT 'spu庫存',`state` tinyint DEFAULT '0' COMMENT '0-上架 1- 下架',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=85 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='商品最小單元';
商品還關聯了多圖
CREATE TABLE `product_picture` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵id',`product_id` int NOT NULL COMMENT '商品id',`product_picture` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品圖片',`intro` text CHARACTER SET utf8 COLLATE utf8_general_ci,PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=276 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='商品圖片';
商品還有一個動態的字典屬性
動態字典屬性表設計
CREATE TABLE `product_attr` (`id` int NOT NULL AUTO_INCREMENT,`product_id` int NOT NULL,`product_attr_config_id` int NOT NULL COMMENT '商品屬性字典id',`attr_val` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '屬性值',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=295 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='商品屬性與字典關聯';
訂單系統的設計
我們將訂單的狀態設計成以下幾種:0-待付款 1-待發貨 2-待收貨 3-待評價 4-已完成 5-退款中 6-已退款 7-已取消。
訂單狀態扭轉流程:
1. 用戶點擊購買商品或從購物車點擊,則商品進入待付款狀態,此時,商品庫存被鎖,也就是實實在在的扣減了銷售庫存。
2. 當30分鐘超過后,用戶未支付上面的待付款訂單,則訂單狀態扭轉為已取消,庫存回流,此筆訂單結束。
3. 當用戶30分鐘內支付后,訂單扭轉為代發貨。
4. 管理員登錄管理后臺,將待發貨訂單進行發貨操作后,訂單狀態變成待收貨。
5. 用戶可以對待收獲訂單進行收獲和退款操作,如果是退款,則變成退款中,管理員進行退款確認,確認后,訂單變成已退款,退款成功后,庫存回流。此訂單結束。
6. 如果用戶確認收獲,則訂單變成待評價,用戶可以進行評價,評價完成后,訂單變成已完成,此訂單結束。
訂單表設計如下:
CREATE TABLE `orders` (`order_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,`user_id` int NOT NULL COMMENT '用戶id',`spu_id` int NOT NULL COMMENT '商品id',`spu_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'spu的名稱',`spu_value` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'spu的值',`product_num` int NOT NULL COMMENT '商品數量',`order_state` int NOT NULL COMMENT '訂單狀態 0-待付款 1-待發貨 2-待收貨 3-待評價 4-已完成 5-退款中 6-已退款 7-已取消',`product_price` double NOT NULL COMMENT '下單商品價格',`shipping_price` double NOT NULL COMMENT '下單運費價格',`refund_cause` varchar(2255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '退款原因',`order_remark` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '訂單備注',`pay_type` int DEFAULT NULL COMMENT '支付方式:0-支付寶 1-微信',`address` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收獲地址',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='訂單';
運費的設計
首先需要有一個模板表 , 上架商品時, 直接選中到這個運費模板
CREATE TABLE `shipping_template` (`template_id` int NOT NULL AUTO_INCREMENT,`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '模板名稱',`is_free_shipping` tinyint(1) DEFAULT '0' COMMENT '是否包郵(0否1是)',PRIMARY KEY (`template_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='運費模板表';
每個模板有著自己的運費規則, 規則里面重要的就是城市信息。每個城市的運費都不一樣。
CREATE TABLE `shipping_rule` (`id` int NOT NULL AUTO_INCREMENT,`template_id` int NOT NULL COMMENT '運費模板ID',`city_id` int NOT NULL COMMENT '地區編碼(可多級)',`first_fee` double NOT NULL COMMENT '該地域的運費',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='運費規則表';
最后,有一個城市表 , 記錄著中國的省市區數據
CREATE TABLE `city` (`id` int NOT NULL COMMENT '主鍵',`name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '省市區名稱',`parentid` int DEFAULT NULL COMMENT '上級ID',`shortname` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '簡稱',`leveltype` tinyint DEFAULT NULL COMMENT '級別:0,中國;1,省分;2,市;3,區、縣',`citycode` varchar(7) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '城市代碼',`zipcode` varchar(7) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '郵編',`lng` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '經度',`lat` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '緯度',`pinyin` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '拼音',`status` enum('0','1') CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
三. 技術棧概述
后端技術棧:
JDK8 + springboot + mysql8
前端技術棧:
vue3 +?Axios 等
四. 項目部署教程
前端部署
安裝node , 版本:v22.15.0 , 安裝完成后。
?進入到項目?hadluo-shop-webadmin 目錄下,這個項目是vue的管理后臺, 右鍵,運行cmd,運行下面命令:
npm run dev
由于我已經跟你npm install好了,所以你無需執行,直接run就可以了!!
運行項目
進入到項目?hadluo-shop-h5 目錄下,這個項目是vue的前端, 右鍵,運行cmd,運行下面命令:
npm run dev
由于我已經跟你npm install好了,所以你無需執行,直接run就可以了!!
運行項目
到此前端項目部署完成。
執行sql
自己安裝好數據庫,注意,必須是mysql8 ,否則代碼運行會出錯。新建一個?clothingshop 數據庫, 然后執行? “clothingshop.sql”
Redis安裝
項目需要安裝redis,直接下載一個windows版本的redis即可,沒有的聯系我。
啟動后端項目
然后部署后端 , 打開idea, 導入maven工程 hadluo-bookshop。
打開resources目錄, 修改 application.yml 配置文件,主要修改下面幾個信息:
然后啟動? main 啟動類 :?Application.class
五. 訪問項目
管理后端:
http://localhost:3001/
賬號:wx-hadluo,? 密碼: 123456
前端:
http://localhost:3000/
用戶:?wxhadluo / 123456
可以自己注冊用戶。