多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java课程设计实战:网上花店系统从数据库到部署全解析

Java课程设计实战:网上花店系统从数据库到部署全解析 简介基于Java的网上花店系统毕业设计资源涵盖从数据库设计到前端展示、后端业务处理的全流程实现适合Java Web初学者、毕业设计学生及需要快速上手项目实践的开发者。资源共5个文件压缩包约358.69MB包括两份源码压缩包、两段部署操作视频及一份SQL数据库脚本部署视频带环境配置、数据导入与项目运行演示便于对照源代码理解Servlet、JSP、JDBC及MVC模式的实际运用。项目包含商品管理、用户订单等典型业务模块和完整数据库表结构能从零启动完成一个可运行的花店系统有效节省环境搭建与排错时间。已有273人学习下载对于想通过完整案例提升Java Web能力或准备毕业设计答辩的读者是一份可直接参考、二次开发的实用资料。1. 网上花店系统为什么说它是Java课程设计里最“能打”的选题每到课程设计或者毕设开题选题表里总有几个万年常青的名字学生管理系统、图书管理系统再就是网上花店系统。选花店的人多不是因为它简单而是因为它把电商网站的骨架占齐了——商品展示、购物车、下单、订单管理、后台维护再加花店特有的库存和配送状态一个Java Web开发者入门要碰的知识点基本全覆盖。交付物通常是“源代码部署视频数据库”三件套的zip包。我的建议是先别急着解压双击把三件套每样东西该在哪儿用、容易在哪儿翻车搞清楚再动手。这套拆解逻辑不只适用于花店换其他java课程设计项目照样能套。2. 数据库先行花店系统的表结构设计与订单状态建模花店系统里数据库是绝对核心。前端页面可以朴素一点后端代码可以糙一点但表结构一乱后面全乱。打开zip里的SQL脚本第一件事不是看INSERT语句而是看CREATE TABLE。一个合格的网上花店系统表数量通常在六到八张之间下面按最常见的六张表拆开讲。2.1 六张核心表从用户到订单项的关联关系第一类是用户侧的表user存账号、密码、手机号、收货地址第二类是商品侧的花品表和分类表flower存价格库存图片描述category存分类名和排序第三类是交易侧cart_item存购物车、orders存订单主表、order_item存订单明细。六张表的关系一句话就能说清user下ordersorders里挂order_itemorder_item指向flowerflower挂在category下。花品表和交易表的建表脚本通常是这样的字段名可能随源码版本略有出入-- 花品表花店系统的核心商品表 CREATE TABLE flower ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 花品ID, name VARCHAR(50) NOT NULL COMMENT 花名, category_id INT NOT NULL COMMENT 所属分类关联category表, price DECIMAL(10,2) NOT NULL COMMENT 单价用DECIMAL避免浮点误差, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, image VARCHAR(255) DEFAULT NULL COMMENT 图片路径存相对路径不存图片本身, description TEXT COMMENT 描述, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 花品表; -- 订单表一次下单行为的主记录 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 下单用户, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号展示和查询用, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总额冗余字段, status TINYINT DEFAULT 0 COMMENT 0待付款 1待发货 2配送中 3已完成 4已取消, receiver_name VARCHAR(20) NOT NULL, receiver_phone VARCHAR(20) NOT NULL, receiver_address VARCHAR(200) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 订单主表; -- 订单项表记录订单明细一个订单对应多行 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT 所属订单, flower_id INT NOT NULL COMMENT 商品ID, flower_name VARCHAR(50) NOT NULL COMMENT 商品名称快照, price DECIMAL(10,2) NOT NULL COMMENT 成交单价快照, quantity INT NOT NULL COMMENT 购买数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计 ) COMMENT 订单明细表;逻辑说明price字段为什么用DECIMAL而不是float/double这是金额数据类型的基本常识——99乘以2在二进制浮点里算出197.99999999999997是常有的事第五章有完整记录。orders表和order_item表拆开是典型的“一主多明细”结构orders管总账order_item管明细。flower_name和price在明细表里各存一份这叫快照设计后台管理员今天把“红玫瑰”改名叫“真爱玫瑰”昨天的订单打印出来还是应该显示用户下单时的名字和价格而不是跟着商品表变。这是课程设计里最容易被忽略的设计点也是答辩评委爱追问的细节。参数说明DECIMAL(10,2)里10是总位数2是小数位数最大能表示99999999.99花店这种客单价完全够用。订单状态用TINYINT存数字而不是直接存“待付款”这种字符串代码里比较状态只要写if(status 1)就行但可读性差所以建表注释一定要保留答辩时被问到“4是什么状态”翻COMMENT就能答上。2.2 下单流程的数据变化事务与库存扣减下单这个动作在数据库层面至少要改四张表往orders插主记录、往order_item插若干明细、把flower的stock扣掉对应数量、把cart_item里已结算的商品清掉。四步操作要么全成功要么全失败这就是事务。很多源代码里下单方法不加Transactional或者加了但没写rollbackFor结果就是库存扣了订单没生成或者订单生成了购物车没清。最可靠的做法是把四步写进同一个事务方法Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, ListCartItem cartItems, String receiverName, String receiverPhone, String receiverAddress) { // 1. 查商品并锁定行记录防止并发超卖 BigDecimal total BigDecimal.ZERO; for (CartItem item : cartItems) { Flower flower flowerMapper.selectByIdForUpdate(item.getFlowerId()); if (flower.getStock() item.getQuantity()) { throw new BusinessException(库存不足 flower.getName()); } total total.add(flower.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity()))); } // 2. 插入订单主表id由数据库自增回填 Order order new Order(); order.setUserId(userId); order.setOrderNo(generateOrderNo()); order.setTotalAmount(total); order.setStatus(0); // 待付款 order.setReceiverName(receiverName); order.setReceiverPhone(receiverPhone); order.setReceiverAddress(receiverAddress); orderMapper.insert(order); // 3. 插入明细并扣库存 for (CartItem item : cartItems) { OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setFlowerId(item.getFlowerId()); oi.setFlowerName(item.getFlowerName()); oi.setPrice(item.getPrice()); oi.setQuantity(item.getQuantity()); oi.setSubtotal(item.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity()))); orderItemMapper.insert(oi); flowerMapper.decreaseStock(item.getFlowerId(), item.getQuantity()); } // 4. 清空购物车事务提交后成功才算数 cartMapper.clearByUserId(userId); return order; }逻辑说明selectByIdForUpdate这个方法的SQL本质是SELECT ... FOR UPDATE它是悲观锁。两个用户同时买最后一枝花时第二个请求会阻塞在查库存这一步等第一个事务提交后读到扣减完的数字再判断库存从而避免超卖。rollbackFor Exception.class这一行必须写Spring默认只对RuntimeException回滚而课程设计里很多自定义BusinessException继承的是普通Exception不写rollbackFor时事务会静默提交留下一半数据。参数说明下单事务里尽量别做远程调用、别发短信、别写文件这类操作失败不该让订单回滚。正确做法是先提交订单再用异步任务发通知课程设计用Spring的Async注解就够不需要引入消息队列。generateOrderNo的常见写法是“yyyyMMddHHmmss加随机数”但同一秒内两个订单可能撞号稳妥做法是把用户ID后四位拼进去比如202501201430150008既唯一又能从订单号反查用户。注意如果发现源代码里的事务注解只写了Transactional没写rollbackFor建议补上这是答辩时最容易暴露的隐患。2.3 从建库脚本到演示数据初始化数据库的最小动作拿到SQL脚本后最小执行路径是登录MySQL然后导入。很多新手卡在这一步其实命令行两句就够# 如果脚本里没有建库语句先手动建库再导入 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS flower_shop DEFAULT CHARACTER SET utf8mb4; # 导入脚本 mysql -u root -p flower_shop flower_shop.sql # 验证表数量应该和源码里的实体类对应 mysql -u root -p -e USE flower_shop; SHOW TABLES;逻辑说明-p后面不直接跟密码是推荐写法让命令交互式提示输入避免密码留在shell历史里。utf8mb4不是可选项而是必选项utf8在MySQL里最多存3字节emoji和部分生僻字是4字节收货人姓名万一碰上一个生僻字就导入失败。如果SQL脚本本身包含CREATE DATABASE语句第二步可以省略建库直接执行mysql -u root -p flower_shop.sql先看一眼脚本头部就能判断。演示数据是另一个容易被忽略的点。如果脚本里没有INSERT语句打开首页就是一片空白答辩气氛瞬间尴尬。手动补几条演示数据时要覆盖关键状态-- 两个分类、三种花、一个测试用户 INSERT INTO category (id, name, sort) VALUES (1, 玫瑰系, 1), (2, 百合系, 2); INSERT INTO flower (name, category_id, price, stock, image, description) VALUES (红玫瑰, 1, 99.00, 100, /upload/rose_red.jpg, 经典款), (白百合, 2, 69.00, 50, /upload/lily_white.jpg, 清新款), (香槟玫瑰, 1, 129.00, 30, /upload/rose_champagne.jpg, 婚礼常用); -- 密码字段课程设计用明文正式项目必须哈希 INSERT INTO user (username, password, phone, address) VALUES (test, 123456, 13800000000, 北京市海淀区测试路1号);逻辑说明这批数据要刻意做出层次——分类有两个、花品价格有99/69/129的梯度、库存数量有差异这样演示筛选和排序时才有内容可说。密码存明文是课程设计里的通病答辩被问起时如实承认“正式上线会改成BCrypt哈希”就行比现场撒谎体面得多。演示数据的INSERT语句建议单独存一份demo_data.sql和建表脚本分开重置数据库时建表和造数就是两个独立动作互不干扰。3. Java后端与页面从源代码里拆出三层结构数据库是骨架肉都在src目录的Java代码里。很多人拿到源代码后直接点运行看到页面能开就关掉了结果答辩时老师问一句“购物车存在哪里”就卡壳。与其临时抱佛脚不如花半小时把代码结构摸一遍。3.1 先看懂包结构controller/service/dao 三层解压源码后第一件事是打开pom.xmlMaven项目或build.gradleGradle项目确认技术栈。Spring Boot项目会看到spring-boot-starter-web依赖老式项目是javax.servlet-api加一个web.xml。两种结构差别很大部署方式也不同先分清再动手。Spring Boot的包结构一般长这样com.flowershop下面是entity实体类、controller接收请求、service业务逻辑、dao或者mapper数据库操作、config配置类。老式Servlet项目的结构则是servlet包加jsp页面service和dao依然存在多一个filter包也不少见。判断口诀看到RestController和SpringBootApplication就是Spring Boot看到web.xml和WebServlet注解就是传统Servlet项目。这套分层不是摆设。controller只做三件事取请求参数、调service、返回视图或JSONservice里写业务规则dao里只放数据库增删改查。课程设计里最常见的错误是业务逻辑全部堆在controller里一个方法两百行service层形同虚设。如果你拿到的源码是这个风格建议至少把下单逻辑挪到service否则答辩演示时老师顺着代码一追很容易被问倒。花店系统从数据库增删改查的角度看商品列表是“查”、加购是“增”、改数量是“改”、移除购物车是“删”一套项目正好把四种操作练全。3.2 商品列表接口一条请求的全链路商品列表是花店系统里最短也最完整的请求链路把它看懂其他页面都是同一套模式的重复前端请求地址、Controller接参数、Service处理、Mapper查库、结果渲染回页面。先看前端怎么发起请求。课程设计用服务端渲染JSP或Thymeleaf而不做前后端分离因为不需要解决跨域和鉴权单机一套就能跑通这正是课程设计最合适的复杂度。!-- 分类导航点击不同分类URL带上categoryId参数 -- a th:href{/flower/list(categoryId1)}玫瑰系/a a th:href{/flower/list(categoryId2)}百合系/a这段定义了两个分类入口真正的列表渲染由后端返回flower_list视图完成。categoryId是分类筛选的唯一入口参数值为0时代表全部分类。Controller接收参数Controller RequestMapping(/flower) public class FlowerController { Autowired private FlowerService flowerService; GetMapping(/list) public String list(RequestParam(defaultValue 0) Integer categoryId, Model model) { // categoryId为0表示查全部分类 ListFlower flowers flowerService.listByCategory(categoryId); model.addAttribute(flowers, flowers); return flower_list; // 返回视图名由解析器拼接前后缀 } }Service和MapperService public class FlowerService { Autowired private FlowerMapper flowerMapper; public ListFlower listByCategory(Integer categoryId) { if (categoryId null || categoryId 0) { return flowerMapper.selectAll(); } return flowerMapper.selectByCategory(categoryId); } }Mapper public interface FlowerMapper { // 注解SQL适合单表查询多表关联建议用XML Select(SELECT * FROM flower WHERE status 1 ORDER BY create_time DESC) ListFlower selectAll(); Select(SELECT * FROM flower WHERE category_id #{categoryId} AND status 1) ListFlower selectByCategory(Integer categoryId); }逻辑说明RequestParam(defaultValue 0)处理了用户没传分类的场景不写这个默认值时访问/flower/list会直接报400参数缺失错误这是加分类筛选功能时最容易犯的错误。Model对象是Spring MVC给视图传数据的标准通道页面里th:text${flower.name}就能拿到花名。返回的字符串“flower_list”不是实际路径而是由视图解析器拼成templates/flower_list.html或WEB-INF/views/flower_list.jsp。参数说明两张SELECT都带了status 1条件只查上架商品这是“下架”功能的实现基础——下架不是删数据而是把状态位改成0。如果源码里的查询没带这个条件建议自己加上否则后台下架了商品、前台首页照样显示这是个演示时必被追问的漏洞。ORDER BY create_time DESC让新上架的花排在前面模拟“新品优先”的电商惯例。3.3 购物车与下单会话与事务的临界点购物车在课程设计里有两种存法存数据库表或者存Session。存数据库的优点是用户换设备购物车还在缺点是每次加购都要写库存Session的优点是实现简单、代码量少不用登录就能加购物车。绝大多数花店系统的课程设计源码选Session方案顺手还能展示HttpSession这个知识点。Session购物车的常见写法是用Map存“商品ID到数量”的映射/** * 加购接口无论购物车是否已存在该商品数量只增不减 * 商品ID是key数量是value */ GetMapping(/cart/add) public String addToCart(RequestParam Integer flowerId, RequestParam Integer quantity, HttpSession session) { MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } cart.put(flowerId, cart.getOrDefault(flowerId, 0) quantity); return redirect:/cart; }逻辑说明返回“redirect:/cart”而不是直接渲染购物车页面是为了防止刷新页面时重复提交加购请求——重定向会让浏览器重新发起GET请求直接返回视图时F5会把上一次请求再执行一遍购物车里莫名其妙多出两件商品。这个细节在答辩演示时很容易踩中老师一按F5就露馅。参数说明HttpSession默认存内存重启Tomcat购物车就没了这是Session方案的原生限制。课程设计里讲清楚“Session生命周期由服务器管理、默认30分钟失效”就算达标不必引入Redis。如果源码里用的是getOrDefault这个方法说明JDK是8以上版本也侧面印证了环境配置时JDK版本不能太低。购物车里攒了一堆花点结算时数据要从Session搬到数据库的orders和order_item表。这中间有个容易忽略的临界点事务只保护数据库的四步写操作而清空Session购物车发生在事务提交之后。如果事务回滚但购物车却已清空用户钱没付成东西也没了属于严重事故。正确顺序是事务方法成功返回后再执行cart.clear()很多源码把这个顺序写反了建议对照检查。这套系统里的Session生命周期、事务边界、连接池参数几乎每一个都是java面试题里的高频考点。答辩时老师最喜欢顺着购物车往下问“两个人同时买最后一枝花会怎样”答案就是上面createOrder里的selectByIdForUpdate。代码是死的但能把设计理由讲清楚课程设计的分就不会低。4. 部署不是双击Startup.bat本地与服务器上的环境配置摘要里带“部署视频”的zip包视频内容通常只拍到本地双击启动然后浏览器打开localhost。但真实部署要过三道关JDK环境、数据库连接、打包方式。很多人在本地跑得飞快复制到服务器上就歇菜下面把三种场景的配置串一遍。4.1 本地先跑通JDK、MySQL、IDEA的最小配置第一个隐性门槛是JDK版本。Spring Boot 2.x对应JDK 8或11Spring Boot 3.x要求JDK 17。打开pom.xml看java.version或者看代码里有没有用var、List.of这类新语法一眼就能判断版本。老代码用新JDK编译最常见报错是“cannot find symbol”和“程序包javax.servlet不存在”。java环境变量配置也是经典翻车点。JDK 8以后不需要配置CLASSPATHJAVA_HOME和PATH两样就够# 以Linux/macOS为例Windows在系统属性里加等效变量 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH # 验证三连检查 java -version javac -version echo $JAVA_HOME逻辑说明java -version正常但javac报command not found说明JAVA_HOME配错了或者PATH里只配了运行路径没配编译器路径两个版本号都正常但echo出空值说明profile文件没被加载重新source一下。IDE里能跑但命令行不能跑则是IDE用了内置JDK而不是系统的需要检查IDEA的Project Structure和系统环境变量是否指向同一个JDK。数据库这块本地MySQL装好后第一件事是确认字符集。导入脚本后执行SHOW TABLE STATUS查各表编码如果建表时锁了utf8而系统要求utf8mb4要执行ALTER TABLE flower CONVERT TO CHARACTER SET utf8mb4注意必须用CONVERT而不是DEFAULT CHARACTER SET——前者重新编码已有数据后者只改新建表的默认值老数据照样乱码。IDEA里的最小配置其实是三处Project SDK选对JDK版本、Maven的settings.xml指向能用的仓库镜像、Run Configuration里配好启动参数。第三处最坑很多人把密码直接写在application.yml里提交演示时连的是自己本机的库换机器就报Access denied配置分离的做法在4.3节展开。4.2 打包部署jar包方式、war包与数据库导入部署方式由项目类型决定Spring Boot项目打jar包java -jar直接运行无需单独装Tomcat传统Servlet项目打war包丢进Tomcat的webapps目录自动解压。从pom.xml的packaging标签一眼可辨jar还是war。jar包方式最小流程mvn clean package -DskipTests # 上传到服务器后启动退出终端进程不退出 nohup java -jar flower-shop-1.0.0.jar --spring.profiles.activeprod \ app.log 21 # 启动验证看端口和日志尾部 ss -lntp | grep 8080 tail -f app.log逻辑说明-DskipTests跳过测试阶段加快打包也避免测试代码连不上测试库导致打包失败。nohup加组合让进程在终端关闭后继续运行标准输出重定向到app.log配合tail -f能实时看启动日志。看到“Started FlowerShopApplication in x.xxx seconds”才是真正启动完成只看到Tomcat启动横幅还不够Spring上下文没加载完就访问页面会报404。war包方式稍不同cp flower-shop.war /opt/tomcat/webapps/ # 启动Tomcatwar会被自动解压成同名目录 /opt/tomcat/bin/startup.sh # 确认解压结果 ls /opt/tomcat/webapps/逻辑说明war包方式最容易出问题的是context-path。默认访问路径是http://IP:8080/flower-shop/如果前端链接写的是/每个请求都404。两个解法把war改名为ROOT.war部署让项目跑在根路径下或者后端统一加context-path前端链接都带前缀。部署视频里演示的往往是改名ROOT.war这种省事方式但业务上更好的做法是保留/flower-shop前缀避免以后多个应用部署在同一Tomcat里路径冲突。数据库导入生产库的推荐方式# 服务器上先建库再导入保持字符集一致 mysql -u flower_user -p -e \ CREATE DATABASE flower_shop DEFAULT CHARACTER SET utf8mb4; mysql -u flower_user -p flower_shop flower_shop.sql # 导入后做数据校验不能默认成功 mysql -u flower_user -p flower_shop -e \ SELECT COUNT(*) FROM flower;逻辑说明导入后必须验证而不是默认成功。SQL脚本里如果包含USE flower_shop语句而服务器上建的库名不同命令行不报错但数据落到了错误的库里。SELECT COUNT(*)的数字和本地一致才算真正完成这一步也是部署视频里经常一笔带过的“黑匣子”环节。4.3 配置文件里的三个关键参数端口、数据库连接、上传路径部署后最常见的两类故障——连不上数据库、图片全部裂开——根源都在配置文件。Spring Boot项目所有配置收敛在application.yml里传统Servlet项目在db.properties或jdbc.properties里改的原理一样。下面以Spring Boot为例讲三个必调参数。server: port: 8080 # 端口被占用时改成8081 spring: datasource: # JDBC连接串serverTimezone是MySQL 8必须url里显式指定编码 url: jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: flower_user password: flower123456 hikari: maximum-pool-size: 10 # 课程设计用默认10足够调大没有正向收益 servlet: multipart: max-file-size: 10MB # 花品高清图常超默认1MB max-request-size: 20MB upload: path: /var/flower_shop/upload # 图片落盘目录生产环境不用项目目录逻辑说明serverTimezoneAsia/Shanghai是MySQL 8驱动强制要求的不写就报时区错误。username和password不要用root单独建flower_user并只授权flower_shop库这是数据库安全的基本习惯。upload.path是自定义配置项代码里通过Value(${upload.path})注入数据库image字段只存相对路径页面访问时由映射规则找真实文件。参数说明HikariCP是Spring Boot 2.x默认连接池不需要额外引依赖。maximum-pool-size默认10对整个花店项目绰绰有余改成100不会让并发变高只会让MySQL白白消耗内存指望改这个数提升性能属于玄学范畴。multipart那组参数针对花品图片上传默认1MB对手机拍的高清花束照片太小传到一半报MaxUploadSizeExceededException就是这个值没调。部署视频通常不会把生产配置和本地配置分开处理。我一般会本地保留application-dev.yml、服务器上用application-prod.yml启动时通过--spring.profiles.activeprod指定。这样本地的数据库密码和图片路径不会被带到生产环境两个环境互相独立改一处不会炸另一处。5. 网上花店系统避坑笔记5个让人翻车的典型问题这一章把源码调试、数据库配置、部署上线里最常见的5个翻车点按“现象—原因—解决”整理出来。每一条都在真实的课程设计里出现过属于反复踩出来的血泪经验。5.1 时区报错连接MySQL时必加的serverTimezone现象项目启动正常但一访问需要查数据库的页面控制台抛异常核心错误是The server time zone value ???ú±ê׼ʱ?? is unrecognized or represents more than one time zone。原因MySQL 8的JDBC驱动比MySQL 5版本严格要求连接串显式声明服务器时区不声明就按系统默认去猜猜不出来就报错。错误信息里的乱码还顺带暴露了第二个问题操作系统字符集不是UTF-8。解决在JDBC连接串里补上时区和编码两个参数中间用连接jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai参数说明serverTimezone的值不要用UTC要用Asia/Shanghai因为订单时间和活动时间显示都要按北京时间算用UTC会差八小时。改完配置文件重启应用这个错误立即消失。如果用的是老MySQL 5.x不加serverTimezone也不报错但加上无害建议统一加上。5.2 中文乱码三个环节逐段定位现象花名在数据库里正常页面显示乱码反过来数据库乱码页面正常更诡异的是有时候管理端正常、用户端乱码。原因乱码有三个独立环节每环编码设置不统一就会出现。第一环是数据库连接串的characterEncoding第二环是请求参数的URI编码第三环是页面响应的Content-Type。三个环节只要有一处是ISO-8859-1或者默认值中文就保不住。解决按三环逐一检查。数据库连接串显式写characterEncodingutf8Spring Boot的Tomcat URI编码在配置文件里设server.tomcat.uri-encodingUTF-8老式Tomcat在server.xml的Connector上加URIEncodingUTF-8JSP页面头部写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %。排查顺序建议是先看数据库里存的值对不对再开F12看请求头里的Content-Type最后看页面meta标签哪一环对不上就改哪一环不要盲猜。5.3 404和405请求路径与方法对不上现象点分类导航全部404点“提交订单”按钮报405。原因404是URL路径不匹配——前端请求/flower/list后端RequestMapping写的是/flowerList405是HTTP方法不匹配——前端用表单POST提交后端却是GetMapping浏览器直接访问页面是GET一提交就405。课程设计里这两类错误占调试时间的大头。解决打开浏览器F12的Network面板找到失败请求先看Request URL对照后端注解里的路径再看Request Method对照注解类型。路径对不上就统一URL方法对不上如果交互简单把GetMapping改成RequestMapping同时接受GET和POST最省事但更好的做法是表单该POST就保持POST后端用PostMapping语义更清晰。提示405这种错误看起来很吓人其实就是方法类型不一致别往框架上找原因先看F12。5.4 订单金额有误差浮点数精度问题现象订单列表显示“合计198.0000000001”或者有时候算出来的总额莫名多一分钱少一分钱。原因数据库或Java代码里用了float/double表示金额。浮点数在二进制里无法精确表示0.1这种十进制小数99.0乘2在底层算出来就是197.99999999999997之类的结果展示时露出尾巴。解决数据库金额字段用DECIMALJava金额运算用BigDecimal并且一律用字符串构造BigDecimal——new BigDecimal(99.00)而不是new BigDecimal(0.1)后者会把浮点世界的误差原样带进来。如果源代码里所有金额都用了double改动量太大最务实的做法是做一个MoneyUtils工具类把所有金额运算收敛到一个类里后续替换实现只改一处。public class MoneyUtils { // 用字符串构造BigDecimal避免double构造的精度误差 public static BigDecimal of(String val) { return new BigDecimal(val); } public static BigDecimal multiply(BigDecimal price, int quantity) { return price.multiply(BigDecimal.valueOf(quantity)); } }参数说明BigDecimal.valueOf(quantity)内部会走Integer.toString是安全的构造方式。页面显示金额时记得调用setScale(2, RoundingMode.HALF_UP)保留两位小数否则可能出现99.0这种不整齐的展示。答辩时能说出“金额不能用浮点数”这句话比写十个功能都加分。5.5 部署后图片不显示路径映射与静态资源配置现象本地运行花品图片正常部署到服务器后图片全部裂开控制台报FileNotFound。原因源码里图片路径写的是本机绝对路径比如C:/upload/rose_red.jpg或者用了System.getProperty(user.dir)拼接目录换台机器路径就失效。另一个原因是Spring Boot默认只映射classpath下的静态资源磁盘上的上传目录不会自动成为可访问的URL路径。解决把图片统一存到配置的upload.path目录数据库image字段只存相对路径/upload/xxx.jpg再写一个配置类把它映射为静态资源。Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // URL里的/upload/**映射到磁盘目录file:前缀表示读文件系统 registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }逻辑说明与参数说明addResourceHandler里的/upload/**是URL匹配规则addResourceLocations里是磁盘路径必须以file:开头并且以/结尾。Windows的写法是file:C:/upload/Linux是file:/var/flower_shop/upload/两种写法不通用部署到服务器前一定要检查这处配置。这类问题看起来像玄学实际就是路径映射断了URL带着/upload/rose_red.jpg来请求代码不知道这个URL对应磁盘哪个目录自然返回404。6. 答辩演示前的一个小时造数、验证、录屏三个动作演示翻车一般不在代码功能而在数据和演示路径。给自己留出这一个小时按下面三件事做。第一造一套“有故事”的数据。三个分类每类至少两到三种花价格分别落在低中高三个段位库存既有充足的数量也有一个售罄的0再留一朵下架状态的花。这样演示时分类筛选、价格排序、售罄标记、后台上下架每个功能都能随手点到合适的数据来展示而不是临时改库。第二走一遍演示全流程并用SQL验证。演示脚本按真实用户路径设计登录、选花加购、改数量、清空重选、结算下单、后台改订单状态、查看已完成订单。下单后跑一条关联查询确认数据真的落库了-- 下单后验证订单主表和明细表都有数据金额一致 SELECT o.order_no, o.total_amount, o.status, oi.flower_name, oi.quantity, oi.subtotal FROM orders o JOIN order_item oi ON o.id oi.order_id WHERE o.user_id 1 ORDER BY o.create_time DESC;这条SQL在答辩现场跑出来比任何截图都有说服力。JOIN把两张表按order_id关联起来WHERE限定演示用户ORDER BY按时间倒序让最新订单排在最上面。如果结果为空趁还没上台赶紧回头检查Session购物车和下单事务的衔接。第三录屏前做环境清理。浏览器清掉缓存和历史记录数据库清一遍脏数据重新导入demo_data.sql保证演示从零开始。给需要的流程准备一个“后悔药”脚本一行命令重置数据库到初始状态现场数据搞崩了十秒恢复。这个习惯救过我一次——当年演示到一半手滑把后台的商品删了首页瞬间空白因为提前准备好了重置脚本花十秒恢复数据才把场子救回来。后来每次做课程设计或者毕设演示我都会先写重置命令再碰功能代码这个顺序让我少慌了好多次。每套课程设计都值得这样准备希望帮到你。本文还有配套的精品资源点击获取
返回列表