day7/3
团购系统开发完整笔记(含详细操作步骤)
一、项目架构与技术栈
1. 架构设计
采用经典MVC分层架构,各层职责清晰,实现代码解耦:
- PO层(实体类) :映射数据库表结构,与数据库字段一一对应
- Mapper层(数据访问层) :负责数据库CRUD操作,基于MyBatis-Plus实现
- Service层(业务逻辑层) :封装核心业务逻辑,通过接口与实现类分离
- Controller层(控制层) :接收前端请求,调用Service处理,返回响应结果
2. 技术栈详情
技术组件 | 版本 | 作用 |
---|---|---|
Spring Boot | 2.7.6 | 快速开发框架,整合Spring生态 |
MyBatis-Plus | 3.5.1 | ORM框架,简化数据库操作 |
MySQL | 8.0 | 关系型数据库,存储业务数据 |
Lombok | 1.18.24 | 简化代码,自动生成getter/setter等 |
JUnit 5 | 5.9.2 | 单元测试框架,验证代码功能 |
Maven | 3.6.3 | 项目构建与依赖管理工具 |
二、项目初始化(IDEA操作)
1. 创建Spring Boot项目
-
打开IDEA →
File → New → Project
-
左侧选择
Spring Initializr
,右侧选择JDK 17 →Next
-
填写项目信息:
- Group:
org.example
- Artifact:
group-buying
- Package name:
org.example
- Java Version:17
- Group:
-
选择依赖:
- 必选:Spring Web、MyBatis-Plus、MySQL Driver、Lombok
-
选择项目保存路径 →
Finish
2. 项目结构整理
在IDEA的项目面板中手动创建分层目录:
src/main/java/org/example/
├─ po/ # 实体类
├─ mapper/ # Mapper接口
├─ service/ # Service接口
│ └─ impl/ # Service实现类
├─ controller/ # 控制器
└─ config/ # 配置类
src/main/resources/
└─ application.yml # 配置文件
三、核心配置文件
1. 数据库与框架配置(application.yml
)
server:
port: 8081 # 项目启动端口
spring:
datasource: # 数据库连接配置
url: jdbc:mysql://localhost:3306/group_buying_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root # 数据库用户名
password: '' # 替换为本地数据库密码
mybatis-plus: # MyBatis-Plus配置
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL日志
global-config:
db-config:
table-prefix: tb_ # 数据库表前缀,实体类无需包含前缀
id-type: auto # 主键自增策略
2. 启动类配置(Main.java
)
package org.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("org.example.mapper") // 扫描Mapper接口所在包
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
IDEA操作:
在org.example
包下创建Main.java
,添加上述代码,通过@MapperScan
指定Mapper接口路径,避免手动添加@Mapper
注解。
四、数据库设计与实现
1. 数据库创建(MySQL操作)
-- 创建数据库并设置编码
CREATE DATABASE IF NOT EXISTS group_buying_db CHARACTER SET utf8mb4;
USE group_buying_db; -- 切换到目标数据库
2. 核心表设计
(1)用户表(tb_user)
CREATE TABLE tb_user(
user_id INT PRIMARY KEY auto_increment COMMENT '用户编号',
user_name varchar(100) NOT NULL UNIQUE COMMENT '登录名称',
user_pwd varchar(100) NOT NULL COMMENT '登录密码',
nike_name varchar(100) COMMENT '昵称',
user_picture text COMMENT '个人头像',
user_desc varchar(255) COMMENT '个人简介',
user_address text COMMENT '收货地址',
create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time datetime ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
status int DEFAULT 1 COMMENT '1正常 2封禁'
) COMMENT '用户表';
-- 初始化管理员数据
INSERT INTO tb_user (user_name, user_pwd, nike_name)
VALUES ('admin', 'admin123', '系统管理员');
(2)商品表(tb_product)
CREATE TABLE tb_product(
product_id int primary key auto_increment COMMENT '商品ID',
product_name varchar(100) NOT NULL COMMENT '商品名称',
product_brand varchar(100) COMMENT '商品品牌',
product_description varchar(255) COMMENT '商品描述',
main_image text COMMENT '主图',
inventory int NOT NULL DEFAULT 0 COMMENT '库存',
detail_images varchar(255) COMMENT '详情图片,用逗号分隔',
selling_price DECIMAL(10,2) NOT NULL COMMENT '销售价格',
group_price DECIMAL(10,2) NOT NULL COMMENT '团购价格',
create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time datetime ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'
) COMMENT '商品表';
IDEA操作:
通过Database
面板连接MySQL后,右键数据库 → New → Query Console
,执行上述SQL语句。
五、分层代码实现(含IDEA操作)
1. PO层(实体类)
(1)User实体类
package org.example.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.util.Date;
@Data // Lombok注解,自动生成getter/setter/toString等
public class User {
@TableId(type = IdType.AUTO) // 主键自增策略
private Integer userId;
private String userName; // 登录名称
private String userPwd; // 登录密码
private String nikeName; // 昵称(注意:数据库字段为nike_name,实体类驼峰命名)
private String userPicture; // 个人头像
private String userDesc; // 个人简介
private String userAddress; // 收货地址
private Date createTime; // 创建时间
@TableField("update_time") // 解决字段名不一致(数据库是下划线,实体类是驼峰)
private Date updateTime; // 修改时间
private Integer status; // 状态(1正常 2封禁)
}
IDEA操作:
在po
包右键 → New → Java Class
→ 输入User
→ 粘贴代码,Lombok报错时点击Install Lombok Plugin
安装插件。
(2)Product实体类
package org.example.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class Product {
@TableId(type = IdType.AUTO)
private Integer productId;
private String productName; // 商品名称
private String productBrand; // 商品品牌
private String productDescription; // 商品描述
private String mainImage; // 主图
private Integer inventory; // 库存
private String detailImages; // 详情图片
private BigDecimal sellingPrice; // 销售价格(使用BigDecimal避免精度问题)
private BigDecimal groupPrice; // 团购价格
private Date createTime; // 创建时间
private Date updateTime; // 修改时间
}
2. Mapper层(数据访问)
(1)UserMapper接口
package org.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.example.po.User;
// 继承BaseMapper获得基础CRUD方法
public interface UserMapper extends BaseMapper<User> {
// 无需手动编写方法,MyBatis-Plus已实现selectById/insert/update等
}
IDEA操作:
在mapper
包右键 → New → Interface
→ 输入UserMapper
→ 继承BaseMapper<User>
,按Alt+Enter
导入依赖。
(2)ProductMapper接口
package org.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.example.po.Product;
public interface ProductMapper extends BaseMapper<Product> {
}
3. Service层(业务逻辑)
(1)UserService接口
package org.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.example.po.User;
import java.util.List;
// 继承IService获得批量操作等高级方法
public interface UserService extends IService<User> {
// 自定义业务方法:根据用户名查询
User getByUsername(String username);
// 自定义业务方法:查询活跃用户(status=1)
List<User> getActiveUsers();
}
IDEA操作:
在service
包右键 → New → Interface
→ 输入UserService
→ 继承IService<User>
。
(2)UserServiceImpl实现类
package org.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.mapper.UserMapper;
import org.example.po.User;
import org.example.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service // 注册为Spring服务组件
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User getByUsername(String username) {
// 使用LambdaQueryWrapper构建查询条件(类型安全)
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, username); // 等价于 WHERE user_name = ?
return baseMapper.selectOne(queryWrapper);
}
@Override
public List<User> getActiveUsers() {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getStatus, 1); // 查询状态正常的用户
return baseMapper.selectList(queryWrapper);
}
}
IDEA操作:
在service/impl
包右键 → New → Java Class
→ 输入UserServiceImpl
→ 实现UserService
接口并继承ServiceImpl
。
(3)ProductService接口
package org.example.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.example.po.Product;
public interface ProductService extends IService<Product> {
// 分页查询商品
IPage<Product> getProductByPage(int pageNum, int pageSize);
// 带条件的分页查询(扩展)
IPage<Product> getProductByPageWithCondition(int pageNum, int pageSize, BigDecimal maxPrice);
}
(4)ProductServiceImpl实现类
package org.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.mapper.ProductMapper;
import org.example.po.Product;
import org.example.service.ProductService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
@Override
public IPage<Product> getProductByPage(int pageNum, int pageSize) {
// 1. 创建分页对象,指定页码和每页条数
Page<Product> page = new Page<>(pageNum, pageSize);
// 2. 执行分页查询(无查询条件)
return baseMapper.selectPage(page, null);
}
@Override
public IPage<Product> getProductByPageWithCondition(int pageNum, int pageSize, BigDecimal maxPrice) {
Page<Product> page = new Page<>(pageNum, pageSize);
// 添加查询条件:价格低于maxPrice
LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.lt(Product::getSellingPrice, maxPrice); // WHERE selling_price < ?
return baseMapper.selectPage(page, queryWrapper);
}
}
4. Controller层(接口)
(1)UserController
package org.example.controller;
import org.example.po.User;
import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController // 标识为REST接口,返回JSON格式
@RequestMapping("/api/user") // 统一接口前缀
public class UserController {
@Autowired // 自动注入UserService
private UserService userService;
// 1. 查询所有用户(GET请求)
@GetMapping
public List<User> getAllUsers() {
return userService.list(); // 调用Service的list()方法
}
// 2. 根据ID查询用户
@GetMapping("/{id}")
public User getUserById(@PathVariable Integer id) { // @PathVariable获取路径参数
return userService.getById(id);
}
// 3. 新增用户(POST请求)
@PostMapping
public boolean addUser(@RequestBody User user) { // @RequestBody接收JSON参数
return userService.save(user);
}
// 4. 更新用户(PUT请求)
@PutMapping
public boolean updateUser(@RequestBody User user) {
return userService.updateById(user);
}
// 5. 删除用户(DELETE请求)
@DeleteMapping("/{id}")
public boolean deleteUser(@PathVariable Integer id) {
return userService.removeById(id);
}
// 6. 根据用户名查询
@GetMapping("/name/{username}")
public User getUserByUsername(@PathVariable String username) {
return userService.getByUsername(username);
}
// 7. 查询活跃用户
@GetMapping("/active")
public List<User> getActiveUsers() {
return userService.getActiveUsers();
}
}
IDEA操作:
在controller
包右键 → New → Java Class
→ 输入UserController
→ 添加上述代码,@Autowired
报错可忽略(不影响运行)。
(2)ProductController(含分页)
package org.example.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.example.po.Product;
import org.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@RequestMapping("/api/product")
public class ProductController {
@Autowired
private ProductService productService;
// 分页查询商品
@GetMapping("/page")
public IPage<Product> getProductPage(
@RequestParam(defaultValue = "1") int pageNum, // 默认查询第1页
@RequestParam(defaultValue = "10") int pageSize // 默认每页10条
) {
return productService.getProductByPage(pageNum, pageSize);
}
// 带条件的分页查询(例如:查询价格低于5000的商品)
@GetMapping("/page/condition")
public IPage<Product> getProductPageWithCondition(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam BigDecimal maxPrice // 价格上限
) {
return productService.getProductByPageWithCondition(pageNum, pageSize, maxPrice);
}
}
5. 分页插件配置
package org.example.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 标识为配置类,Spring 启动时自动加载
public class MyBatisPlusConfig {
/**
注册 MyBatis-Plus 分页插件
作用:自动拦截 SQL 并添加分页语句(如 LIMIT)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor () {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor ();
// 添加分页拦截器,支持 MySQL、Oracle 等多种数据库
interceptor.addInnerInterceptor (new PaginationInnerInterceptor ());
return interceptor;
}
}
IDEA操作:
在config包右键 → New → Java Class → 输入MyBatisPlusConfig → 粘贴代码,确保@Configuration和@Bean注解正确导入。
六、测试环节(IDEA操作)
-
单元测试(Service层)
(1)UserService测试
package org.example.service; import org.example.po.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest // 加载Spring上下文环境 public class UserServiceTest { @Autowired private UserService userService; // 测试根据用户名查询 @Test public void testGetByUsername() { User user = userService.getByUsername("admin"); assertNotNull(user, "查询结果不应为null"); // 断言用户存在 assertEquals("系统管理员", user.getNikeName(), "昵称匹配失败"); } // 测试查询活跃用户 @Test public void testGetActiveUsers() { List<User> activeUsers = userService.getActiveUsers(); assertFalse(activeUsers.isEmpty(), "活跃用户列表不应为空"); // 断言列表非空 } }
操作步骤:
在src/test/java/org/example/service
下创建测试类 → 右键测试方法 → Run 'testGetByUsername()'
→ 查看控制台输出结果。
2. 接口测试(Postman/IDEA 工具)
(1)UserController 接口测试
接口功能 | 请求方式 | 请求地址 | 请求参数 | 预期结果 |
---|---|---|---|---|
查询所有用户 | GET | http://localhost:8081/api/user | 无 | 返回用户列表 JSON |
根据 ID 查询 | GET | http://localhost:8081/api/user/1 | 无 | 返回 ID=1 的用户信息 |
新增用户 | POST | http://localhost:8081/api/user | {"userName":"test","userPwd":"123456"} | 返回true |
更新用户 | PUT | http://localhost:8081/api/user | {"userId":2,"nikeName":"测试用户"} | 返回true |
删除用户 | DELETE | http://localhost:8081/api/user/2 | 无 | 返回true |
IDEA 内置工具测试:
Tools → HTTP Client → Test RESTful Web Service
→ 输入 URL 和方法 → 点击Send
→ 查看响应结果。
(2)ProductController 分页测试
-
分页查询所有商品:
GET http://localhost:8081/api/product/page?pageNum=1&pageSize=2
响应示例:
json{ "records": [ // 当前页数据 {"productId":1,"productName":"商品1",...}, {"productId":2,"productName":"商品2",...} ], "total": 10, // 总条数 "size": 2, // 每页条数 "current": 1, // 当前页码 "pages": 5 // 总页数 }
-
带条件的分页查询:
GET http://localhost:8081/api/product/page/condition?pageNum=1&pageSize=2&maxPrice=5000
预期结果:返回价格低于 5000 的商品分页列表。
七、项目扩展方向
- 统一响应格式:封装
Result
类,包含code
(状态码)、msg
(消息)、data
(数据) - 异常处理:使用
@ControllerAdvice
定义全局异常处理器,统一捕获并处理异常 - 权限控制:集成 Spring Security 实现用户登录认证与权限管理
- 缓存优化:添加 Redis 缓存热门商品数据,减少数据库访问压力
- 前端集成:使用 Vue/React 开发前端页面,调用后端 REST 接口实现交互
八、常见问题与解决
- Lombok 注解无效:
解决:安装 Lombok 插件(File → Settings → Plugins → 搜索Lombok → Install
),重启 IDEA。 - 分页查询返回所有数据:
解决:检查MyBatisPlusConfig
是否正确配置,确保分页插件已注册。 - 数据库连接失败:
解决:检查application.yml
中数据库密码是否正确,MySQL 服务是否启动,端口是否被占用。 - Mapper 接口注入失败:
解决:确认启动类上的@MapperScan("org.example.mapper")
路径正确,Mapper 接口是否在指定包下。
通过以上步骤,可完整实现团购系统的基础功能,包括用户管理、商品管理及分页查询等核心模块。代码遵循分层架构设计,便于后期维护和扩展。