简介本资源是一套完整的基于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等技术的应用更是对业务逻辑抽象、系统架构设计、异常处理和用户体验思考的综合考验。过程中遇到的每一个报错和踩过的每一个坑都是宝贵的经验。希望这篇超详细的拆解能帮你理清思路少走弯路最终做出一个让自己满意、也能打动答辩老师的手工艺品销售系统。记住在开发中多写注释多写日志善用调试工具遇到问题先自己思考搜索再请教他人你的成长会快得多。本文还有配套的精品资源点击获取