
簡介本資源是一套完整的基于SpringBoot的手工藝品銷售系統畢業設計項目面向計算機專業本科生及Java全棧初學者解決課程設計選題難、環境搭建復雜、前后端聯調無參考等實際痛點。壓縮包共830個文件含133個Java后端核心代碼、66個Vue前端組件、157個JS交互邏輯、162個SVG圖標資源、79個GIF動效素材及1個SQL數據庫腳本覆蓋用戶管理、商品發布、購物車、訂單支付與評論模塊的全鏈路實現包體大小34.18MB。所有代碼經完整測試可直接運行并配套詳細部署文檔與bat一鍵啟動腳本如run.bat、install.bat顯著降低環境配置門檻。學習者可快速掌握SpringBootVueMySQL前后端分離開發范式理解RESTful接口設計、JWT鑒權、MyBatis動態SQL等關鍵技術點同時獲得可直接用于答辯的源碼與論文雙交付成果。1. 項目概述從零到一構建一個手工藝品電商平臺最近幾年身邊做獨立設計、手工創作的朋友越來越多從陶藝、皮具到編織、木工大家手里都有不少好作品。但一個普遍的問題是很多人精于手藝卻苦于沒有一個合適的線上渠道去展示和銷售自己的作品。要么是依賴大型電商平臺被淹沒在海量商品中要么是自己用社交媒體零散發布管理起來非常麻煩。正好我之前用SpringBoot做過幾個電商類的項目這次就結合“手工藝品銷售”這個垂直領域從頭到尾梳理一下如何開發一個專屬的銷售系統。這個系統不僅僅是商品的上下架它更需要體現手工藝品的獨特性和創作者的故事同時兼顧后臺管理的便捷性。如果你是一名Java開發者或者是一名計算機專業的學生正在尋找畢業設計的課題這篇內容或許能給你提供一個完整的、可落地的思路和實現參考。簡單來說我們要做的是一個B2C模式的電商系統但它的內核更偏向于“創作者平臺”。前端用戶可以看到精美的商品詳情、了解手工藝人的故事、進行購買后端管理員和手工藝人賣家可以管理商品、訂單、用戶。技術棧上我們選擇SpringBoot作為后端框架這幾乎是目前Java領域快速開發Web應用的首選它簡化了配置讓我們能更專注于業務邏輯。數據庫用MySQL前端可以考慮Thymeleaf模板引擎快速搭建或者分離前后端用Vue.js。接下來我會詳細拆解整個系統的設計思路、技術選型背后的考量、核心功能的實現細節以及開發過程中那些容易踩坑的地方。2. 系統整體設計與架構選型2.1 為什么是SpringBoot在開始敲代碼之前我們先聊聊為什么選SpringBoot。市面上框架很多從傳統的SSH/SSM到新興的微服務框架。對于“手工藝品銷售系統”這樣一個典型的單體Web應用SpringBoot的優勢非常明顯。首先就是快速啟動它提供了大量的“Starter”依賴比如spring-boot-starter-web用于Web開發spring-boot-starter-data-jpa或mybatis-spring-boot-starter用于數據持久化幾乎不需要你手動配置XML一個pom.xml文件就能搞定大部分依賴。其次它內嵌了Tomcat這意味著你的應用可以打包成一個獨立的Jar文件直接通過java -jar命令運行部署變得極其簡單非常適合作為畢業設計演示或個人項目部署。最后SpringBoot擁有豐富的社區生態和詳盡的文檔你在開發中遇到的絕大多數問題都能在社區找到解決方案這對于學習和解決問題至關重要。注意雖然SpringBoot簡化了配置但并不意味著不需要理解Spring框架的核心如IoC、AOP。建議在開始前至少對Spring MVC和一種ORM框架如JPA或MyBatis有基本了解否則你會感覺雖然項目跑起來了但完全不知道發生了什么。2.2 核心業務模塊拆解一個手工藝品銷售系統其核心業務模塊可以抽象為以下幾個部分這構成了我們數據庫設計和代碼結構的基礎用戶模塊包括買家普通用戶和賣家手工藝人。這里需要一個統一的用戶表通過“角色”字段或單獨的關系表來區分。買家關注收藏、購物車、訂單歷史賣家則需要商品管理、訂單處理、數據統計等功能。商品模塊這是系統的核心。手工藝品商品信息比標準品復雜得多。除了常規的名稱、價格、庫存、主圖還必須包含詳情圖文描述需要支持富文本以便賣家講述作品背后的故事、工藝、材質等。多圖展示至少需要輪播圖展示作品不同角度。定制化屬性例如對于皮具可能有“皮革類型”、“顏色”、“尺寸”等可選屬性。這涉及到SKU庫存量單位的管理是設計中的難點。分類與標簽分類可以是樹形結構如手工藝 - 皮具 - 錢包標簽則更靈活如復古、手工染色、禮物。訂單模塊記錄訂單狀態待付款、待發貨、已發貨、已完成、已取消、收貨地址、支付信息通常對接第三方支付系統內只記錄支付狀態和流水號、物流信息。購物車模塊臨時存放用戶意向商品支持增刪改查。后臺管理模塊提供儀表盤集中管理用戶、商品、訂單、分類、輪播圖等所有內容。2.3 技術棧與工具清單基于以上分析我建議的技術棧如下后端核心框架SpringBoot 2.x (建議2.7.x穩定且資料多)持久層Spring Data JPA (開發速度快) 或 MyBatis-Plus (SQL更靈活)數據庫MySQL 8.0模板引擎/前端Thymeleaf (適合快速出原型) 或 完全前后端分離Vue.js RESTful API安全框架Spring Security (用于登錄認證和權限控制)工具庫Lombok (簡化Bean代碼)Hutool (國產工具集好用)前端如果不用前后端分離Bootstrap 5 jQuery Thymeleaf 碎片化布局。如果前后端分離Vue 3 Element Plus 或 Ant Design Vue。開發與部署IDEIntelliJ IDEA (對SpringBoot支持最好)構建工具Maven版本控制Git部署打包成Jar可以在Windows/Linux上直接運行。對于畢業設計用本機或學生服務器即可。3. 數據庫設計與核心表結構解析數據庫設計是系統的基石設計得好后續開發事半功倍。這里我給出幾個核心表的設計思路和避坑點。3.1 用戶表 (sys_user)這張表需要同時容納買家和賣家信息。一個常見的做法是通過一個user_type字段來區分例如0-管理員1-賣家2-買家。但更靈活的方式是使用角色關聯表實現RBAC基于角色的訪問控制模型。CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主鍵, username varchar(50) NOT NULL COMMENT 用戶名唯一, password varchar(255) NOT NULL COMMENT 加密后的密碼, nickname varchar(50) DEFAULT NULL COMMENT 昵稱, avatar varchar(500) DEFAULT NULL COMMENT 頭像URL, email varchar(100) DEFAULT NULL COMMENT 郵箱, phone varchar(20) DEFAULT NULL COMMENT 手機號, status tinyint(4) DEFAULT 1 COMMENT 狀態0-禁用1-正常, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 創建時間, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新時間, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用戶表;實操心得密碼存儲絕對不要明文存儲使用Spring Security的BCryptPasswordEncoder進行哈希加密。即使數據庫泄露攻擊者也無法直接獲得用戶密碼。字段長度avatar頭像字段要預留足夠長度因為可能存儲的是完整的OSS對象存儲URL地址。字符集務必使用utf8mb4字符集以支持存儲Emoji表情等特殊字符特別是昵稱字段。3.2 商品與SKU設計最大的挑戰這是系統最復雜的部分。普通商品可能只有一個價格和庫存但手工藝品常有定制選項。例如一個手工皮包可以選擇“棕色牛皮”或“黑色植鞣革”選擇“大號”或“小號”。每種組合對應不同的價格和庫存。這就需要引入**SPU標準產品單元和SKU庫存保有單位**的概念。商品SPU表 (product_spu)描述一個商品的基本、通用信息。CREATE TABLE product_spu ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 商品標題, sub_title varchar(500) DEFAULT NULL COMMENT 副標題/賣點, category_id bigint(20) NOT NULL COMMENT 分類ID, seller_id bigint(20) NOT NULL COMMENT 賣家用戶ID, main_pic varchar(500) NOT NULL COMMENT 主圖, slide_pics text COMMENT 輪播圖JSON數組格式, detail_html text COMMENT 商品詳情HTML富文本, status tinyint(4) DEFAULT 0 COMMENT 狀態0-待審核1-上架2-下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品SPU表;商品規格/屬性表 (product_spec)定義可以選擇的維度如“顏色”、“尺寸”。CREATE TABLE product_spec ( id bigint(20) NOT NULL AUTO_INCREMENT, spu_id bigint(20) NOT NULL COMMENT 所屬SPU ID, spec_name varchar(50) NOT NULL COMMENT 規格名如顏色, spec_values varchar(500) NOT NULL COMMENT 規格值列表JSON數組如[棕色,黑色], PRIMARY KEY (id), KEY idx_spu_id (spu_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品規格表;商品SKU表 (product_sku)代表一個具體的、可售賣的庫存單元是規格組合的結果。CREATE TABLE product_sku ( id bigint(20) NOT NULL AUTO_INCREMENT, spu_id bigint(20) NOT NULL COMMENT 所屬SPU ID, sku_code varchar(100) DEFAULT NULL COMMENT SKU編碼可自定義, spec_combination varchar(500) DEFAULT NULL COMMENT 規格組合JSON如{顏色:棕色,尺寸:大號}, price decimal(10,2) NOT NULL COMMENT 價格, stock int(11) NOT NULL DEFAULT 0 COMMENT 庫存, pic varchar(500) DEFAULT NULL COMMENT SKU特有圖片, status tinyint(4) DEFAULT 1 COMMENT 狀態, PRIMARY KEY (id), UNIQUE KEY uk_spu_specs (spu_id, spec_combination(100)), -- 防止重復組合 KEY idx_spu_id (spu_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品SKU表;核心難點與解決方案SKU生成與庫存扣減前端選擇不同規格時需要動態查詢對應的SKU ID。在用戶下單時必須基于SKU ID來扣減庫存。這里必須使用樂觀鎖或悲觀鎖來防止超賣。例如在更新庫存的SQL中加上條件where id? and stock ?。規格組合的存儲spec_combination字段存儲JSON是一個靈活的選擇方便前端解析和展示。但要注意如果需要根據某個規格值如所有“棕色”的商品進行篩選JSON字段的查詢效率很低。如果篩選是核心需求可能需要更復雜的、標準化的表結構如關聯表。圖片管理slide_pics字段存儲圖片URL的JSON數組。圖片本身建議上傳到云存儲如阿里云OSS、騰訊云COS數據庫只存路徑。這能極大減輕服務器壓力。3.3 訂單與購物車表訂單表需要關聯用戶、地址并且一個訂單可能包含多個商品所以通常拆分為訂單主表 (order_master)和訂單詳情表 (order_item)。購物車表 (cart_item) 相對簡單記錄用戶、商品SKU、數量即可。需要注意的是購物車商品價格應該實時查詢商品SKU表的最新價格而不是在加入購物車時固定下來避免商品調價后產生糾紛。4. SpringBoot后端核心功能實現詳解4.1 項目初始化與配置使用IntelliJ IDEA的Spring Initializr或訪問 start.spring.io 快速生成項目。依賴選擇Spring Web,Spring Data JPA,MySQL Driver,Lombok,Thymeleaf如果做前后端不分離。關鍵的application.yml配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/handicraft_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 開發環境可以用update生產環境務必設為none或validate并通過SQL腳本管理表結構 show-sql: true # 開發時顯示SQL方便調試 properties: hibernate: format_sql: true # 格式化打印的SQL servlet: multipart: max-file-size: 10MB # 文件上傳大小限制 max-request-size: 20MB # 自定義配置 myapp: upload: path: /tmp/upload/ # 本地存儲路徑生產環境請改為絕對路徑重要提示ddl-auto: update在開發初期很方便但存在數據丟失風險且對字段重命名等操作支持不好。正式項目一定要使用Flyway或Liquibase這樣的數據庫版本遷移工具。4.2 用戶認證與權限控制Spring Security手工藝品系統涉及買家、賣家、管理員三種角色權限控制必不可少。Spring Security配置是初學者的一大難點。核心配置類示例Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Autowired private UserDetailsService userDetailsService; Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/, /home, /product/**, /login, /register, /css/**, /js/**).permitAll() // 公開訪問路徑 .antMatchers(/seller/**).hasRole(SELLER) // 賣家后臺路徑 .antMatchers(/admin/**).hasRole(ADMIN) // 管理員后臺路徑 .anyRequest().authenticated() // 其他所有路徑都需要認證 .and() .formLogin() .loginPage(/login) // 自定義登錄頁 .defaultSuccessUrl(/) // 登錄成功跳轉 .permitAll() .and() .logout() .logoutSuccessUrl(/) .permitAll() .and() .rememberMe() // 記住我功能 .and() .csrf().disable(); // 開發階段可關閉CSRF上線后需要開啟并處理 } Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }實操心得角色前綴Spring Security默認在角色前加ROLE_。所以數據庫中存儲的角色名如果是SELLER在配置時寫hasRole(SELLER)框架實際會去匹配ROLE_SELLER。你也可以通過配置修改這一行為。CSRF防護在前后端不分離且使用Thymeleaf時Thymeleaf會自動在表單中添加CSRF Token。如果前后端分離如Vue需要將Token放在請求頭中傳遞或者根據情況謹慎地禁用對安全性要求不高的API。密碼加密務必在用戶注冊時使用BCryptPasswordEncoder的encode()方法對密碼進行加密后再存入數據庫。在登錄校驗時Spring Security會自動調用matches()方法進行比對。4.3 商品管理的后端邏輯實現以“賣家添加商品”這個復雜流程為例展示Controller-Service-Repository的分層架構。1. Repository層 (數據訪問)Repository public interface ProductSpuRepository extends JpaRepositoryProductSpu, Long { // JPA會自動實現基本CRUD方法 // 自定義查詢查找某個賣家下所有上架的商品 ListProductSpu findBySellerIdAndStatusOrderByCreateTimeDesc(Long sellerId, Integer status); }2. Service層 (業務邏輯)這里是核心包含添加SPU、SKU以及處理規格組合的邏輯。Service Transactional public class ProductService { Autowired private ProductSpuRepository spuRepository; Autowired private ProductSkuRepository skuRepository; Autowired private ProductSpecRepository specRepository; public void addProduct(ProductSpu spu, ListProductSpec specs, ListProductSku skus) { // 1. 保存SPU基本商品信息 ProductSpu savedSpu spuRepository.save(spu); // 2. 保存規格定義 specs.forEach(spec - spec.setSpuId(savedSpu.getId())); specRepository.saveAll(specs); // 3. 保存SKU列表 skus.forEach(sku - { sku.setSpuId(savedSpu.getId()); // 這里可以生成唯一的SKU編碼例如SPUID 規格組合的哈希值 if (StringUtils.isEmpty(sku.getSkuCode())) { sku.setSkuCode(generateSkuCode(savedSpu, sku.getSpecCombination())); } }); skuRepository.saveAll(skus); } private String generateSkuCode(ProductSpu spu, String specCombination) { // 簡單的生成邏輯示例 try { String specHash DigestUtils.md5DigestAsHex(specCombination.getBytes()).substring(0, 6); return SPU spu.getId() - specHash.toUpperCase(); } catch (Exception e) { return SPU spu.getId() - System.currentTimeMillis(); } } }關鍵點整個添加商品的方法需要用Transactional注解標記確保SPU、Spec、SKU的保存操作在一個數據庫事務中要么全部成功要么全部回滾保證數據一致性。3. Controller層 (接收請求)Controller RequestMapping(/seller/product) public class SellerProductController { Autowired private ProductService productService; PostMapping(/add) public String addProduct(Valid ProductForm form, BindingResult result, HttpSession session) { if (result.hasErrors()) { return seller/product_add; // 返回表單頁并顯示錯誤 } // 從session中獲取當前登錄的賣家ID Long sellerId (Long) session.getAttribute(userId); // 將表單對象form轉換為實體對象spu, specs, skus // ... 轉換邏輯 productService.addProduct(spu, specs, skus); return redirect:/seller/product/list; // 添加成功跳轉到商品列表 } }表單驗證ProductForm是一個DTO數據傳輸對象可以使用JSR-303注解如NotBlank,Min進行校驗配合Valid注解在Controller中自動觸發校驗。4.4 訂單創建與庫存扣減的并發處理這是電商系統的核心難點。在高并發下多個用戶同時購買最后一個庫存的商品會導致超賣。下面是一個使用悲觀鎖在數據庫層面加鎖的簡單實現方案。Service層方法Service public class OrderService { Autowired private ProductSkuRepository skuRepository; Autowired private OrderRepository orderRepository; Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, ListOrderItemDTO itemDTOs, Address address) { // 1. 計算總價驗證商品狀態略 BigDecimal totalAmount BigDecimal.ZERO; ListOrderItem orderItems new ArrayList(); for (OrderItemDTO itemDTO : itemDTOs) { // 關鍵步驟通過悲觀鎖查詢SKU (使用JPA的Lock注解或原生SQL) ProductSku sku skuRepository.findSkuForUpdate(itemDTO.getSkuId()); if (sku null || sku.getStatus() ! 1) { throw new RuntimeException(商品[ sku.getId() ]已下架或不存在); } if (sku.getStock() itemDTO.getQuantity()) { throw new RuntimeException(商品[ sku.getId() ]庫存不足); } // 2. 扣減庫存 int updatedRows skuRepository.deductStock(itemDTO.getSkuId(), itemDTO.getQuantity()); if (updatedRows 0) { // 扣減失敗可能已被其他請求修改拋出異常觸發事務回滾 throw new RuntimeException(商品庫存并發更新失敗請重試); } // 3. 構建訂單項 OrderItem orderItem new OrderItem(); orderItem.setSkuId(sku.getId()); orderItem.setSkuSnapshot(JSON.toJSONString(sku)); // 保存快照防止后續商品信息變更 orderItem.setQuantity(itemDTO.getQuantity()); orderItem.setPrice(sku.getPrice()); orderItems.add(orderItem); totalAmount totalAmount.add(sku.getPrice().multiply(new BigDecimal(itemDTO.getQuantity()))); } // 4. 創建訂單主表 Order order new Order(); order.setUserId(userId); order.setOrderNo(generateOrderNo()); // 生成唯一訂單號 order.setTotalAmount(totalAmount); order.setStatus(OrderStatusEnum.WAIT_PAY.getCode()); // ... 設置其他字段 Order savedOrder orderRepository.save(order); // 5. 保存訂單項關聯訂單ID orderItems.forEach(item - item.setOrderId(savedOrder.getId())); orderItemRepository.saveAll(orderItems); // 6. 清空用戶購物車中對應的商品略 return savedOrder; } }Repository層扣減庫存方法使用原生SQL確保原子性Repository public interface ProductSkuRepository extends JpaRepositoryProductSku, Long { // 悲觀鎖查詢 Query(value SELECT * FROM product_sku WHERE id :id FOR UPDATE, nativeQuery true) ProductSku findSkuForUpdate(Param(id) Long id); // 扣減庫存 Modifying Query(value UPDATE product_sku SET stock stock - :quantity WHERE id :id AND stock :quantity, nativeQuery true) int deductStock(Param(id) Long id, Param(quantity) Integer quantity); }方案對比與選擇悲觀鎖 (FOR UPDATE)在查詢時直接鎖定記錄其他事務必須等待。簡單粗暴能保證強一致性但在高并發下容易導致大量請求等待性能有瓶頸。適用于庫存量極少、競爭激烈的場景。樂觀鎖在表中增加一個version字段更新時帶上版本號條件update ... set stock?, versionversion1 where id? and version?。如果更新返回影響行數為0說明版本沖突讓用戶重試。性能更好但用戶體驗稍差可能需要前端提示“請重試”。Redis分布式鎖在扣減前先用Redis鎖住這個SKU ID防止其他進程同時操作。適用于分布式部署環境。Redis緩存庫存異步同步到數據庫將庫存扣減操作放在Redis中原子操作decr快速返回然后再異步將結果持久化到MySQL。這是應對極高并發秒殺場景的常用方案但架構復雜。對于畢業設計或中小型項目樂觀鎖是一個在性能和一致性之間取得較好平衡的選擇。5. 前端頁面與用戶體驗關鍵點5.1 商品詳情頁的動態SKU選擇前端需要根據用戶選擇的規格動態匹配出對應的SKU并更新價格、庫存和圖片。這是一個經典的交互。實現思路頁面加載時從后端獲取該SPU下的所有ProductSpec規格定義和ProductSku列表。前端使用JavaScript構建一個規格值到SKU的映射關系。可以將所有SKU的spec_combinationJSON字符串解析成對象然后建立一個查找字典。當用戶點擊選擇規格如“顏色棕色”時遍歷當前已選的所有規格組合去映射字典里查找是否存在對應的SKU。如果找到唯一SKU則顯示該SKU的價格、庫存和特有圖片如果找不到則顯示“此組合暫無貨”或禁用購買按鈕。示例代碼片段Vue.js思路// 假設 skuList 是從后端獲取的所有SKU數組 const skuMap {}; skuList.forEach(sku { const key JSON.stringify(sku.specCombination); // 將規格組合對象轉為字符串作為鍵 skuMap[key] sku; }); // 當用戶選擇規格時 const selectedSpecs { 顏色: 棕色, 尺寸: 大號 }; const searchKey JSON.stringify(selectedSpecs); const matchedSku skuMap[searchKey]; if (matchedSku) { this.currentPrice matchedSku.price; this.currentStock matchedSku.stock; this.currentSkuId matchedSku.id; } else { this.currentPrice --; this.currentStock 0; this.currentSkuId null; }5.2 圖片上傳與預覽功能手工藝品展示極度依賴圖片。我們需要實現多圖上傳、預覽、排序和刪除。后端ControllerPostMapping(/upload/image) ResponseBody public Result uploadImage(RequestParam(file) MultipartFile file, HttpServletRequest request) { if (file.isEmpty()) { return Result.error(請選擇文件); } // 1. 生成唯一文件名防止覆蓋 String originalFilename file.getOriginalFilename(); String fileExt originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString() fileExt; // 2. 確定存儲路徑 (配置文件中讀取) File dest new File(uploadPath newFileName); // 確保目錄存在 dest.getParentFile().mkdirs(); // 3. 保存文件 try { file.transferTo(dest); // 4. 返回訪問URL (如果是本地存儲可能需要配置靜態資源映射) String fileUrl /upload/ newFileName; // 示例URL return Result.success(上傳成功, fileUrl); } catch (IOException e) { e.printStackTrace(); return Result.error(上傳失敗); } }前端實現可以使用現成的組件如Element Plus的el-upload或Bootstrap-FileInput。關鍵是要支持多選、拖拽排序、預覽和刪除已選圖片的交互。上傳成功后將返回的URL存儲在一個數組中最終在提交商品表單時將這個數組以JSON格式傳給后端。5.3 響應式布局與移動端適配如今大部分流量來自手機所以前端頁面必須做好響應式設計。使用Bootstrap 5的柵格系統可以輕松實現。核心原則是任何容器、行和列都使用Bootstrap的類而不是固定像素寬度。例如商品列表在PC上一行顯示4個在平板上顯示3個在手機上顯示2個div classrow div classcol-lg-3 col-md-4 col-sm-6 mb-4 v-forproduct in productList :keyproduct.id !-- 商品卡片 -- div classcard h-100 img :srcproduct.mainPic classcard-img-top alt... div classcard-body h5 classcard-title{{ product.title }}/h5 p classcard-text text-danger{{ product.price }}/p a :href/product/detail/ product.id classbtn btn-primary查看詳情/a /div /div /div /div6. 系統部署與上線注意事項開發完成后如何讓老師和同學訪問你的系統本地運行和部署到服務器是兩回事。6.1 打包與運行打包在項目根目錄執行Maven命令mvn clean package -DskipTests。成功后會在target目錄下生成一個*.jar文件。運行將Jar包上傳到服務器或就在本機在命令行執行java -jar your-project-name.jar。SpringBoot會使用內嵌的Tomcat啟動服務。后臺運行在Linux服務器上可以使用nohup命令讓進程在后臺運行nohup java -jar your-project-name.jar app.log 21 。更規范的做法是使用systemd創建服務。6.2 生產環境配置數據庫不要使用本地測試數據庫。在服務器上安裝MySQL創建專用數據庫用戶和密碼并在application-prod.yml中配置。文件存儲切勿將上傳的文件保存在項目Jar包內部或相對路徑因為Jar包是只讀的且每次更新部署會被覆蓋。必須使用絕對路徑并確保運行Jar包的用戶對該目錄有讀寫權限。最佳實踐是使用云存儲服務。日志配置logback-spring.xml將日志按級別和日期滾動輸出到文件方便排查問題。端口與防火墻確保服務器安全組或防火墻開放了你的應用端口如8080。通常我們會用Nginx做反向代理將80/443端口請求轉發到8080并配置域名。6.3 使用Nginx進行反向代理在服務器上安裝Nginx修改配置文件如/etc/nginx/conf.d/your-site.confserver { listen 80; server_name your-domain.com; # 你的域名或服務器IP location / { proxy_pass http://127.0.0.1:8080; # 轉發到SpringBoot應用 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 靜態資源如圖片直接由Nginx處理效率更高 location /upload/ { alias /path/to/your/upload/dir/; # 指向你文件存儲的絕對路徑 expires 30d; # 設置瀏覽器緩存 } }配置完成后重啟Nginxsudo systemctl restart nginx。現在通過訪問服務器IP或域名就能看到你的手工藝品銷售系統了。7. 開發常見問題與調試技巧實錄在開發這個系統的過程中我踩過不少坑這里總結幾個最具代表性的問題和解決方法。7.1 問題一JPA 更新實體時字段被意外設置為NULL現象你從數據庫查詢出一個ProductSpu對象修改了它的title字段然后調用repository.save()結果發現其他未修改的字段如createTime在數據庫里變成了NULL。原因JPA的save()方法在執行更新時默認行為是使用你提供的實體對象的所有字段去覆蓋數據庫記錄。如果你的實體對象某些字段為null例如從JSON反序列化過來的DTO轉換而成只包含了部分字段那么這些字段在數據庫里就會被更新為null。解決方案使用查詢-修改-保存流程先通過findById()查詢出完整的持久化實體Managed Entity然后只修改需要改的字段再調用save()。因為此時實體處于被管理狀態Hibernate只會更新發生變化的字段臟檢查。ProductSpu spu productSpuRepository.findById(id).orElseThrow(...); spu.setTitle(newTitle); // 只修改需要的字段 // spu.setSubTitle(null); // 千萬不要這樣做 productSpuRepository.save(spu); // 實際上這行可以不寫事務提交時會自動flush使用動態更新注解在實體類上添加DynamicUpdate注解。這樣Hibernate會在生成Update SQL時只包含發生變化的字段。但注意這需要Hibernate進行額外的臟檢查有輕微性能開銷。Entity DynamicUpdate Table(name product_spu) public class ProductSpu { ... }自定義Update方法在Repository中定義Modifying和Query注解的更新方法直接編寫Update SQL精確控制更新的字段。7.2 問題二Thymeleaf模板中Spring Security的權限判斷不生效現象在HTML中使用Thymeleaf的sec:authorize屬性如div sec:authorizehasRole(ADMIN)但內容始終不顯示或一直顯示。原因沒有正確引入Thymeleaf與Spring Security的集成命名空間或者依賴缺失。解決方案確保pom.xml依賴正確除了spring-boot-starter-thymeleaf還需要添加thymeleaf-extras-springsecurity5依賴。dependency groupIdorg.thymeleaf.extras/groupId artifactIdthymeleaf-extras-springsecurity5/artifactId /dependency在HTML模板中聲明命名空間在html標簽中添加。html xmlns:thhttp://www.thymeleaf.org xmlns:sechttp://www.thymeleaf.org/extras/spring-security檢查Security配置確保你的角色名正確并且用戶確實被授予了該角色。可以在Controller中打印SecurityContextHolder.getContext().getAuthentication()來調試。7.3 問題三事務Transactional不回滾現象在Service方法上標注了Transactional方法內部拋出了異常但數據庫操作并沒有回滾。原因排查異常類型默認情況下Transactional只在拋出運行時異常RuntimeException和Error時回滾。如果你拋出的是受檢異常Exception事務不會回滾。解決在注解中指定回滾的異常類型Transactional(rollbackFor Exception.class)。方法訪問權限Transactional注解在代理對象Spring AOP生成上生效。如果方法不是public代理可能無法正常工作。解決確保事務方法是public的。自調用問題在同一個類中一個非事務方法A調用了另一個事務方法BB的事務不會生效。因為這是通過this對象直接調用而不是通過Spring代理對象調用。解決將事務方法B放到另一個Service中或者通過AopContext.currentProxy()獲取代理對象再調用不推薦耦合高。數據庫引擎確保MySQL表使用的引擎是InnoDB因為MyISAM引擎不支持事務。7.4 問題四前端頁面樣式和JS文件404現象SpringBoot應用啟動后HTML能訪問但CSS、JS、圖片等靜態資源加載失敗404。原因SpringBoot對靜態資源有默認的映射規則你的文件可能沒放對位置或者被攔截了。解決方案放置位置將靜態資源/static/,/public/,/resources/,/META-INF/resources/放在src/main/resources目錄下對應的文件夾里。例如src/main/resources/static/css/style.css在HTML中引用路徑為/css/style.css。自定義路徑如果想放在其他目錄如/web-resources/可以在application.yml中配置spring: mvc: static-path-pattern: /resources/** web: resources: static-locations: classpath:/web-resources/這樣src/main/resources/web-resources/css/style.css對應的訪問路徑就是/resources/css/style.css。檢查Security配置確保Spring Security的配置中放行了靜態資源的路徑如.antMatchers(/css/**, /js/**, /images/**).permitAll()。7.5 問題五訂單號等唯一標識的生成現象直接使用數據庫自增ID或時間戳作為訂單號在高并發或分布式環境下可能重復或者看起來不夠專業。解決方案生成一個全局唯一、趨勢遞增且難以猜測的訂單號。public class OrderNoGenerator { /** * 生成訂單號時間戳精確到毫秒 隨機數或機器ID 簡短哈希 */ public static String generate() { // 1. 時間部分yyyyMMddHHmmssSSS SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmssSSS); String timePart sdf.format(new Date()); // 2. 隨機數部分3位模擬機器或進程標識 int random (int) ((Math.random() * 9 1) * 100); // 100-999 // 3. 組合并可能加一個校驗位簡單示例 String baseNo timePart random; // 可以再加一個簡單的校驗碼如取后兩位數字和的個位數 int sum 0; for (char c : baseNo.toCharArray()) { sum Character.getNumericValue(c); } String checkCode String.valueOf(sum % 10); return baseNo checkCode; } }更嚴謹的方案是使用雪花算法Snowflake可以生成分布式環境下唯一的、趨勢遞增的ID。有許多開源實現如Hutool工具包里的IdUtil.createSnowflake。開發這樣一個系統從需求分析、技術選型、數據庫設計、編碼實現到最終部署是一個完整的軟件工程實踐。它不僅僅是對SpringBoot等技術的應用更是對業務邏輯抽象、系統架構設計、異常處理和用戶體驗思考的綜合考驗。過程中遇到的每一個報錯和踩過的每一個坑都是寶貴的經驗。希望這篇超詳細的拆解能幫你理清思路少走彎路最終做出一個讓自己滿意、也能打動答辯老師的手工藝品銷售系統。記住在開發中多寫注釋多寫日志善用調試工具遇到問題先自己思考搜索再請教他人你的成長會快得多。本文還有配套的精品資源點擊獲取