毕设题目拿到手,看到“基于JSP的宠物猫狗商业系统”这几个字,很多人的第一反应是:这不就是一个网上卖猫卖狗、顺便卖点猫粮狗粮的商城吗?第二反应才是——JSP都这么年头了,为什么毕业设计还在用这套技术栈?
实际把项目做下来你会明白,这个题目远不止“商城”两个字那么简单。它把JavaWeb开发从页面渲染、表单提交、Session会话管理、JDBC数据库操作,到文件上传、订单状态流转这些基本功几乎全部覆盖了一遍。JSP作为服务端页面技术,虽然企业项目早就转向了前后端分离,但在高校毕业设计和课程设计里,它依然占据着不可替代的位置:考核点密集、资料丰富、上手门槛低,和MySQL配合起来还能顺带练一轮SQL。这篇文章我按照自己带项目、带学弟学妹改代码时的完整思路,从需求拆解、技术选型、数据库表设计、核心功能实现、IDEA运行配置到常见坑点排查,完整过一遍。准备做同题目的同学可以直接按这个路线推进,打算选JavaWeb方向的师弟师妹,也能提前看清楚这条路要踩哪些石头。
1. 项目定位:先搞清楚这个“宠物猫狗商业系统”到底要做什么
1.1 用户端、管理端、数据端三层拆解
很多同学拿到题目就急着建工程、写页面,结果写到一半发现逻辑全拧在一起,改一处崩三处。正确的第一步是把需求拆成三个层次,每个层次对应一套独立的代码模块。
第一层是面向普通用户的浏览购买端。这里面包含注册登录、个人信息维护、宠物列表与详情展示、商品分类浏览、加入购物车、提交订单、模拟支付、查看我的订单这些功能。注意“宠物”和“商品”在这个系统里是两套不同的数据模型——宠物是活体,有品种、年龄、性别、疫苗状态、健康状态这些字段;商品是宠物用品,主要关心价格、库存、销量。如果用一个表硬套,后期做筛选和详情页会非常别扭。
第二层是管理后台。管理员登录后可以维护宠物和商品信息,实现增删改查;可以上传图片;可以处理订单,把用户下单状态从“待支付”推进到“已发货”“已完成”;还可以管理注册用户,包括禁用账号、重置密码这类操作。管理端不要求美观,能跑通、字段齐全、操作流程完整就可以。
第三层是数据支撑层。MySQL数据库里至少要有用户表、宠物表、商品表、分类表、订单表、订单明细表这六张核心表,它们之间的关系是:用户一对多订单,订单一对多订单明细,宠物和商品都属于某个分类。把这三层想清楚,后面写代码就是按图索骥。
1.2 为什么毕业设计还要选 JSP + JavaWeb 这套“旧”技术
总有人问,现在都用SpringBoot+Vue了,为什么毕设还要求JSP?这里面的逻辑其实很现实。SpringBoot把很多底层细节封装掉了,初学者很难讲清楚一次HTTP请求从浏览器到数据库再到浏览器,中间到底发生了什么。而JSP+Servlet这套组合,要求你自己处理请求转发、会话跟踪、参数获取、JDBC连接管理,每一个环节都暴露在代码里。
答辩的时候老师最喜欢问的问题就是“你这个请求是怎么从页面传到数据库的”“Session和Cookie有什么区别”“你怎么解决中文乱码”。这些知识点恰恰是JSP阶段必须掌握的。换句话说,JSP项目是JavaWeb基本功的“显微镜”,你做的时候多痛苦,答辩的时候就有多从容。
另外还有一个现实因素:实验室环境普遍是JDK8起步,Tomcat8.5或9.0都能稳定运行,MySQL5.7或8.0都支持良好,这些版本搭配在兼容性上几乎没有坑。反观Node版本升级、前端构建工具链冲突这类问题,在毕设答辩现场翻车概率要高得多。技术选型不是越新越好,而是越可控越好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构:动手写代码之前先把骨架立起来
2.1 JSP、Servlet、JavaBean 的分工逻辑
很多网上案例把Java代码直接写在JSP页面里,用<%%>脚本块到处穿插,页面看起来像一锅粥。这种写法在课程设计里也许能蒙混过关,但在毕业设计里会被老师直接扣分,因为它打破了最基本的职责分离。
我推荐的配套是这样的。JSP只负责展示,页面里出现的数据通过EL表达式${}或JSTL标签来读取,禁止出现大段的Java业务代码。Servlet负责接收请求、调用业务方法、把结果塞进request或session、再转发给JSP渲染。JavaBean包括实体类和业务类:实体类对应数据库表结构,业务类封装具体的操作逻辑,比如注册时校验用户名是否存在、下单时扣减库存。
一个典型的请求链路是这样的:用户在pet_list.jsp页面点击“加入购物车”按钮,表单提交到CartServlet,CartServlet调用CartService里的addToCart方法,方法内部把商品ID和数量写入购物车存储结构,然后CartServlet重定向回商品列表页。整个过程页面不碰数据库,Servlet不写HTML,业务方法不直接暴露给页面,层次清清楚楚。
2.2 MVC 分层在这个项目里的落地方式
项目包结构建议按下面这种方式组织,按包名就能看出系统全貌:
- com.pet.entity:存放User、Pet、Product、Order、OrderItem、Category等实体类
- com.pet.dao:数据访问层,一个实体对应一个Dao类,比如UserDao、OrderDao
- com.pet.service:业务逻辑层,处理跨表操作和事务
- com.pet.servlet:控制器层,一个功能点一个Servlet,比如LoginServlet、RegisterServlet、CartServlet、OrderServlet
- com.pet.util:工具类,比如DBUtil(数据库连接)、UploadUtil(图片上传)、PageUtil(分页)
- webapp/jsp:存放所有JSP页面,按照admin、user、common等目录进一步区分
这种分层方式的好处是答辩时你能画出一张清晰的架构图:浏览器发请求→Servlet接收→Service处理业务→Dao访问数据库→结果返回实体对象→请求转发到JSP渲染→响应回浏览器。老师一看这个结构就知道你是真做过设计,而不是从网上抄了个整包改个名字就交差。
2.3 用不用 Maven、用不用框架的取舍
我的建议是:能用Maven就一定要用。Maven解决的是依赖管理和项目构建问题,你不需要手动去下载mysql-connector-j.jar、jstl.jar再塞进WEB-INF/lib目录,只需要在pom.xml里声明依赖坐标,IDEA会自动帮你拉取。这个习惯从毕设阶段就建立,后面进公司接触项目会非常省力。
至于是否引入Spring这类框架,我不建议在毕设里用。原因很简单:如果你JSP+Servlet的基础版本能做出来,说明你已经掌握了JavaWeb的核心;一旦引入Spring,老师会默认你掌握了依赖注入、AOP、事务管理等更高级的概念,答辩追问的深度完全不一样。你还没毕业,没必要给自己挖这么大一个坑。JDBC手动管理连接、手动提交事务的过程虽然繁琐,但正是这些繁琐让你真正理解底层原理。
3. 数据库设计:作品成败的一半在这里
3.1 核心表结构:用户、宠物、商品、订单
数据库是整个项目的底座,表结构设计得好,后面所有查询和关联都顺畅;设计得烂,写了三千行业务代码也救不回来。我先给出一个经过多次验证的核心建表方案。
用户表:
sql复制CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录名',
`password` varchar(64) NOT NULL COMMENT '密码(建议MD5加密后存储)',
`nickname` varchar(50) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL COMMENT '头像图片路径',
`role` tinyint(4) DEFAULT 0 COMMENT '0普通用户 1管理员',
`status` tinyint(4) DEFAULT 1 COMMENT '1正常 0禁用',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
宠物表:
sql复制CREATE TABLE `pet` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`category_id` int(11) NOT NULL COMMENT '所属分类,如喵星人/汪星人',
`breed` varchar(50) DEFAULT NULL COMMENT '品种',
`age` decimal(4,1) DEFAULT NULL COMMENT '年龄,单位岁',
`gender` tinyint(4) DEFAULT 0 COMMENT '0公 1母',
`vaccine_status` tinyint(4) DEFAULT 0 COMMENT '0未疫苗 1已疫苗',
`health` varchar(255) DEFAULT NULL COMMENT '健康描述',
`price` decimal(10,2) NOT NULL,
`image_url` varchar(255) DEFAULT NULL,
`description` text,
`status` tinyint(4) DEFAULT 1 COMMENT '1在售 0下架',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商品表与宠物表结构类似,但多了stock库存字段和sales销量字段,因为它不涉及活体属性。订单表和订单明细表是核心交易表:
sql复制CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号,用时间戳+随机数生成',
`user_id` int(11) NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` tinyint(4) DEFAULT 0 COMMENT '0待支付 1已支付 2已发货 3已完成 4已取消',
`receiver_name` varchar(50) NOT NULL,
`receiver_phone` varchar(20) NOT NULL,
`receiver_address` varchar(255) NOT NULL,
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
`pay_time` datetime DEFAULT NULL,
`deliver_time` datetime DEFAULT NULL,
`finish_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `order_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`item_type` tinyint(4) DEFAULT 0 COMMENT '0宠物 1商品',
`item_id` int(11) NOT NULL,
`item_name` varchar(100) NOT NULL COMMENT '冗余存名字,防止商品删除后订单明细丢信息',
`price` decimal(10,2) NOT NULL COMMENT '下单时的快照价格',
`quantity` int(11) NOT NULL DEFAULT 1,
`subtotal` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 字段类型、状态值、金额与时间的关键约定
有几个设计细节值得单独强调。金额字段必须用decimal(10,2),绝对不要用float或double。浮点数在计算机底层是二进制近似存储,0.1加0.2都可能得不到0.3,用来算钱是会出事的。decimal是定点数,精度可控,虽然计算效率略低,但对商业系统来说正确性优先。
状态字段统一用tinyint数字表示,不要用字符串“待支付”“已支付”。数字的好处是排序快、存储省、写代码时用常量比较清晰。orderItem表里冗余了item_name和price,看似违反数据库设计三范式,实际上是刻意的——订单是历史数据,用户下单后商品改了价格甚至被删除,订单明细仍然要保存用户当时买到的东西和价格。做商业系统,订单表里永远要存快照,这个经验在真实企业项目里也是通用的。
时间字段使用datetime类型,并设置默认值CURRENT_TIMESTAMP,插入记录时不需要手动维护创建时间。订单状态流转需要多个时间字段分开记录,pay_time、deliver_time、finish_time各管一段,这也是为了将来做统计报表做准备。
3.3 购物车与库存扣减怎么设计才不慌
购物车有两种常见实现方式:存数据库表,或者存Session。毕设项目我推荐存Session,因为它不涉及额外的表设计和CRUD操作,刷新页面数据不丢,简单直接。用一个Map<String, Integer>,key是商品或宠物的ID,value是数量,再封装一个Cart类,包含items、totalCount、totalAmount这些属性。用户刷新浏览器时Session还在,购物车数据就还在。
下单是整个过程最需要小心的地方,核心是防止超卖。前台展示库存有10件,两个用户同时下单各买10件,结果库存变成负数,这就是超卖。解决办法是利用SQL的原子操作:UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0。这条语句在数据库层面保证了只有库存大于0时才能扣减成功,返回受影响行数如果是0,说明库存不足,直接提示用户。这是乐观锁思想在库存场景里的落地,不加锁、性能好,也经得起并发测试。
4. 核心功能实现与 JSP 实操细节
4.1 IDEA 运行 JavaWeb 项目:从零开始跑通的配置
很多同学项目代码写完了,卡在“跑不起来”这一步,其实大多是IDEA配置问题。我以IntelliJ IDEA社区版或专业版为例,把配置流程说清楚。
第一步是确认本机环境:JDK8及以上,Maven3.6以上,Tomcat8.5或9.0。第二步在IDEA里新建项目,选择Maven Archetype,Archetype选org.apache.maven.archetypes:maven-archetype-webapp,这样就生成了一个标准JavaWeb项目骨架。第三步配置Tomcat:点开Run菜单下的Edit Configurations,左上角加号找到Tomcat Server—Local,在Application Server栏选中本机Tomcat安装目录。第四步是Deployment选项卡里点加号,选择Artifact,确认是war exploded模式,Application context填写项目名,比如/pet_shop。第五步直接点运行按钮,浏览器访问http://localhost:8080/pet_shop,能看到默认的index.jsp页面就说明环境通了。
比较容易踩的坑有两个。一是新建Maven项目之后webapp目录下缺少WEB-INF/web.xml文件,老版本Tomcat要求必须有这个文件才能部署。解决方法是右键webapp目录,选择Add Framework Support,勾选Web Application,IDEA会自动补全。二是Artifact里没有可选项,通常是web.xml或webapp目录结构不对,检查一下项目结构里是否已经识别出Web模块。
4.2 信息展示页面:JSP + EL + JSTL 的推荐写法
信息展示页面是用户对这个系统的第一印象,宠物列表页、商品列表页、详情页都是JSP的主场。我推荐用EL表达式加JSTL核心标签库来写,替代传统的<%%>脚本块。
先看一个宠物列表页的核心片段:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="pet-grid">
<c:forEach var="pet" items="${petList}">
<div class="pet-card">
<a href="${pageContext.request.contextPath}/pet/detail?id=${pet.id}">
<img src="${pageContext.request.contextPath}/${pet.imageUrl}" alt="${pet.name}" />
</a>
<h3>${pet.name}</h3>
<p>品种:${pet.breed} 年龄:${pet.age}岁</p>
<p class="price">¥${pet.price}</p>
<a class="btn" href="${pageContext.request.contextPath}/cart/add?itemType=0&itemId=${pet.id}">加入购物车</a>
</div>
</c:forEach>
</div>
这里有三件事值得说明。第一,${pageContext.request.contextPath}是获取当前项目上下文路径的标准写法,用它拼接URL可以避免部署时项目名不同导致路径404。第二,c:forEach是JSTL的循环标签,items指向Servlet存入request的List,var定义循环变量,页面里没有一行Java代码。第三,图片的src拼了相对路径,imageUrl在数据库里存的是upload/pet1.jpg这类相对路径,所以页面里要加contextPath才能正确拼出完整访问路径。
在Servlet侧准备好数据:
java复制int pageSize = 12;
int currentPage = 1;
String pageStr = request.getParameter("page");
if (pageStr != null && !pageStr.matches("\\d+")) pageStr = null;
if (pageStr != null) currentPage = Integer.parseInt(pageStr);
int offset = (currentPage - 1) * pageSize;
List<Pet> petList = petDao.findPetsByPage(offset, pageSize);
int totalCount = petDao.countPets();
int totalPages = (int) Math.ceil(totalCount * 1.0 / pageSize);
request.setAttribute("petList", petList);
request.setAttribute("currentPage", currentPage);
request.setAttribute("totalPages", totalPages);
request.getRequestDispatcher("/jsp/pet_list.jsp").forward(request, response);
分页逻辑要特别注意totalPages的计算,用Math.ceil向上取整,否则最后一页数据会显示不完整。分页查询的SQL用LIMIT ?, ?,第一个参数是偏移量offset,第二个是每页条数pageSize。
4.3 图片与坐标定位:上传、显示、局部标记的完整链路
图片处理是这个项目里最容易被忽视但经常出问题的环节。先说上传。表单里必须设置enctype="multipart/form-data",后端如果用原生方式解析会非常麻烦,建议直接用Apache的commons-fileupload组件,在pom.xml里引入依赖,然后这样解析:
java复制DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(2 * 1024 * 1024); // 单文件限制2MB
upload.setSizeMax(10 * 1024 * 1024); // 总请求限制10MB
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) {
String fileName = System.currentTimeMillis() + "_" + item.getName();
File uploadDir = new File(request.getServletContext().getRealPath("/upload"));
if (!uploadDir.exists()) uploadDir.mkdirs();
item.write(new File(uploadDir, fileName));
// 数据库保存相对路径 upload/文件名
}
}
上传文件的文件名必须重命名,否则两个用户传了同名图片会互相覆盖。用System.currentTimeMillis()拼上原文件名,基本可以保证全局唯一。
再说图片如何在JSP页面里显示和定位。显示很简单,
就可以了。但有的毕设会要求更高级的效果,比如在宠物图片上叠加价格标签、或者点击图片的不同区域跳转到不同页面。这就涉及“坐标定位”的知识。
一种做法是CSS定位。把图片放在一个position: relative的容器里,然后在图片上方叠加一个position: absolute的标签层,left和top属性就是坐标位置。这样做的好处是坐标可以动态计算,如果图片在不同尺寸下要等比缩放,坐标也要按比例换算。计算公式是:实际坐标 = 图片显示宽度 / 原始图片宽度 × 原始坐标。这个细节在答辩时讲出来很加分。
另一种做法是HTML的map标签,定义一个可点击区域:
html复制<img src="pet_map.jpg" usemap="#petmap" />
<map name="petmap">
<area shape="rect" coords="50,60,180,220" href="detail.jsp?id=1" alt="宠物1" />
<area shape="circle" coords="320,150,40" href="detail.jsp?id=2" alt="宠物2" />
</map>
coords里对应区域的坐标,rect是矩形左上角和右下角,circle是圆心加半径。这种做法适合展示一张包含多只宠物的合影图,点击不同宠物跳转到对应详情页,比单纯罗列图片列表更有设计感。
4.4 登录鉴权、购物车与下单全流程
登录功能的核心是Session会话管理。用户提交用户名密码后,Servlet里验证通过就执行session.setAttribute("loginUser", user),之后在需要登录的页面里检查Session中是否有loginUser,没有就跳转登录页。这个问题在多个页面重复出现,不应该每个JSP都写一遍判断,而应该用一个Filter统一拦截:
java复制public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession(false);
String uri = request.getRequestURI();
if (uri.contains("login") || uri.contains("register") || uri.endsWith(".css") || uri.endsWith(".js")) {
chain.doFilter(req, resp);
return;
}
if (session != null && session.getAttribute("loginUser") != null) {
chain.doFilter(req, resp);
} else {
response.sendRedirect(request.getContextPath() + "/jsp/login.jsp");
}
}
过滤器的白名单要注意放行静态资源和登录注册接口,否则会出现静态页面也被拦截的尴尬。
购物车操作建议单独做一个CartServlet,通过参数action区分add、remove、clear三个操作。加购时先判断Session里是否已有购物车对象,没有就new一个放入Session:
java复制HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
cart.addItem(itemType, itemId, quantity, price, itemName);
下单流程按这个顺序来:确认购物车不为空→生成订单主表记录,状态为待支付→遍历购物车生成订单明细→扣减库存→清空购物车→跳转到支付页面。模拟支付就是用户点击“确认支付”,把订单状态从0改成1,记录pay_time,这样一个完整闭环就串起来了。整个过程涉及的多个表操作,建议放到同一个Service方法里,用JDBC手动控制事务,出错就rollback,保证订单数据和库存数据的一致性。
5. 常见问题排查与避坑实录
5.1 Tomcat 启动与项目部署问题
这部分我把这些年答疑时遇到最多的问题整理成了一张速查表。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 启动端口被占用 | 8080被其他进程占用 | 改Tomcat端口,或在IDEA配置里换端口 |
| 部署后访问404 | Application context配置不对 | 检查Deployment里的Application context是否等于项目名 |
| 404但首页能开,子页面打不开 | URL路径缺少contextPath | 统一用${pageContext.request.contextPath}拼接地址 |
| 500报错ClassNotFoundException | 依赖缺失 | 检查Maven依赖是否引入,是否刷新成功 |
| 修改代码后不生效 | 热部署没开 | 把On frame deactivation设为Update resources,或手动redeploy |
一个容易被忽略的细节是IDEA中Tomcat的控制台中文乱码。这个通常是Tomcat日志编码和IDEA编码不一致导致,在Tomcat的conf/logging.properties里把java.util.logging.ConsoleHandler.encoding改成UTF-8,同时IDEA的VM options加-Dfile.encoding=UTF-8,能解决大多数情况。
5.2 数据库连接与中文乱码问题
数据库连接推荐用JDBC原生方式写一个DBUtil工具类,加载驱动、获取连接、关闭资源的逻辑集中在同一个类里。MySQL8.0以上版本的驱动类和连接串和5.x完全不同,很多人在这里踩坑:
java复制Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/pet_shop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
注意驱动类是com.mysql.cj.jdbc.Driver,不是旧版的com.mysql.jdbc.Driver;连接串必须带serverTimezone参数,否则会报时区错误;useUnicode和characterEncoding必须配套使用,不然写入数据库的中文会变成问号。
英文问号式乱码还有一个常见来源:页面编码、请求编码、数据库编码三者不一致。解决方法是统一杀手锏——所有JSP页面头部的contentType设为charset=UTF-8,IDEA文件编码统一UTF-8,数据库表结构用utf8mb4字符集,Servlet里在读取参数前先执行request.setCharacterEncoding("UTF-8")。四个环节都统一了,乱码基本绝迹。
5.3 JSP/JSTL 标签解析问题
JSTL标签无法解析是另一个高频问题,页面报错信息大致是“org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved”。原因很简单:项目找不到JSTL的依赖库。
解决方法分两种情况。Maven项目就在pom.xml里加jstl依赖:
xml复制<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
非Maven项目就要手动把jstl-1.2.jar和standard.jar放到WEB-INF/lib目录下。另外,如果项目Servlet版本是3.1以上,JSP页面里还要确认taglib指令的URI没有写错,正确的写法是http://java.sun.com/jsp/jstl/core,别随手抄成别的版本。
还有种情况是EL表达式没有解析,页面原样输出${pet.name}。这是因为web.xml里设置了isELIgnored为true,或者web.xml版本声明过低。把web.xml头部改成以下版本声明即可:
xml复制<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>
5.4 文件上传的边界情况
文件上传除了依赖引错、目录不存在这类基础问题,还有几个边界情况需要注意。第一,上传的文件名不能直接信任客户端传过来的值,有些浏览器会传完整路径C:\Users\xxx\pet.jpg,必须用item.getName()再截取最后的文件名部分。第二,上传目录不存在时要用mkdirs()创建,注意是mkdirs不是mkdir,因为可能涉及多级目录。
第三,上传后的图片在页面上显示404,最常见的原因是数据库存了绝对路径或错误相对路径。我建议数据库统一存upload/xxx.jpg这种相对路径,页面渲染时用${contextPath}前缀拼成完整访问路径,这样项目部署到任何环境都不受影响。第四,图片显示出来但非常慢,多半是原图太大,上传时没做压缩。毕设可以不做严格压缩,但至少要限制文件大小,2MB以内比较合理,超了就提示用户。
6. 个人经验:答辩讲解重点与后续扩展想法
项目功能全部跑通之后,别急着交差,先把自己代到答辩老师的视角,想想哪些点最可能被追问。我参与过不少毕业设计的评审工作,老师们最爱问的集中在四个方面:一是架构设计,为什么这么分层、各层之间怎么通信;二是数据库设计,为什么订单表要冗余字段、库存扣减怎么防止超卖;三是会话机制,Session和Cookie的区别、用户登录状态怎么保持;四是异常处理,如果下单中途出错了事务怎么回滚。
这四块恰恰都是我在前面反复强调过的设计点,也是你平时写代码时的“决策过程”。答辩时不要背代码,要把决策逻辑讲清楚:为什么这个表的字段这么定,为什么这条SQL要这么写,为什么购物车放Session而不是数据库。能讲清“为什么”的项目,在评审眼里就是有独立思考的项目,分数自然上得去。
如果做完这些还有余力,我建议再加两个小功能,性价比很高。一是搜索功能,在列表页加一个关键字搜索框,用LIKE模糊查询匹配宠物名称和品种,代码量不大,但展示效果明显。二是订单统计图表,管理后台里按月份统计销售额,用JFreeChart或者ECharts画个简单的柱状图,这会让系统看起来完整度更高。
最后分享一个我从带项目过程中总结出的经验:写JavaWeb项目时,不要迷信“先写代码再调通”的流程,而是“数据库先行、接口先行、页面最后”。把表结构建好、把Servlet的路径约定好、把页面跳转关系画出来,然后一行一行填实现代码,你会发现整个开发过程顺畅得多。我自己做过太多项目,凡是先建页面再回头改数据库的,基本都经历了大返工。顺序对了,毕业设计就是稳步推进的事情。
