Java 商品交易实验(第二版)
一、项目概述
本次实验完成的是一个商品交易类后端项目,项目名为 new_shop_finall。项目整体采用 Spring Boot 开发,主要提供用户注册登录、商品发布与查询、商品点赞、订单创建、订单查询、管理员管理等功能。项目没有做复杂的前端页面,重点放在后端接口、数据库操作、登录鉴权、事务处理和缓存使用上。
从代码结构看,这个项目属于比较典型的 Java Web 单体后端项目。它按照控制层、业务层、数据访问层进行划分,接口通过 REST 风格暴露给前端或接口测试工具使用。项目使用 MySQL 保存用户、商品、订单和点赞数据,使用 Redis 缓存商品详情,登录状态则通过 Session 保存。
项目入口类是:
text
src/main/java/com/key/new_shop_finall/NewShopFinallApplication.java
启动后默认端口为 8081,接口文档地址为:
text
http://localhost:8081/swagger-ui.html
二、开发环境与技术栈
本项目最后使用的开发环境如下:
| 类型 | 版本或说明 |
|---|---|
| 操作系统 | Windows 11 |
| JDK | JDK 17 |
| 构建工具 | Maven 3.9.12 |
| 后端框架 | Spring Boot 2.7.18 |
| 数据库 | MySQL |
| 缓存 | Redis |
| 接口文档 | Springdoc OpenAPI |
| 运行端口 | 8081 |
项目主要依赖在 pom.xml 中配置。比较关键的依赖有:
spring-boot-starter-web:用于编写 Web 接口,处理 HTTP 请求。spring-boot-starter-jdbc:用于通过JdbcTemplate操作数据库。spring-boot-starter-validation:用于请求参数校验。spring-security-crypto:用于 BCrypt 密码加密。springdoc-openapi-ui:用于生成 Swagger 接口文档。mysql-connector-j:用于连接 MySQL。lombok:减少实体类和 DTO 中重复的 getter、setter 代码。
项目中已经把 Java 版本统一配置为 17:
xml
<java.version>17</java.version>
这也是后面解决 JDK 25 不兼容问题时最关键的一步。
三、项目制作步骤
1. 创建 Spring Boot 项目
制作项目时,首先创建 Maven 结构的 Spring Boot 项目,并确定包名和项目坐标:
xml
<groupId>com.key</groupId>
<artifactId>new_shop_finall</artifactId>
<version>0.0.1-SNAPSHOT</version>
项目父工程使用的是 Spring Boot 2.7.18:
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
之后创建启动类 NewShopFinallApplication,使用 @SpringBootApplication 标记,让 Spring Boot 自动扫描项目中的 Controller、Service、Repository 等组件。
2. 配置运行参数
项目运行参数写在 application.yml 中,主要包括端口、数据库、Redis 和 Swagger 地址。
核心配置如下:
yaml
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop_date
username: root
redis:
host: localhost
port: 6379
springdoc:
swagger-ui:
path: /swagger-ui.html
这里说明项目默认连接本机的 shop_date 数据库和本机 Redis。如果在其他电脑运行,需要根据自己电脑上的 MySQL 用户名、密码和 Redis 地址修改配置。
3. 搭建分层目录
项目主要目录如下:
text
com.key.new_shop_finall
├── common 通用响应对象和 Session 常量
├── config Web、JDBC、Swagger 等配置
├── controller 接口控制层
├── dao 数据访问接口
├── dao.impl 数据访问实现
├── dto 请求参数和返回对象
├── entity 实体类
├── exception 异常类和全局异常处理
├── intercepetor 登录拦截器
├── service 业务接口
├── service.impl 业务实现
└── util 工具类
这种分层方式比较清楚。Controller 只负责接收请求和返回结果,Service 处理业务规则,DAO 负责 SQL,DTO 负责参数传递和校验,Entity 对应数据库中的主要字段。
4. 统一接口返回格式
项目中定义了 ApiResponse<T>,接口统一返回 code、message、data 三个字段。这样前端或接口测试工具拿到的数据格式比较固定,不需要每个接口单独判断返回结构。
成功时使用:
java
ApiResponse.success(data)
失败时使用:
java
ApiResponse.fail(code, message)
这个设计虽然简单,但在项目接口比较多时很有用。
5. 编写用户模块
用户模块主要在 UserController 和 UserServiceImpl 中实现,包含注册、登录、退出登录、获取当前登录用户等功能。
主要接口有:
text
POST /api/users/register
POST /api/users/login
POST /api/users/logout
GET /api/users/me
注册时先检查用户名是否存在,如果不存在就把密码加密后保存到数据库。登录时先根据用户名查询用户,再校验密码是否正确。登录成功后,系统会把用户信息保存到 Session 中:
java
session.setAttribute(SessionKeys.LOGIN_USER, loginUser);
后面的接口就可以根据 Session 判断用户是否已经登录。
6. 添加登录拦截器
为了避免每个接口都重复写登录判断,项目中写了 LoginInterceptor。它会在请求进入 Controller 前检查 Session 中是否存在 LOGIN_USER。
如果用户没有登录,接口会返回 401;如果已经登录,请求继续执行。
在 WebConfig 中配置了拦截器,同时放行登录、注册、健康检查和 Swagger 文档相关路径:
text
/api/users/login
/api/users/register
/api/health
/swagger-ui.html
/swagger-ui/**
/v3/api-docs/**
/webjars/**
/error
这样登录和注册可以直接访问,其他业务接口则需要先登录。
7. 编写商品模块
商品模块是项目中内容最多的部分,主要代码在 ProductController、ProductServiceImpl 和 ProductDaoImpl 中。
主要功能包括:
- 查询商品详情;
- 分页查询商品;
- 发布商品;
- 修改商品;
- 点赞商品;
- 取消点赞;
- 查询销量排行榜;
- 查询点赞排行榜。
对应接口包括:
text
GET /api/products/{id}
GET /api/products
POST /api/products
PUT /api/products/{id}
POST /api/products/{id}/like
DELETE /api/products/{id}/like
GET /api/products/rank/sales
GET /api/products/rank/likes
商品详情查询时没有每次都直接查数据库,而是先查 Redis。缓存中有数据就直接返回,缓存没有才查 MySQL。查到商品后再写入 Redis。对于不存在的商品,项目也写入了一个短时间的空值缓存,避免一直查询数据库。
8. 编写订单模块
订单模块主要在 OrderController、OrderServiceImpl 和 OrderDaoImpl 中实现。
主要接口有:
text
POST /api/orders
GET /api/orders/my
GET /api/orders/sold
DELETE /api/orders/{id}
创建订单时,业务流程大致是:
- 查询商品是否存在;
- 判断商品是否处于上架状态;
- 判断库存是否足够;
- 扣减库存;
- 创建订单;
- 增加商品销量;
- 清除商品详情缓存。
这一段业务使用了 @Transactional,因为扣库存和创建订单必须保持一致。如果中间步骤失败,事务会回滚,避免库存和订单数据不一致。
9. 编写管理员模块
管理员接口集中在 AdminController 中,主要用于管理普通用户和商品。
管理员相关接口包括:
text
GET /api/admin/users
PUT /api/admin/users/{id}
PUT /api/admin/users/{id}/status
PUT /api/admin/users/{id}/disable
DELETE /api/admin/users/{id}
PUT /api/admin/products/{id}/status
DELETE /api/admin/products/{id}
管理员权限通过 AuthUtils.requireAdmin(...) 判断。项目中约定 userType 为 1 的用户是管理员,普通用户不能操作管理员接口。
10. 添加异常处理
项目中使用 GlobalExceptionHandler 统一处理异常。它主要处理业务异常、参数校验异常、数据库异常和其他未知异常。
这样做的好处是接口不会直接把 Java 错误堆栈返回给前端,而是返回统一格式的数据。例如参数错误时返回 400,未登录时返回 401,没有管理员权限时返回 403。
四、技术要点分析
1. Spring Boot 自动配置
项目使用 Spring Boot 后,很多配置不需要手动写 XML。只要在类上添加对应注解,Spring 就可以自动创建对象并完成依赖注入。
例如:
@RestController用于接口类;@Service用于业务类;@Repository用于数据访问类;@Configuration用于配置类;@Bean用于手动注册对象。
这让项目的搭建过程比较快,也减少了配置文件数量。
2. REST 接口设计
项目接口基本按照 REST 风格设计。查询用 GET,新增用 POST,修改用 PUT,删除用 DELETE。接口路径也比较直观,比如商品详情是 /api/products/{id},订单列表是 /api/orders/my。
这种设计对接口测试比较友好,也便于后期接前端页面。
3. Session 登录鉴权
本项目没有使用 Token 或 JWT,而是使用 Session。用户登录成功后,服务端保存登录用户信息,浏览器或接口工具通过 Cookie 维持会话。
这种方式实现起来更直接,适合本项目这种课程设计规模的系统。不过如果后期要做分布式部署,就需要考虑 Session 共享或改成 Token 鉴权。
4. BCrypt 密码加密
项目没有直接保存明文密码,而是使用 BCryptPasswordEncoder 加密。注册时加密,登录时匹配。
相关工具类是:
text
src/main/java/com/key/new_shop_finall/util/PasswordUtils.java
这一点比较重要,因为如果直接保存明文密码,一旦数据库泄露,用户密码就会完全暴露。使用 BCrypt 后,即使拿到数据库中的密码字段,也不能直接还原出原密码。
5. 参数校验
项目中的请求对象使用了很多校验注解,例如:
java
@NotBlank
@NotNull
@Size
@Min
@Max
@Email
@Pattern
@DecimalMin
例如注册用户时,用户名不能为空,密码长度需要符合要求,邮箱要符合邮箱格式,手机号也使用正则表达式限制。
这些校验放在 DTO 中,比在业务代码里写大量 if 判断更清晰。
6. JdbcTemplate 操作数据库
项目没有使用 MyBatis,而是直接使用 JdbcTemplate 和 NamedParameterJdbcTemplate 写 SQL。
这样做的优点是 SQL 很直观,可以清楚看到每个接口到底执行了什么数据库语句。缺点是代码量比 MyBatis 多一些,尤其是 RowMapper 需要自己写。
分页查询中使用了动态 SQL,比如商品查询可以按照关键字、分类、状态、价格范围和时间范围筛选。
7. 事务控制
订单创建和点赞等操作使用了 @Transactional(rollbackFor = Exception.class)。这些操作不只是改一张表,例如创建订单会同时影响订单表、商品库存、销量和缓存。
如果不加事务,就可能出现扣了库存但订单没有创建成功的情况。加上事务后,只要业务中出现异常,数据库操作就会回滚。
8. Redis 缓存
项目中商品详情使用 Redis 缓存,key 的格式是:
text
shop:product:detail:{id}
正常商品缓存 30 分钟,不存在的商品缓存 3 分钟。商品被修改、删除、下单或点赞变化时,会主动删除缓存。
比较有特点的是,项目没有直接引入 Spring Data Redis,而是自己写了 RedisCacheUtil,通过 Socket 按 Redis RESP 协议发送 GET、SETEX、DEL 命令。这个实现比直接使用现成库更底层,也能帮助理解 Redis 客户端和服务端之间是怎么通信的。
9. 逻辑删除
项目中很多删除操作不是直接删除数据库记录,而是把 is_deleted 设置为 1。
例如商品删除:
sql
update product set is_deleted = 1 where id = ? and is_deleted = 0
查询时再统一加上 is_deleted = 0。这种方式可以保留历史数据,也方便以后做数据恢复或审计。
10. Swagger 接口文档
项目集成了 Springdoc OpenAPI,启动后可以访问:
text
http://localhost:8081/swagger-ui.html
在没有前端页面的情况下,Swagger 对测试接口很方便。可以直接查看接口路径、请求方式、参数格式和返回结构。
五、数据库设计分析
从 DAO 层 SQL 可以看出,项目主要使用了四张表:user、product、order、like。
user 表保存用户信息,包括用户名、密码、邮箱、手机号、头像、用户类型、状态和逻辑删除标记。
product 表保存商品信息,包括商品名、描述、分类、价格、库存、图片、发布者、状态、点赞数、销量和浏览量。
order 表保存订单信息,包括订单号、买家、商品、购买数量、总价、订单状态和支付时间。
like 表保存用户和商品之间的点赞关系,用来判断某个用户是否已经点赞过某个商品。
这几个表之间的关系比较直接。用户可以发布商品,也可以买商品生成订单;商品可以被多个用户点赞;订单关联买家和商品。
六、遇到的问题与解决方法
1. JDK 25 与项目不兼容
实验开始时,我在 IDE 中使用的是 JDK 25。刚开始以为 JDK 越新越好,但实际运行和配置项目时发现并不合适。
项目使用的是 Spring Boot 2.7.18,这个版本更适合 JDK 8、JDK 11、JDK 17 这类长期支持版本。使用 JDK 25 时,可能会遇到下面这些问题:
- IDE 项目 SDK 和 Maven 使用的 JDK 不一致;
- Maven 编译插件对过新的 JDK 支持不稳定;
- Lombok 注解处理可能出现兼容问题;
- Spring Boot 2.7.x 的部分依赖没有针对 JDK 25 做完整适配;
- 编译版本、运行版本和语言级别容易混乱。
后来我把环境统一改成 JDK 17,具体做法是:
- 安装 JDK 17;
- 在 IDE 的 Project Structure 中把 Project SDK 改成 JDK 17;
- 把模块的 Language Level 改成 17;
- 在 Maven Runner 中选择 JDK 17;
- 修改
pom.xml,设置<java.version>17</java.version>; - 重新刷新 Maven 依赖;
- 执行
mvn test验证项目是否能正常编译。
改完之后,项目能够正常编译,Spring Boot 测试也可以通过。这个问题说明,做 Java 项目时并不是 JDK 越新越合适,还是要看框架版本和依赖生态。
2. IDE 和命令行 JDK 不一致
调试时还发现一个容易忽略的问题:IDE 中看到的 JDK 版本和命令行实际使用的 JDK 版本可能不是同一个。
所以后面我用下面两个命令确认环境:
bash
java -version
mvn -v
当前验证时使用的是 Java 17.0.18 和 Maven 3.9.12。确认这两个命令显示的 JDK 是 17 后,再运行项目会稳很多。
3. 中文乱码问题
查看源码时,部分中文注解和提示信息在 Windows 终端里显示成乱码。这个问题主要和编码有关。项目文件一般按 UTF-8 保存,但 Windows 控制台默认编码可能是 GBK,直接输出文件内容时就容易显示不正常。
解决方法是:
- IDE 文件编码统一设置为 UTF-8;
- Maven 资源编码保持 UTF-8;
- 尽量在 IDE 中查看源码中的中文;
- 如果必须在终端查看,可以切换终端编码后再看。
这个问题不一定影响编译,但会影响阅读和后期维护。
4. MySQL 和 Redis 服务依赖
项目运行时依赖 MySQL。数据库没有启动、库名不对、账号密码不对,都会导致接口访问失败。
商品详情缓存还依赖 Redis。如果 Redis 没有启动,项目不会完全不能运行,但商品详情缓存就不能正常使用,只能退回数据库查询。
所以运行项目之前,需要先检查:
- MySQL 是否启动;
- 是否已经创建
shop_date数据库; application.yml中账号密码是否正确;- Redis 是否启动;
- Redis 端口是否为
6379。
5. Maven settings 文件问题
一开始尝试使用自定义 settings 文件运行:
bash
mvn -s .mvn/local-settings.xml test
但是项目中没有 .mvn/local-settings.xml,所以 Maven 会报错。后来直接使用:
bash
mvn test
项目可以正常测试通过。这个问题说明,命令中的配置文件路径必须真实存在,否则 Maven 不会继续执行。
七、项目使用方法
1. 准备环境
先确认本机安装 JDK 17:
bash
java -version
再确认 Maven 可以使用:
bash
mvn -v
建议两个命令中显示的 Java 版本都为 17。
2. 准备数据库
启动 MySQL 后,创建数据库:
sql
create database shop_date default character set utf8mb4 collate utf8mb4_general_ci;
然后根据项目实体和 DAO 中的 SQL 创建 user、product、order、like 表。
如果本机 MySQL 密码和项目配置不同,需要修改 application.yml:
yaml
spring:
datasource:
username: root
password: 你的数据库密码
3. 启动 Redis
启动 Redis,确认地址为:
text
localhost:6379
如果 Redis 地址或端口不同,也需要修改 application.yml。
4. 构建项目
在项目根目录执行:
bash
mvn clean package
或者只做测试:
bash
mvn test
本次实验中,我执行 mvn test 后结果为:
text
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
5. 启动项目
可以使用 Maven 启动:
bash
mvn spring-boot:run
也可以直接运行主类:
text
com.key.new_shop_finall.NewShopFinallApplication
启动成功后,访问地址为:
text
http://localhost:8081
接口文档地址为:
text
http://localhost:8081/swagger-ui.html
6. 基本测试流程
实际测试时可以按下面顺序来:
- 调用
/api/users/register注册用户; - 调用
/api/users/login登录,并保留 Cookie; - 调用
/api/products发布商品; - 调用
/api/products查询商品列表; - 调用
/api/products/{id}查询商品详情; - 调用
/api/products/{id}/like点赞; - 调用
/api/orders创建订单; - 调用
/api/orders/my查询自己的订单; - 使用管理员账号登录后,测试
/api/admin下的管理接口。
注册请求示例:
http
POST /api/users/register
Content-Type: application/json
{
"username": "testuser",
"password": "123456",
"email": "test@example.com",
"phone": "13800138000"
}
登录请求示例:
http
POST /api/users/login
Content-Type: application/json
{
"username": "testuser",
"password": "123456"
}
发布商品示例:
http
POST /api/products
Content-Type: application/json
{
"name": "测试商品",
"description": "这是一个测试商品",
"categoryId": 1,
"categoryName": "数码",
"price": 99.99,
"stock": 20,
"imageUrl": "https://example.com/product.png"
}
创建订单示例:
http
POST /api/orders
Content-Type: application/json
{
"productId": 1,
"quantity": 2
}
八、测试结果
本次在项目根目录执行:
bash
mvn test
测试结果为:
text
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
说明项目在 JDK 17 环境下可以正常编译,Spring Boot 上下文也能正常加载。虽然当前测试用例比较基础,只验证了项目能启动,但至少可以确认环境配置和依赖没有明显问题。
九、总结
通过这次实验,我对 Spring Boot 后端项目的基本开发流程有了更完整的理解。项目从创建工程、配置依赖、连接数据库,到编写 Controller、Service、DAO,再到加入登录拦截、异常处理、事务和 Redis 缓存,整体流程比较完整。
本项目比较有价值的地方是功能虽然不算特别复杂,但覆盖了后端开发中很常见的内容,例如 REST 接口、Session 登录、BCrypt 密码加密、参数校验、JdbcTemplate SQL 操作、事务控制、逻辑删除、Redis 缓存和 Swagger 文档。
开发过程中印象最深的问题是 JDK 25 与项目环境不兼容。最后把 IDE、Maven 和 pom.xml 都统一到 JDK 17 后,项目才稳定通过测试。这也说明做项目时环境版本要和框架生态匹配,不能只追求最新版本。
总体来说,本项目达到了商品交易后端系统的基本要求,也为后续继续扩展前端页面、支付功能、商品分类管理、订单状态流转和更完善的测试打下了基础。成,具有较完整的实验和学习价值。