ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java+JSP网上拍卖系统毕设:核心实现与并发避坑指南

Java+JSP网上拍卖系统毕设:核心实现与并发避坑指南 简介基于JavaJSP的网上拍卖系统毕业设计提供完整的项目源码与配套文件适合Java Web方向的学习者、正在准备毕业设计的学生以及希望复现在线交易系统开发流程的开发者。系统涵盖用户注册登录、商品分类浏览、在线竞拍、出价管理、结单处理等核心业务并整合Servlet、JSP、JavaBean、JDBC与数据库设计等常用技术栈。压缩包共137个文件、约2.36MB其中包含42个JSP页面、20个Java源文件、25个class文件以及JAR依赖库、图片、CSS样式、数据库文件和项目配置文件覆盖从动态页面展示到后台业务逻辑、再到运行环境配置的完整环节。目前已有266人浏览学习。通过阅读源码可以清晰理解MVC分层、Session会话保持、用户权限校验、SQL查询与事务处理等关键点同时也可参考文件上传和竞拍状态机的实现思路作为课程设计、毕业答辩或企业级拍卖系统的二次开发基础。1. 网上拍卖系统毕设为什么JavaJSP仍然是好选择每年毕业季都会收到类似基于JavaJSP的网上拍卖系统毕业设计实现源码这样的题目。说实话现在用 Spring Boot 的多了但 JSP 这套技术栈并没有过时——它把请求处理、数据库操作、页面渲染全部摊开在眼前特别适合用来理解 Java Web 的基础原理。网上拍卖系统也不是普通的 CRUD它天然带着并发出价时间判定状态流转这几个看点这些正好是面试官喜欢问的 java 面试题 素材。如果你正在做这个毕设或者想拿一个经典系统练手下面这些从表结构到部署的细节都是我实际跑项目时踩过的路能让你少走不少弯路。2. 系统拆解与核心模块从登录到出价哪些是必做的2.1 角色权限与业务闭环买家、卖家、管理员一个网上拍卖系统需求清单拆到底就是三类人买家、卖家、管理员。买家能浏览在拍商品、对商品出价、查看自己的出价记录卖家能发布商品、修改商品信息未开始前、查看自己商品的竞拍进度管理员负责审核违规商品、禁用异常用户、查看系统拍卖统计。要支撑起这个闭环首先得在用户表里存一个角色字段否则所有页面都分不清给谁显示什么。常见做法是在user表加role字段0 表示管理员、1 表示卖家、2 表示买家。有些需求还允许一个用户既是买家又是卖家所以不能用一个用户只有一个角色的单字段做死更好的办法是单独建一张user_role映射表。但毕设为了控制工作量单字段也够用——你只要在注册页面让用户选择我是买家还是卖家一个账号固定一种身份这在答辩时解释为业务上做了轻量抽象即可。用户表的建表 SQL 前面给过了这里再强调两处细节密码字段长度至少要 60 位因为用 BCrypt 加密后的字符串有 60 个字符如果你用 MD5字段长度 32 就够但 MD5 已经不适合在现代项目中使用。安全不是毕设硬性要求但答辩老师问起来你不能说明文存了。至少写一个MD5(password salt)然后告诉老师你用了加盐哈希。业务闭环要理清楚卖家发布商品时商品状态为已发布或竞拍中买家出价成功后商品状态仍为竞拍中但当前价和最高出价人变了到了结束时间如果有人出价状态变为已成交如果没有状态变为已流拍。这三个状态建议用数字常量定义在 Java 类里比如public static final int STATUS_AUCTIONING 1;不要散落在 JSP 里用魔法数字判断否则改需求时要全项目替换。权限控制在 JSP 项目里经常被忽略。有些代码把所有 JSP 都放根目录任何人直接访问 URL 就能看到个人中心页面。这里我建议把所有需要登录才能看的页面放到WEB-INF目录下让 Servlet 转发进入。因为WEB-INF目录是 Tomcat 的访问禁区外部不能直接请求只有服务器内部的forward才能把请求带进去。这样做不用写太多拦截逻辑安全性也能高一个档次。关于管理员可以在管理员 Filter 里检查session.getAttribute(role)是否等于 0不通过就返回 403 页面。2.2 数据库设计拍卖最核心的出价表与商品表拍卖系统的数据库设计核心是两张表auction_item和bid_record。普通商城商品表只有价格、库存而拍卖商品表必须记录起拍价、当前价、加价幅度、结束时间、拍卖状态、最后出价人。出价记录表更是商城没有的它每行代表一次竞价是审计和追溯的依据。auction_item的建表 SQL 已经贴过这里补充几个容易漏的索引。(status, end_time)联合索引非常重要因为首页列出的都是竞拍中的商品而且要按结束时间排序查询条件先按 status 过滤再按 end_time 排序有联合索引能避免 filesort。seller_id也要建索引因为卖家中心要按卖家查商品。如果你不做审核功能可以不管商品状态但要做审核还得加一个review_status字段和拍卖状态分开管理否则会出现管理员还没审核商品就开始拍卖的漏洞。bid_record表的设计要强调谁在什么时候以什么价格拍了哪个商品。这四个维度缺一不可。不要在出价记录里冗余商品标题或用户名关联查询用JOIN就好。有些同学为了显示方便在记录表里存了item_title结果卖家改了标题历史记录也跟着变心这违反了基本的规范化原则。如果你确实想在列表页显示商品标题就写SELECT b.*, i.title FROM bid_record b JOIN auction_item i ON b.item_id i.id。这样一层 JOIN 对几十万条记录也不会有压力。关于价格字段类型DECIMAL(10, 2)是标配。有人用FLOAT这会导致精度误差比如 99.99 变成了 99.9899999显示出来非常难看。货币一律不用浮点这是 java 基础 里就该养成的习惯。如果拍卖的价格要支持更大范围比如到千万级DECIMAL(12, 2)也能放得下但毕设用(10,2)足够。另一件容易被忽略的事是外键。我建议不用外键约束但要在业务层保证关联完整。比如删除用户时先检查他是否有未结束的拍卖商品如果直接跨表删除会用 MySQL 的ON DELETE CASCADE把出价记录也删掉可能造成审计缺失。毕设项目规模小可以在dao层用事务控制删除顺序这样既不依赖数据库外键又能把逻辑讲清楚。时间字段统一用DATETIME而不是TIMESTAMP。TIMESTAMP的范围上限是 2038 年而且受时区影响大DATETIME更直白适合做拍卖截止时间。在 Java 代码里用java.sql.Timestamp接收不要用java.util.Date避免在 JDBC 参数绑定时出现类型匹配错误。2.3 会话管理与权限控制JSP里怎么判断当前用户JSP 是服务端渲染页面它的当前用户记录在HttpSession里。登录成功时把用户对象的关键信息放进 sessionrequest.getSession().setAttribute(userId, user.getId()); request.getSession().setAttribute(username, user.getUsername()); request.getSession().setAttribute(role, user.getRole());这样在 JSP 页面里可以直接用${sessionScope.userId}拿到当前登录用户 ID。但注意如果你把整个User对象放进去要确保User类实现了Serializable因为 Tomcat 在做 session 持久化时会要求对象可序列化。毕设一般不涉及 session 持久化但这是面试会问的知识点顺便留意一下。权限过滤用 Filter 最合适。前面已经给过一个 AuthFilter 示例这里再补充一下web.xml里的注册方式filter filter-nameAuthFilter/filter-name filter-classcom.auction.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern写/*会拦截所有请求包括 JSP、图片、CSS、JS。所以 Filter 内部必须做静态资源放行。上面代码里我写了/static前缀如果你的资源都放在static目录下就不会被拦截。如果不想每个路径都加前缀就改为对/jsp/*或/user/*等特定路径拦截。筛选原则是拦截需要登录的动作 URL和页面 URL。Filter 里要注意sendRedirect的路径问题。sendRedirect的路径是相对 Web 根目录的如果你的项目上下文是/auction那么重定向到登录页应该写login.jsp当当前请求已经在该上下文内或者写成request.getContextPath() /login.jsp更安全。直接用login.jsp可能因为当前 URL 层级不同导致跳错这是很多人的坑。我习惯统一用response.sendRedirect(request.getContextPath() /login.jsp);在 JSP 内判断角色用 JSTL 比小脚本整洁得多。比如只在登录用户是管理员时显示商品审核链接c:if test${sessionScope.role 0} a hrefadmin/list商品审核/a /c:if注意 EL 表达式中的sessionScope是 Map所以取sessionScope.role会调用 session 里 key 为 role 的属性。如果你的 session 属性名是userRole就写sessionScope.userRole。这些细节如果记错页面会一直不显示链接但也不报错排查起来很浪费时间。3. 用JavaJSP把拍卖流程跑通关键代码与配置3.1 搭建项目骨架ServletJSPFilter的标准分层一个可维护的 JavaJSP 网上拍卖系统不应该把所有代码写在一个 Servlet 里。我常用的分法是com.auction.servlet放控制器com.auction.service放业务逻辑com.auction.dao放 JDBC 数据访问com.auction.entity放实体类com.auction.util放数据库连接、MD5 加密等工具。视图层就是webapp下的 JSP 页面静态资源放webapp/static。pom.xml的依赖前面已经写了补充一个常见的坑如果你使用 Servlet 4.0 但 Tomcat 是 9.0没问题如果你用的是 Tomcat 8.5javax.servlet-api版本最好用 4.0实际上 Tomcat 8.5 支持 Servlet 4.0 规格。如果你是老项目用javax.servlet:servlet-api:2.5就会在 Tomcat 9 上报NoClassDefFoundError因为 Tomcat 9 已经完全移除javax.servlet的老包。在web.xml中注册 Servlet可以采用注解方式WebServlet(/publish)也可以写 XML。用注解更省事但如果你的 Servlet 需要带初始化参数比如上传临时目录XML 更灵活。如果你是初学者我更推荐用注解因为 Java 文件里直接看到 URL 映射调试时能减少一层查找成本。核心 Servlet 的流程要统一接收参数 → 组装实体 → 调用 service → 返回结果或跳转 JSP。比如发布商品的PublishServlet的doPost接收 title、description、startPrice、endTime然后组装成AuctionItem对象调用AuctionService.publish(item)。Service 里先校验结束时间不能早于当前时间然后插入数据库。校验逻辑不要放在 Servlet 里因为将来如果有多个入口比如管理员代发布也要复用。事务边界也要在 service 层控制这算是分层的一个重要好处。商品发布只是单表插入不需要多表事务但出价就需要了。如果项目中有同时插入商品和图片记录图片独立成表也必须在同一个事务里。事务控制在 JDBC 里就是connection.setAutoCommit(false)、commit()、rollback()三件套。如果使用 Spring用Transactional就够了但 JSP 项目没有 Spring所以还是要自己写。3.2 商品发布与图片上传文件存储与路径映射商品发布页面向用户时表单要包含标题、描述、起拍价、加价幅度、结束时间、图片。表单的method必须为post并且enctypemultipart/form-data否则 Servlet 的getPart(image)会抛异常。下面是一个典型的发布表单核心片段form actionpublish methodpost enctypemultipart/form-data 商品标题input typetext nametitle required /br/ 起拍价input typenumber step0.01 namestartPrice required /br/ 加价幅度input typenumber step0.01 namestepPrice value10.00 /br/ 结束时间input typedatetime-local nameendTime required /br/ 商品图片input typefile nameimage acceptimage/* /br/ button typesubmit发布/button /formdatetime-local输入框提交的格式是2025-05-01T12:00这个字符串不能直接Timestamp.valueOf因为valueOf期望的是yyyy-MM-dd HH:mm:ss空格分隔。要先replace(T, )然后再Timestamp.valueOf。这是 JSP 表单提交最容易翻车的细节之一。Servlet 上传部分在 3.2 前面已经给过这里再补充上传大小限制的配置。在MultipartConfig注解里可以设置maxFileSize和maxRequestSizeMultipartConfig(maxFileSize 1024 * 1024 * 5, maxRequestSize 1024 * 1024 * 10)maxFileSize限制单个文件最大 5MBmaxRequestSize是整个请求最大 10MB。如果不设置Tomcat 默认允许 50MB 的单文件但实际部署到云不可能放这么大。毕业设计演示时如果传了一张手机拍摄的 10MB 照片就会报SizeLimitExceededException所以必须加这个限制并且在 JSP 前段把图片压缩或提示用户选择小图。图片存储路径问题我再展开说。使用getServletContext().getRealPath(/upload)时当项目以 war 包部署在 Tomcat 上这个路径指向的是webapps/项目名/upload但 Tomcat 在重部署时会删除并重新解压 war导致之前的/upload目录消失。于是出现运行一段时间后图片全丢的现象。解决办法有三种最省事的是把图片存到webapps/ROOT之外比如写死D:/auction_upload然后用 Tomcat 的虚拟主机映射。在server.xml的Host下加Context path/upload docBaseD:/auction_upload /这样 URL 里的/upload/xxx.jpg会直接映射到磁盘目录。这个server.xml的修改和项目无关部署时容易漏写所以最好写进部署文档。如果你的毕设只在 IDE 里演示用getRealPath完全够但要在文档里注明重启后需重新上传图片。说到图片还有一个容易让新手困惑的是jsp图片如何对坐标定位。比如拍卖房产时需要用户在地图上点击一个位置标记这个坐标存在数据库里页面加载后根据坐标显示标记。实现思路是前端图片区域绑onclick通过event.offsetX和event.offsetY获取点击位置的相对坐标再通过隐藏域提交给 Servlet。JSP 端渲染坐标时用div覆盖在图片上通过position:absolute; left:${image.x}px; top:${image.y}px定位。这种交互不难但需要解决一个细节图片如果被 CSS 设了宽度offsetX是相对于元素的宽度而不是图片原始宽度所以要统一按元素尺寸换算。这不是核心需求加不加看你时间。3.3 竞拍核心出价防止并发超卖与时间校验出价功能是整个系统里最应该做好的模块它直接决定分数。前面给出的bid方法用的是先查询后更新的模式这里我要明确告诉你如果查询和更新不在同一个事务里就会出现经典的丢失更新。比如用户 A 和 B 同时读到当前价 100 元A 出价 110B 出价 110如果 B 的更新 SQL 没有限制current_price 110B 也会更新成功把 A 的 110 覆盖掉。所以 SQL 里的WHERE current_price ?是乐观锁的核心。除了更新条件还需要处理加价幅度的校验。加价幅度是step_price出价必须高于当前价至少一个加价幅度。下面这一行判断if (price.compareTo(currentPrice.add(stepPrice)) 0) { // 不满足加价幅度 }注意compareTo返回负数表示小于不要写成 0就认为大于。很多新手在这个环节用Float和判断导致价格 10.00 和 10.0 被判断为不相等。用BigDecimal时数字的标度不同equals会返回 false比如new BigDecimal(10.00).equals(new BigDecimal(10))是 false但compareTo返回 0。所以价格比较一律用compareTo。如果项目里使用了存储过程或定时任务来关闭拍卖要保证它和出价方法的并发安全。例如定时任务在 12:00 把商品状态改成已结束而用户请求 11:59:59 出价刚好到达。这时要避免两个事务互相覆盖。定时任务的 SQL 建议这样写UPDATE auction_item SET status 2 WHERE id ? AND status 1 AND end_time NOW()同样用了status 1作为条件保证只把还在拍卖的商品关上。绝不会出现已经关闭后又被改回竞拍中。前端出价按钮的交互也要做限制点击后立即 disabled防止双击收到响应后提示出价成功或出价过低。如果你只做了后端校验前端不限制用户连续点五次就会提交五次。虽然数据库层能正确拒绝其中四次但多出来的请求还是会给数据库带来不必要的压力。所以前后端各管一半才是正确的做法。列表页和详情页的倒计时显示不要用整页 JSP 刷新而是用 JavaScript 定时器每秒更新剩余时间。这里再给一个更完整的示例在 JSP 里通过fmt:formatDate输出结束时间然后 JS 读取这个时间并计算剩余input typehidden idendTime valuefmt:formatDate value${item.endTime} patternyyyy-MM-dd HH:mm:ss/ /setInterval(function(){ var end document.getElementById(endTime).value.replace( , T); // 兼容 Safari var seconds Math.floor((new Date(end).getTime() - new Date().getTime()) / 1000); if (seconds 0) { document.getElementById(bidBtn).disabled true; document.getElementById(countdown).innerHTML 已结束; } else { var dd Math.floor(seconds / 86400); var hh Math.floor((seconds % 86400) / 3600); var mm Math.floor((seconds % 3600) / 60); var ss seconds % 60; document.getElementById(countdown).innerHTML dd 天 hh 时 mm 分 ss 秒; } }, 1000);这个定时器只更新时间字段不刷新整个 JSP也解决了刷新后倒计时重制的问题。注意在 JSP 中直接输出endTime时由于服务器时间和浏览器时间可能有差异最好在后端把endTime转成时间戳再传给 JS而不是传字符串让前端解析。上面代码里用new Date(2025-05-01 12:00:00)在 iOS Safari 上会返回 Invalid Date因为 Safari 不识别空格分隔。我做了replace( , T)处理保证兼容。4. 上线部署与调试Tomcat、JDBC、中文乱码的常见问题4.1 本地运行的最小配置JDK、Tomcat、数据库很多新手第一关就挂在环境上。JDK 装完之后输出java -version能看到版本号不代表 Tomcat 能正常使用因为 Tomcat 的启动脚本依赖JAVA_HOME环境变量。我在 Windows 上一般这么配系统变量新建JAVA_HOME值填C:\Program Files\Java\jdk-17然后在Path中加入%JAVA_HOME%\bin。注意 JDK 17 编译的项目跑在 Tomcat 10 上会有包名变更问题因为 Tomcat 10 把javax.servlet改为jakarta.servlet。毕设如果坚持用 javax 包最好选 Tomcat 9如果选了 Tomcat 10代码里所有javax.servlet都要改名成jakarta.servlet。这就是为什么很多从网上下载的源码在新版 Tomcat 上跑不起来的原因。数据库连接串的写法也是常见痛点。不要在代码里写死数据库密码至少抽到一个配置文件里。毕设可以从简用一个jdbc.properties放连接串、用户名、密码然后用一个静态代码块读属性。下面给一个超简版private static Properties props new Properties(); static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(jdbc.properties)) { props.load(in); } catch (IOException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(props.getProperty(url), props.getProperty(username), props.getProperty(password)); }把jdbc.properties放在src/main/resources下打包后它会出现在 classes 根目录。这样换数据库配置不用改 Java 代码在答辩演示时很实用。Tomcat 的启动脚本在 Windows 上是bin/startup.bat在 Linux/macOS 是bin/startup.sh。启动后访问http://localhost:8080/看到 Tomcat 首页就说明环境正常。如果你访问不到先检查端口是否被占用其次看命令行日志有没有SEVERE报错。不要在没有日志的情况下反复重启日志会告诉你一切。数据库初始化网上拍卖系统至少需要user、auction_item、bid_record三张表。你可以用 Navicat 手动建表也可以用 SQL 脚本一键初始化。建议把初始化脚本放在项目仓库的sql/init.sql下这样换电脑时不需要重新手动建库。脚本里先创建数据库再建表注意设置默认字符集utf8mb4。CREATE DATABASE IF NOT EXISTS auction DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE auction; -- 建表语句省略...这里还有一个 IDE 配置的细节在 IDEA 中运行项目要确保 Tomcat 的 Application context 设置为/auction与项目上下文一致。如果你设置为/则在浏览器访问时不用带项目名但很多代码里写了request.getContextPath()这会变成空字符串导致某些路径拼接出现问题。我建议统一设置为/auction然后在浏览器用http://localhost:8080/auction/index.jsp访问。4.2 部署过程中的3个常见报错及排查下面这张表整理了毕业生最常见的三个报错每一行都可以直接按步骤操作现象原因解决启动时ClassNotFoundException: com.mysql.cj.jdbc.Driver没有引入 MySQL 驱动 jar或者使用旧驱动类名在 pom.xml 添加mysql-connector-java依赖如果是手动放 jar确认 jar 位于WEB-INF/lib下页面中文显示乱码提交到数据库也是乱码JSP 页面编码、请求编码、数据库连接编码三者不一致JSP 头部设置pageEncodingUTF-8Servlet 在getParameter前执行request.setCharacterEncoding(UTF-8)JDBC URL 加?characterEncodingUTF-8访问/auction/显示 404但 Tomcat 首页正常项目没有部署到 webapps或 URL 缺少上下文路径检查webapps下是否有auction.war/URL 写http://localhost:8080/auction/index.jsp除了这三个还要注意java.net.BindException: Address already in use: JVM_Bind这种端口占用。原因通常是另一个 Tomcat 实例或应用占用 8080。解决要么改server.xml里的端口要么用netstat -ano | findstr 8080找到 PID 然后结束进程。如果你在 IDE 里启动时发生端口占用可以先停止 IDE 内跑着的旧实例再重新重启而不是运行。部署到服务器时数据库连接不能只用localhost要改成服务器的 IP 或云数据库的地址。同时防火墙要放行3306端口否则本地连接报Communications link failure。这个报错还可能因为 MySQL 服务没启动或者用户名密码错误。排查顺序是ping数据库地址 → 用 Navicat 测试连接 → 检查连接串中的用户名密码。5. 避坑指南毕业设计里最容易翻车的6个细节5.1 数据重复、时区与中文乱码三个让人摸不着头脑的坑第一条F5 刷新导致重复出价。现象用户在成功出价后按 F5商品详情页重新提交上一次的表单参数数据库里多出一条完全相同出价记录而当前价没变出现脏数据。原因浏览器的刷新动作会将最后一次 POST 请求重放。解决在后端成功出价后使用response.sendRedirect(itemDetail?id itemId)让浏览器变成 GET 请求刷新不会再重放出价。这是最简单有效的做法代价是需要在 Servlet 里多一次重定向。如果重定向后还是要刷新页面就用session里存一个随机 token表单提交时比对token一次性使用可以彻底防刷新提交但代码量多一些。毕设我推荐做重定向即可。第二条时区导致拍卖时间错乱。现象本地测试一切正常部署到云服务器后拍卖结束时间比预期少了 8 小时或者后台看到的end_time和页面不一致。原因数据库和 JVM 使用了不同时区JDBC 读取 DATETIME 时按 JVM 默认时区解析写库时又按数据库时区存储。解决在 JDBC URL 上强制指定serverTimezoneAsia/Shanghai同时在数据库连接初始化时执行SET time_zone 08:00。如果 MySQL 服务器本身时区是 UTC还可以在my.cnf里配置default-time-zone 08:00。千万不要靠手动把时间加 8 小时来补偿因为你不知道部署环境到底哪个时区。第三条JSP 页面的 EL 表达式原样输出。现象页面显示${item.title}而不是商品标题控制台也没有报错。原因web.xml里显式设置了jsp-configtaglib或者老项目里定义了isELIgnoredtrue也可能是 JSP 页面没有% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。解决检查web.xml是否包含jsp-config中el-ignored元素有则删除确认 JSTL jar 在WEB-INF/lib将 JSP 页面第一行改成% page isELIgnoredfalse %作为临时验证。记住JSP 2.0 之后 EL 默认开启你遇到这个问题一定是配置被改过。5.2 文件路径、并发出价与EL表达式另外三个坑第四条上传图片后浏览器 404。现象本地用 IDE 跑能看到图片但打成 war 部署到另一台机器后就打不开了。原因图片存在项目目录下war 重新解压导致图片丢失或路径写成了相对路径。解决参考 3.2 的虚拟目录方案将存储路径配置到 Tomcat 之外同时 JSP 中的img一律使用绝对上下文路径${pageContext.request.contextPath}/upload/xxx.jpg。如果你在本地使用getRealPath注意 Tomcat 的work目录下会有重命名某些时候图片能访问但重启后失效这不算稳定方案。第五条并发出价时同一件商品被两个人拍中。现象用 JMeter 或手工两个浏览器并发出价发现当前价更新了但出价记录里出现两条价格相同的记录且其中一条被另一条覆盖。原因没有用事务约束读→改→写的原子性或者UPDATE没有条件限制。解决使用 3.3 给出的乐观锁 SQL。这里再强调UPDATE auction_item SET current_price ? WHERE id ? AND current_price ?必须和INSERT INTO bid_record在同一个事务里。不要用synchronized因为它在集群环境无效而且要锁的对象是你不知道的什么类容易漏锁。第六条JSP 页面加载完后刷新一次导致倒计时闪断。现象把meta http-equivrefresh content1放在商品详情页结果整个页面每秒刷新用户无法稳定阅读倒计时也断断续续。原因希望实现实时更新但不该用整页刷新。解决把meta删除改用 JSsetInterval更新倒计时和当前价格。如果确实需要加载后清一次缓存用window.onload执行一次fetch获取最新数据。这条也算是对常见jsp页面让加载完后刷新一次需求的正解需求本身是局部刷新不是整页刷新。6. 进阶改进把毕设变成能写进简历的技术亮点如果你希望自己的网上拍卖系统在答辩中不只被评价为能跑可以从三个方向做加强而且每个方向都能对应到一份不错的简历描述。第一个方向是连接池替换。把裸 JDBC 改为 HikariCP 或 Druid只需增加依赖和改几行初始化代码却能消除并发时Too many connections报错。这是最廉价的工程化改进也顺手解决了一个真实的性能隐患。第二个方向是并发控制从乐观锁升级到 Redis 分布式锁。乐观锁适合低频更新但拍卖是典型的高冲突场景用 Redis 的SETNX为每个商品加锁成功后执行出价流程失败则返回失败能显著减少数据库更新冲突。这个改进能写进简历但前提是你把 Redis 的安装和 Jedis/Lettuce 依赖都搞定。第三个方向是实时刷新用 WebSocket 推平局面。当有人出价时服务端把最新价格推送给所有正在浏览该商品详情的客户端前端 JS 监听 WebSocket 消息并更新页面。这比轮询更具实时性也是面试官爱问的 java 开发工程师面试题 常驻考点。在动手这些改进前建议先做完一轮验证用 JMeter 做并发出价测试。具体做法是在 JMeter 中创建一个线程组设置 100 个线程同时访问出价接口每个线程带不同的用户 ID 和价格执行完检查数据库里bid_record数量和auction_item.current_price是否一致且符合预期。如果你连这个测试都还没做最好先别急着加功能因为基础并发逻辑可能有漏洞你加了 Redis 锁也暴露不了真正的缺陷。我当初做这套系统时就因为没有做并发测试直到在答辩前一晚用自动化脚本模拟 50 个并发用户才发现出价方法里犯了一个经典错误——查询和更新用了两个不同的 Connection。这个错误在单线程演示下完全正常一并发就翻车。后来我养成了习惯任何涉及先查后用的逻辑要么用同一个事务要么在 SQL 里带条件。这个习惯后来在真实项目中帮我避免了很多次数据不一致的事故。这次把笔记整理出来也是希望大家少走我走过的弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表