简介:这是一套面向高校计算机相关专业学生的JavaEE课程设计完整项目,以二手图书交易平台为主题,适合作为期末大作业、课程设计或毕业设计参考。项目采用Java语言开发,功能覆盖用户注册登录、图书发布、分类浏览、订单管理等核心业务模块,界面美观、操作简单,新手也能快速理解与部署。资源包共173个文件,约25.68MB,包含21个Java源文件、7个JSP页面、11个HTML页面、12个CSS样式、10个JavaScript脚本以及4个SQL数据库脚本,另附项目报告文档,代码注释清晰,便于二次修改与学习。目前已有132人学习下载。整体目录结构规范,涵盖前端页面、后端逻辑与数据库设计,下载后简单配置即可运行,能帮助读者快速掌握JavaEE项目开发流程,是课程设计冲刺高分的实用参考。
1. 从一份课程设计说起:二手图书交易平台到底要解决什么
每年毕业季,计算机专业的学生都会面对同一个问题:课程设计或毕业设计做什么。二手图书交易平台是出现频率极高的选题之一,原因很直接——业务场景清晰、用户角色分明、技术栈成熟,而且确实有真实需求。校园里每学期都有大量教材和参考书在闲置,传统的处理方式是跳蚤市场摆摊或者微信群喊话,信息散、匹配效率低、交易没有保障。一个基于 JavaEE 的二手图书交易平台,本质上就是把这套线下流程搬到线上,用标准化的方式管理用户、图书信息、订单和交易状态。
这个选题适合两类人:一类是需要完成课程设计或毕业设计的学生,需要一套能跑通、有完整源码和配套报告的项目;另一类是刚接触 JavaEE 技术栈、想通过一个完整项目把 Servlet、JSP、Spring、MyBatis 这些知识点串起来的开发者。它不追求高并发或分布式架构,核心价值在于业务闭环完整、技术点覆盖全面、代码可读可改。接下来我会从技术选型、数据库设计、核心功能实现到部署调试,把整个平台的落地路径拆开讲清楚。
2. 技术选型与工程结构:为什么用 Spring + MyBatis 而不是全量 EJB
2.1 JavaEE 技术栈的取舍逻辑
JavaEE 这个词在不同年代含义不同。早期 JavaEE 指的是 EJB + JSF + JPA 那一套重量级规范,部署要跑在 WebLogic 或 JBoss 上,开发效率低、调试成本高。现在做课程设计级别的项目,实际选择通常是「轻量级 JavaEE」——用 Spring 管理 Bean 和事务,用 Spring MVC 处理请求路由,用 MyBatis 做数据持久化,部署在 Tomcat 上。这套组合既保留了 JavaEE 的分层思想,又避开了 EJB 的复杂度。
具体到二手图书交易平台,核心业务逻辑并不复杂:用户注册登录、发布图书、浏览搜索、下单、订单状态流转。用 Spring 的 IoC 容器管理 Service 层对象,用 AOP 做事务控制,用 MyBatis 的 XML 映射处理 SQL,足够覆盖所有场景。如果强行上 EJB,光是配置 JNDI 数据源和事务属性就要花掉大量时间,而且调试体验很差。我一般会建议:课程设计选 Spring + MyBatis,毕业设计如果要求「体现 JavaEE 规范」,可以在报告里说明用了 Spring 对 JavaEE 的轻量级实现,同时保留 Servlet 和 JSP 作为底层技术支撑。
工程结构按标准 Maven 项目组织,目录划分如下:
| 目录 | 内容 | 说明 |
|---|---|---|
| src/main/java | 实体类、Mapper 接口、Service、Controller | 按包分层 |
| src/main/resources | mybatis-config.xml、jdbc.properties、mapper XML | 配置与映射 |
| src/main/webapp | JSP 页面、静态资源、WEB-INF/web.xml | 视图层 |
| src/test/java | 单元测试 | 可选 |
2.2 从零搭建项目骨架的操作步骤
第一步,用 Maven 创建 Web 项目。在 IDE 里新建 Maven 项目,选择 webapp 模板,或者在终端执行:
mvn archetype:generate -DgroupId=com.booktrade \ -DartifactId=secondhand-book \ -DarchetypeArtifactId=maven-archetype-webapp \ -DinteractiveMode=false这条命令生成标准的 Maven Web 目录结构。groupId是组织标识,artifactId是项目名,后续在 pom.xml 里引用依赖时用得上。
第二步,在 pom.xml 里加入核心依赖。Spring 上下文、Spring MVC、MyBatis、MyBatis-Spring 整合包、MySQL 驱动、JSTL、Servlet API:
<dependencies> <!-- Spring 核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.30</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet API,scope 设为 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies>版本号选择上,Spring 5.3.x 是稳定版本,兼容 JDK 8 及以上。MyBatis 3.5.x 对 JDK 8 支持良好。MySQL 驱动用 8.0.x 时,JDBC URL 需要加时区参数,否则会报时区错误。
第三步,配置 web.xml。注册 Spring 的 ContextLoaderListener 和 DispatcherServlet:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0"> <!-- Spring 根容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Spring MVC 前端控制器 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>这里把 Spring 根容器和 Spring MVC 容器分开配置。根容器扫描 Service 和 Mapper,MVC 容器只扫描 Controller,避免事务配置被重复加载。url-pattern设为/而不是/*,否则静态资源会被 DispatcherServlet 拦截。
第四步,配置数据库连接和 MyBatis。在jdbc.properties里写连接信息:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/book_trade?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=your_passwordserverTimezone必须设置,否则 MySQL 8 会抛The server time zone value is unrecognized。characterEncoding=utf8保证中文书名和描述不乱码。
在applicationContext.xml里配置数据源和 SqlSessionFactory:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.booktrade.entity"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.booktrade.mapper"/> </bean>DriverManagerDataSource适合课程设计场景,生产环境应换成连接池如 Druid 或 HikariCP。mapperLocations指定 XML 映射文件位置,typeAliasesPackage让实体类在 XML 里用短名引用。
2.3 分层包结构与职责划分
包结构按controller、service、mapper、entity、util划分。Controller 只负责接收参数和返回视图,不写业务逻辑。Service 层处理业务规则,比如下单时检查库存、更新订单状态。Mapper 层只做数据库操作,一个方法对应一条 SQL。Entity 层是 POJO,字段与数据库表列对应。
这种分层的好处是改需求时定位快。比如要加一个「图书下架」功能,Controller 加一个方法接收图书 ID,Service 里写状态更新逻辑,Mapper 里加一条 UPDATE 语句,三层各改一处,互不影响。如果所有逻辑堆在 Servlet 里,改一处可能牵动整个流程。
事务管理用 Spring 的注解式事务。在 Service 类或方法上加@Transactional,Spring 会自动管理提交和回滚。注意默认只对RuntimeException回滚,如果方法抛的是受检异常,需要显式指定rollbackFor = Exception.class。
3. 数据库设计与核心表结构:五张表撑起交易闭环
3.1 用户表与图书表的设计要点
二手图书交易平台的核心数据实体有五个:用户、图书、订单、订单明细、分类。用户表存账号信息,图书表存商品信息,订单表存交易记录,订单明细存每笔订单包含的图书,分类表存图书类别。
用户表t_user的字段设计:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT AUTO_INCREMENT | 主键 |
| username | VARCHAR(50) | 登录名,唯一索引 |
| password | VARCHAR(64) | 密码,存 MD5 值 |
| nickname | VARCHAR(50) | 昵称 |
| phone | VARCHAR(20) | 手机号 |
| role | TINYINT | 0 普通用户,1 管理员 |
| create_time | DATETIME | 注册时间 |
密码不能明文存储,注册时用 MD5 加密后入库。虽然 MD5 现在不算安全,但课程设计层面够用,报告里可以提一句「生产环境应使用 BCrypt」。
图书表t_book的字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT AUTO_INCREMENT | 主键 |
| title | VARCHAR(100) | 书名 |
| author | VARCHAR(50) | 作者 |
| isbn | VARCHAR(20) | ISBN 号 |
| price | DECIMAL(10,2) | 售价 |
| original_price | DECIMAL(10,2) | 原价 |
| category_id | INT | 分类外键 |
| seller_id | INT | 卖家外键 |
| description | TEXT | 描述 |
| status | TINYINT | 0 在售,1 已售,2 下架 |
| create_time | DATETIME | 发布时间 |
status字段是关键。图书发布后默认为 0,下单后改为 1,卖家主动下架改为 2。查询列表时只查status = 0的记录,避免已售图书重复出现。
订单表t_order和订单明细表t_order_item是一对多关系。订单表存总金额、买家、状态、创建时间;明细表存订单 ID、图书 ID、成交价。这样设计的好处是:一笔订单可以包含多本书,每本书的成交价单独记录,方便后续统计。
3.2 建表 SQL 与索引优化
CREATE DATABASE book_trade DEFAULT CHARACTER SET utf8mb4; USE book_trade; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(50), phone VARCHAR(20), role TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, author VARCHAR(50), isbn VARCHAR(20), price DECIMAL(10,2) NOT NULL, original_price DECIMAL(10,2), category_id INT, seller_id INT NOT NULL, description TEXT, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_seller (seller_id), INDEX idx_status (status) ) ENGINE=InnoDB; CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, buyer_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_buyer (buyer_id) ) ENGINE=InnoDB; CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, INDEX idx_order (order_id) ) ENGINE=InnoDB;索引加在category_id、seller_id、status上,因为列表查询经常按分类筛选、按卖家查自己的发布、按状态过滤在售图书。order_no加唯一索引,防止重复提交生成相同订单号。
注意:
utf8mb4比utf8多支持 emoji 和部分生僻字,书名里如果出现特殊字符不会报错。
3.3 实体类与 Mapper 映射的对应关系
实体类字段名与表列名用驼峰和下划线对应。比如create_time对应createTime。MyBatis 开启驼峰映射后可以自动转换:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>Mapper 接口定义方法,XML 里写 SQL。以图书查询为例:
public interface BookMapper { List<Book> selectByCategory(@Param("categoryId") Integer categoryId); Book selectById(@Param("id") Integer id); int insert(Book book); int updateStatus(@Param("id") Integer id, @Param("status") Integer status); }对应的 XML:
<select id="selectByCategory" resultType="Book"> SELECT * FROM t_book WHERE status = 0 <if test="categoryId != null"> AND category_id = #{categoryId} </if> ORDER BY create_time DESC </select><if>标签做动态 SQL,分类 ID 为空时查全部在售图书,不为空时按分类过滤。#{}是预编译占位符,能防止 SQL 注入,不要用${}拼接字符串。
4. 核心功能实现:从登录到下单的完整链路
4.1 用户登录与 Session 管理
登录流程:用户提交用户名和密码,Controller 接收后调用 Service 查询用户,比对密码 MD5 值,匹配成功则把用户对象存入 Session,跳转首页;失败则返回登录页并提示错误。
@Controller public class UserController { @Autowired private UserService userService; @PostMapping("/login") public String login(String username, String password, HttpSession session, Model model) { // 密码转 MD5 后比对 String md5Pwd = MD5Util.encode(password); User user = userService.login(username, md5Pwd); if (user == null) { model.addAttribute("msg", "用户名或密码错误"); return "login"; } session.setAttribute("currentUser", user); return "redirect:/book/list"; } }@PostMapping限制只接受 POST 请求,避免密码出现在 URL 里。session.setAttribute把用户信息存到服务端,后续页面通过session.getAttribute("currentUser")判断是否登录。退出登录时调用session.invalidate()清空会话。
拦截器做登录校验,未登录用户访问发布、下单等页面时跳转到登录页:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("currentUser") == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }在spring-mvc.xml里注册拦截器,排除登录、注册、首页等公开路径。
4.2 图书发布与多条件搜索
发布图书时,卖家填写书名、作者、售价、分类、描述,上传封面图可选。Controller 接收表单参数,补全seller_id和create_time,调用 Service 插入数据库。
@PostMapping("/book/publish") public String publish(Book book, HttpSession session) { User user = (User) session.getAttribute("currentUser"); book.setSellerId(user.getId()); book.setStatus(0); bookService.insert(book); return "redirect:/book/my"; }搜索功能支持按书名模糊查、按分类筛选、按价格区间过滤。Mapper 里用动态 SQL 拼接:
<select id="search" resultType="Book"> SELECT * FROM t_book <where> status = 0 <if test="keyword != null and keyword != ''"> AND title LIKE CONCAT('%', #{keyword}, '%') </if> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> </where> ORDER BY create_time DESC </select><where>标签自动处理第一个条件前的 AND,避免 SQL 语法错误。CONCAT('%', #{keyword}, '%')实现模糊匹配,注意%不能写在参数里,否则预编译会失效。
4.3 下单流程与事务控制
下单是最容易出问题的环节。用户点击「立即购买」后,系统需要做三件事:生成订单记录、生成订单明细、把图书状态改为已售。这三步必须在一个事务里完成,任何一步失败都要回滚。
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderItemMapper orderItemMapper; @Autowired private BookMapper bookMapper; @Override @Transactional(rollbackFor = Exception.class) public void createOrder(Integer buyerId, Integer bookId) { // 1. 检查图书是否仍在售 Book book = bookMapper.selectById(bookId); if (book == null || book.getStatus() != 0) { throw new RuntimeException("图书已售出或已下架"); } // 2. 生成订单 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setBuyerId(buyerId); order.setTotalAmount(book.getPrice()); order.setStatus(0); orderMapper.insert(order); // 3. 生成明细 OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setBookId(bookId); item.setPrice(book.getPrice()); orderItemMapper.insert(item); // 4. 更新图书状态 bookMapper.updateStatus(bookId, 1); } }@Transactional(rollbackFor = Exception.class)保证受检异常也回滚。generateOrderNo()用时间戳加随机数生成订单号,比如System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000))。
注意:并发下单同一本书时,两个线程可能同时查到
status = 0,然后都执行更新。解决办法是在 UPDATE 语句里加条件:UPDATE t_book SET status = 1 WHERE id = #{id} AND status = 0,根据返回的影响行数判断是否抢到。
4.4 订单状态流转与页面展示
订单状态用 TINYINT 表示:0 待付款、1 已付款、2 已发货、3 已完成、4 已取消。买家在订单列表页看到自己的订单,卖家在「我卖出的」页面看到待处理订单。状态变更通过 Controller 接收操作指令,Service 校验当前状态是否允许该操作,然后更新。
JSP 页面用 JSTL 遍历订单列表:
<c:forEach items="${orderList}" var="order"> <tr> <td>${order.orderNo}</td> <td>${order.totalAmount}</td> <td> <c:choose> <c:when test="${order.status == 0}">待付款</c:when> <c:when test="${order.status == 1}">已付款</c:when> <c:when test="${order.status == 2}">已发货</c:when> <c:when test="${order.status == 3}">已完成</c:when> <c:otherwise>已取消</c:otherwise> </c:choose> </td> </tr> </c:forEach><c:choose>做状态码到中文的映射,避免在 Java 代码里拼 HTML。页面上的操作按钮根据状态动态显示,比如待付款订单显示「去支付」和「取消订单」,已完成订单只显示「查看详情」。
5. 避坑与排查:课程设计项目最容易翻车的五个地方
5.1 中文乱码:现象是页面显示问号,原因是字符集不统一
现象:JSP 页面输入中文书名,保存到数据库后变成???,或者查询出来显示乱码。
原因:三个环节的字符集不一致——JSP 页面编码、请求编码、数据库连接编码。JSP 默认用 ISO-8859-1,请求体默认也是 ISO-8859-1,MySQL 建库时如果用了 latin1 也会出问题。
解决:JSP 页面顶部加<%@ page contentType="text/html;charset=UTF-8" %>;web.xml 里加 CharacterEncodingFilter 强制请求和响应都用 UTF-8;JDBC URL 加characterEncoding=utf8;建库时指定DEFAULT CHARACTER SET utf8mb4。四处都对齐后乱码消失。
5.2 事务不生效:现象是下单后图书状态没更新,原因是注解没被扫描到
现象:下单方法抛了异常,但订单记录已经插入数据库,没有回滚。
原因:@Transactional注解生效需要 Spring 的事务管理器配置正确,并且该类被 Spring 容器管理。常见错误是在spring-mvc.xml里扫描了 Service 包,导致 Service 被 MVC 容器加载而不是根容器,事务配置没作用到。
解决:检查spring-mvc.xml的<context:component-scan>是否只扫描了 Controller 包。Service 和 Mapper 应该由applicationContext.xml扫描。另外确认applicationContext.xml里有<tx:annotation-driven transaction-manager="transactionManager"/>。
5.3 MyBatis 映射失败:现象是查出来字段为 null,原因是列名和属性名没对上
现象:selectById查出来的 Book 对象,createTime字段是 null,但数据库里明明有值。
原因:数据库列名是create_time,实体类属性是createTime,MyBatis 默认不会自动转换下划线到驼峰,需要手动开启mapUnderscoreToCamelCase。
解决:在mybatis-config.xml里加<setting name="mapUnderscoreToCamelCase" value="true"/>。或者在 XML 里用<resultMap>手动映射。推荐开全局设置,省去每个字段写映射的麻烦。
5.4 静态资源 404:现象是 CSS 和图片加载不出来,原因是 DispatcherServlet 拦截了所有请求
现象:JSP 页面能打开,但样式全没了,浏览器控制台报 404。
原因:web.xml里 DispatcherServlet 的url-pattern设成了/,所有请求都走 Spring MVC,静态资源找不到对应的 Controller。
解决:在spring-mvc.xml里加<mvc:default-servlet-handler/>或<mvc:resources mapping="/static/**" location="/static/"/>。前者把找不到的请求交回给容器默认 Servlet 处理,后者显式指定静态资源路径。
5.5 数据库连接超时:现象是运行一段时间后报连接失效,原因是没配连接池
现象:项目跑了一晚上,第二天打开页面报Communications link failure。
原因:DriverManagerDataSource每次请求新建连接,用完就关,没有连接池管理。MySQL 默认 8 小时不活动会断开连接,下次请求拿到的是失效连接。
解决:换成 Druid 或 HikariCP 连接池,配置validationQuery和testWhileIdle。Druid 的配置示例:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="validationQuery" value="SELECT 1"/> <property name="testWhileIdle" value="true"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> </bean>validationQuery是心跳检测 SQL,testWhileIdle在连接空闲时检测有效性,timeBetweenEvictionRunsMillis控制检测频率。
6. 报告撰写与项目交付:怎么让课程设计拿到高分
6.1 报告结构的关键得分点
课程设计报告不是代码说明书,评审老师看的是「你做了什么、为什么这么做、结果如何」。结构上建议按「需求分析 → 技术选型 → 系统设计 → 实现与测试 → 总结」组织。需求分析部分用用例图说明角色和功能边界,技术选型部分对比至少两种方案并给出选择理由,系统设计部分放 E-R 图和架构图,实现部分贴关键代码并解释逻辑,测试部分列出测试用例和结果。
容易丢分的地方:需求分析写成功能列表,没有角色区分;技术选型只说「用了 Spring」不说为什么不用 EJB;系统设计只有表结构没有关系说明;测试部分只写「功能正常」没有具体用例。反过来,如果每个部分都有对比、有理由、有数据,分数不会低。
6.2 源码整理与可运行性验证
交付源码前做三件事:第一,把数据库连接信息改成通用配置,不要留自己的密码;第二,写一份 README 说明环境要求和启动步骤;第三,在干净环境里跑一遍完整流程——注册、登录、发布、搜索、下单、状态变更。
README 里至少包含:JDK 版本、Maven 版本、MySQL 版本、Tomcat 版本、建库 SQL 文件位置、启动命令。常见做法是提供一个init.sql文件,评审老师导入后直接能跑。
# 导入数据库 mysql -u root -p < init.sql # 编译打包 mvn clean package # 部署到 Tomcat cp target/secondhand-book.war $TOMCAT_HOME/webapps/6.3 从课程设计到可展示项目的进阶方向
如果想让这个项目在面试或作品集里更有说服力,可以在现有基础上加两个功能:一是基于 Redis 的浏览计数和热门图书排行,二是用 WebSocket 做站内消息通知。前者体现缓存设计能力,后者体现实时通信理解。不需要做多复杂,能说清楚「为什么加 Redis」「缓存穿透怎么处理」「WebSocket 握手流程」就够。
我自己的习惯是:每做完一个功能,先不急着写下一个,而是把当前功能的边界情况列出来——空输入、重复提交、并发操作、异常回滚——逐个验证。这个习惯让我在答辩时被问到「如果两个人同时买同一本书怎么办」时能直接说出 UPDATE 加条件判断的方案,而不是现场编。希望帮到你。
本文还有配套的精品资源,点击获取