今天教大家如何設計一個 鮮花商城 , 基于目前主流的技術:前端vue3,后端springboot。學習完這個項目,你將來找工作開發實際項目都會又很大幫助。文章最后部分還帶來的項目的部署教程。
系統有著基于用戶的協同過濾推薦算法,還有保證庫存不超賣的庫存系統設計,還帶有訂單(發貨,收獲,退款,評價),購物車,運費等某塊功能的設計,基本上符合將來工作上的實際項目。
視頻演示
鮮花商城(帶用戶協同過濾算法推薦)
圖片演示
系統功能概述
分類管理
首先,系統將鮮花按照功能分為4個大的類目(當然你也可以在管理后臺修改):
- 愛情鮮花
- 友情鮮花
- 商務鮮花
- 永生花
對應著后臺就是分類管理, 前端會在 全部鮮花板塊展示這個分類。
首頁管理
首頁有輪播圖,華語知識,鮮花分類,精品推薦,新品上架幾大模塊。
輪播圖: 在后臺的輪播圖管理,能夠對前端首頁的輪播廣告進行管理, 支持點擊跳轉到外部鏈接。
花語知識: 查詢最近10條的花語文章。這個可以在后臺管理的花語知識甘管理里面配置文章。
鮮花分類: 這個可以在后臺管理的字典管理里面配置,系統將鮮花分為了:康乃馨,百合花,向日葵,郁金香,紫羅蘭,滿天星,玫瑰花,永生花 。 這些類別在前端的全部鮮花板塊中可以進行篩選。
精品推薦: 查詢銷量的top前8個鮮花商品。銷量會在用戶支付接口調用后新增商品銷量,并且用戶退款不支持減去銷量。
新品上架: 查詢商品按照創建時間倒敘的top前8個鮮花商品。
商品管理
這個是系統的核心, 商品的很多屬性,主要有: 名稱,分類,多張商品圖片,商品詳情的動態屬性,商品詳情,spu等。在后臺管理的商品管理里面可以對商品進行上下架,商品新增,修改,切不可刪除。因為商品關聯了很多表,如果不賣該商品了,只能作下架處理。
spu庫存管理
spu是商品的最小單元,當用戶買玫瑰鮮花時,有可能是99多玫瑰,999多玫瑰,這些都是不同的規格, 每個規格有不同的庫存和價格。用戶購買時,都需要選中到具體的哪個規格才能保證下單。
在后臺管理的庫存管理,就可以對所有商品的spu規格進行管理,可以新增,減少庫存。還有詳細的庫存日志可查。
評價管理
評價是商城不可或缺的一部分,很多用戶就是看評價下單的,我們的評價有內容,還支持多張圖片,還有評分等級。可以說是一個很豐富的評價體系 。這個評分還參與了用戶協同過濾推薦系統。用戶只有當訂單處與完成狀態時,才可以對商品進行評價。
在后臺管理的商品管理下面的評價管理可以查看和刪除用戶的評價。
用戶管理
用戶有頭像,用戶名,郵箱,密碼,是否禁用,當前收獲地址等屬性。用戶前端注冊是通過郵箱驗證碼來注冊,所以系統初始化時,需要在后臺管理的字典管理的系統字典里面設置好郵箱服務器,然后才可以進行正確的注冊。
用戶登錄可以通過用戶名或者郵箱登錄。當后臺管理將用戶禁用后,登錄失效。
前端可以對用戶的用戶名,頭像進行修改, 還可以修改默認的收獲地址。
運費管理
運費首先是有一個運費模板,商品上架時選中這個運費模板, 如果模板是包郵的,則前端顯示鮮花商品的郵費就是包郵,下單接口也會計算這個郵費。
如果模板里面是不包郵的,那么就需要關聯多個運費規則,每個運費規則有省市區 和 運費 兩個屬性,這樣當用戶下單的收獲地址匹配到了運費規則 的地區,就可以拿到運費進行計算 訂單價格。
花語知識
這個板塊是鮮花商城的一個吸引模塊, 主要承載一些鮮花知識的文章,可以在后臺進行新增和編輯,還有刪除 鮮花知識。
購物車
用戶瀏覽鮮花時, 可以對鮮花商品進行收藏處理,收藏后可以在后臺管理的用戶列表的操作列看到查看收藏。前端用戶自己也可以進行收藏取消等。
訂單管理
我們將訂單的狀態設計成以下幾種:0-待付款 1-待發貨 2-待收貨 3-待評價 4-已完成 5-退款中 6-已退款 7-已取消。 訂單狀態扭轉流程:
1. 用戶點擊購買商品或從購物車點擊,則商品進入待付款狀態,此時,商品庫存被鎖,也就是實實在在的扣減了銷售庫存。
2. 當30分鐘超過后,用戶未支付上面的待付款訂單,則訂單狀態扭轉為已取消,庫存回流,此筆訂單結束。
3. 當用戶30分鐘內支付后,訂單扭轉為代發貨。
4. 管理員登錄管理后臺,將待發貨訂單進行發貨操作后,訂單狀態變成待收貨。
5. 用戶可以對待收獲訂單進行收獲和退款操作,如果是退款,則變成退款中,管理員進行退款確認,確認后,訂單變成已退款,退款成功后,庫存回流。此訂單結束。
6. 如果用戶確認收獲,則訂單變成待評價,用戶可以進行評價,評價完成后,訂單變成已完成,此訂單結束。
收貨地址
每個用戶新注冊是沒有收貨地址的,如果此時下單鮮花,也是無法下單。需要在個人中心去新增收獲地區,收貨地址有:省市區,具體的聯系人,手機號,具體地址信息。
用戶可以增加很多收貨地址, 同時用戶可以在修改資料時,切換已經存在的收貨地址。
核心功能實現思想
庫存系統的設計
庫存最大的問題就是超賣,也就是說有多個人同時并發下單,庫存需要保持一致性,不會扣減到小于0的情況。普通的設計就是加一個全局鎖。每個人下單都需要等待上一個人下單完成。
這樣嚴重影響效率。這里我們庫存的設計流程如下:
1. 首先我們將庫存分為 數據庫庫存 和 銷售庫存。 數據庫庫存就是存儲到數據庫的商品庫存值,銷售庫存就是用戶下單,頁面所在的庫存值。
2. 后臺管理上架商品時,會設置一個初始庫存,我們將初始庫存存儲到數據庫庫存 和 銷售庫存 。
3.當用戶下單時,不是直接扣減的數據庫庫存,而是通過redis的 decrement 方法,對銷售庫存進行扣減。但是redis的扣減操作這里還不是一個原子性操作,需要先從redis查出庫存,然后進行decrment操作。這兩步操作我們用reddsion的分布式鎖來控制原子性,同時,我們將加鎖的維度控制到了商品id。這樣大大提高了并發效率。
4. 庫存扣減后,我們又通過redis消費隊列,實現了對數據庫庫存的同步。這樣保持了redis庫存和數據庫庫存的一致性。
5. 后臺我們設計的是對商品只能加加庫存,和減少庫存的操作,而不是直接修改庫存值。如果你直接修改庫存值,就有可能會導致庫存數據不一致,難以跟蹤。
6. 我們還設計了庫存的扣減,新增日志,方便對庫存進行跟蹤管理。
庫存扣減的部分代碼:
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
/*** 扣減庫存(使用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();}}}
用戶協同過濾算法的設計
協同過濾(Collaborative Filtering, CF)是推薦系統中最經典的算法之一,其核心思想是通過用戶的歷史行為數據(如評分、點擊、購買等)發現用戶或物品的相似性,并基于這種相似性進行推薦。協同過濾分為兩大類:基于用戶的協同過濾和基于物品的協同過濾。 算法的步驟 1. 獲取所有用戶行為數據,構建用戶-物品評分矩陣。 2. 目標用戶與其它用戶的相似度計算: 將用戶對商品的評分視為向量,計算余弦相似度。 3. 選取與目標用戶相似度最高的 k 個用戶作為鄰居 。 4. 通過鄰居用戶的評分進行加權平均預測(權重為用戶相似度)。 5. 將預測評分按降序排序,選擇評分最高的N個物品作為推薦結果。 舉例說明
用戶評分矩陣的構建
需要借助Array2DRowRealMatrix算法工具,Array2DRowRealMatrix
是 Apache Commons Math 庫中的一個類,用于表示二維實數矩陣,并提供矩陣運算功能。
maven依賴如下:
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-math3</artifactId><version>3.6.1</version></dependency>
構建評分矩陣的代碼:
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
// 獲取所有用戶的行為數據,用于構建用戶-物品評分矩陣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());}}
余弦相似度計算
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
/*** 計算兩個向量的余弦相似度* 余弦相似度用于衡量兩個用戶的評分模式的相似程度* @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個相似的用戶作為鄰居
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
// 計算目標用戶與其他用戶的相似度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());
最后是計算加權平均,當中還需要進行 歸一化處理, 來避免了因用戶群體整體相似度偏高/偏低導致的預測偏差,使得推薦結果更貼近用戶的真實偏好。 整體代碼較長,我就不貼了。 商品系統的設計
鮮花商品的相關表結構如下:
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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=67 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='商品';CREATE TABLE `product_spu` (`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',`product_id` int NOT NULL COMMENT '商品id',`spu_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '規格的key名稱,比如尺碼',`spu_value` varchar(100) CHARACTER SET utf8mb4 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=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC 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=315 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='商品圖片';
商品還有一個動態的字典屬性
動態字典屬性表設計
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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='商品屬性與字典關聯';
訂單表設計如下:
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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='訂單';
運費的設計
首先需要有一個模板表 , 上架商品時, 直接選中到這個運費模板
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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='運費模板表';
每個模板有著自己的運費規則, 規則里面重要的就是城市信息。每個城市的運費都不一樣。
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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='運費規則表';
最后,有一個城市表 , 記錄著中國的省市區數據
代碼語言:javascript
代碼運行次數:0
運行
AI代碼解釋
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 ,否則代碼運行會出錯。新建一個wxhadluo-flowers 數據庫, 然后執行 “wxhadluo-flowers.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 可以自己注冊用戶。