day7/3

0 阅读1分钟

day7/3

团购系统开发完整笔记(含详细操作步骤)

一、项目架构与技术栈

1. 架构设计

采用经典MVC分层架构,各层职责清晰,实现代码解耦:

  • PO层(实体类) :映射数据库表结构,与数据库字段一一对应
  • Mapper层(数据访问层) :负责数据库CRUD操作,基于MyBatis-Plus实现
  • Service层(业务逻辑层) :封装核心业务逻辑,通过接口与实现类分离
  • Controller层(控制层) :接收前端请求,调用Service处理,返回响应结果

2. 技术栈详情

技术组件版本作用
Spring Boot2.7.6快速开发框架,整合Spring生态
MyBatis-Plus3.5.1ORM框架,简化数据库操作
MySQL8.0关系型数据库,存储业务数据
Lombok1.18.24简化代码,自动生成getter/setter等
JUnit 55.9.2单元测试框架,验证代码功能
Maven3.6.3项目构建与依赖管理工具

二、项目初始化(IDEA操作)

1. 创建Spring Boot项目

  1. 打开IDEA → File → New → Project

  2. 左侧选择Spring Initializr​,右侧选择JDK 17 → Next

  3. 填写项目信息:

    • Group:org.example
    • Artifact:group-buying
    • Package name:org.example
    • Java Version:17
  4. 选择依赖:

    • 必选:Spring Web、MyBatis-Plus、MySQL Driver、Lombok
  5. 选择项目保存路径 → 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操作)

  1. 单元测试(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 接口测试
接口功能请求方式请求地址请求参数预期结果
查询所有用户GEThttp://localhost:8081/api/user返回用户列表 JSON
根据 ID 查询GEThttp://localhost:8081/api/user/1返回 ID=1 的用户信息
新增用户POSThttp://localhost:8081/api/user{"userName":"test","userPwd":"123456"}返回true
更新用户PUThttp://localhost:8081/api/user{"userId":2,"nikeName":"测试用户"}返回true
删除用户DELETEhttp://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 的商品分页列表。

七、项目扩展方向

  1. 统一响应格式:封装Result​类,包含code​(状态码)、msg​(消息)、data​(数据)
  2. 异常处理:使用@ControllerAdvice​定义全局异常处理器,统一捕获并处理异常
  3. 权限控制:集成 Spring Security 实现用户登录认证与权限管理
  4. 缓存优化:添加 Redis 缓存热门商品数据,减少数据库访问压力
  5. 前端集成:使用 Vue/React 开发前端页面,调用后端 REST 接口实现交互

八、常见问题与解决

  1. Lombok 注解无效
    解决:安装 Lombok 插件(File → Settings → Plugins → 搜索Lombok → Install​),重启 IDEA。
  2. 分页查询返回所有数据
    解决:检查MyBatisPlusConfig​是否正确配置,确保分页插件已注册。
  3. 数据库连接失败
    解决:检查application.yml​中数据库密码是否正确,MySQL 服务是否启动,端口是否被占用。
  4. Mapper 接口注入失败
    解决:确认启动类上的@MapperScan("org.example.mapper")​路径正确,Mapper 接口是否在指定包下。

通过以上步骤,可完整实现团购系统的基础功能,包括用户管理、商品管理及分页查询等核心模块。代码遵循分层架构设计,便于后期维护和扩展。