Skip to content

接口开发指南

本文档介绍如何在 youlai-boot 项目中开发新的接口,包括增删改查的完整流程。

开发流程

接口开发遵循以下步骤:

1. 设计数据库表 → 2. 创建实体类 → 3. 创建 Mapper → 4. 创建 Service → 5. 创建 Controller → 6. 测试接口

实战案例:商品管理

我们通过一个完整的商品管理模块来演示接口开发的全流程。

第一步:设计数据库表

创建商品表 pms_goods

sql
CREATE TABLE `pms_goods` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) NOT NULL COMMENT '商品名称',
  `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',
  `price` decimal(10,2) NOT NULL COMMENT '价格',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存',
  `pic_url` varchar(500) DEFAULT NULL COMMENT '商品图片',
  `description` text COMMENT '商品描述',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(1:上架 0:下架)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

第二步:创建实体类

model/entity 包下创建 PmsGoods.java

java
package com.youlai.boot.pms.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.youlai.boot.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.math.BigDecimal;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pms_goods")
public class PmsGoods extends BaseEntity {

    /**
     * 商品名称
     */
    private String name;

    /**
     * 分类ID
     */
    private Long categoryId;

    /**
     * 价格
     */
    private BigDecimal price;

    /**
     * 库存
     */
    private Integer stock;

    /**
     * 商品图片
     */
    private String picUrl;

    /**
     * 商品描述
     */
    private String description;

    /**
     * 状态(1:上架 0:下架)
     */
    private Integer status;
}

第三步:创建 Mapper

mapper 包下创建 PmsGoodsMapper.java

java
package com.youlai.boot.pms.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.pms.model.entity.PmsGoods;
import com.youlai.boot.pms.model.query.GoodsPageQuery;
import com.youlai.boot.pms.model.vo.GoodsVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 商品 Mapper
 */
@Mapper
public interface PmsGoodsMapper extends BaseMapper<PmsGoods> {

    /**
     * 获取商品分页列表
     */
    Page<GoodsVO> getGoodsPage(
        Page<GoodsVO> page,
        @Param("query") GoodsPageQuery query
    );
}

创建对应的 XML 文件 PmsGoodsMapper.xml(在 resources/mapper 目录下):

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.youlai.boot.pms.mapper.PmsGoodsMapper">

    <select id="getGoodsPage" resultType="com.youlai.boot.pms.model.vo.GoodsVO">
        SELECT
            id,
            name,
            category_id,
            price,
            stock,
            pic_url,
            status,
            create_time
        FROM pms_goods
        <where>
            deleted = 0
            <if test="query.name != null and query.name != ''">
                AND name LIKE CONCAT('%', #{query.name}, '%')
            </if>
            <if test="query.categoryId != null">
                AND category_id = #{query.categoryId}
            </if>
            <if test="query.status != null">
                AND status = #{query.status}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>

</mapper>

第四步:创建 Model 类

GoodsForm.java - 表单类

java
package com.youlai.boot.pms.model.form;

import lombok.Data;

import jakarta.validation.constraints.*;
import java.math.BigDecimal;

@Data
public class GoodsForm {

    /**
     * 商品ID(编辑时需要)
     */
    private Long id;

    /**
     * 商品名称
     */
    @NotBlank(message = "商品名称不能为空")
    @Size(max = 100, message = "商品名称长度不能超过100")
    private String name;

    /**
     * 分类ID
     */
    @NotNull(message = "分类不能为空")
    private Long categoryId;

    /**
     * 价格
     */
    @NotNull(message = "价格不能为空")
    @DecimalMin(value = "0.01", message = "价格必须大于0")
    private BigDecimal price;

    /**
     * 库存
     */
    @NotNull(message = "库存不能为空")
    @Min(value = 0, message = "库存不能为负数")
    private Integer stock;

    /**
     * 商品图片
     */
    private String picUrl;

    /**
     * 商品描述
     */
    private String description;

    /**
     * 状态(1:上架 0:下架)
     */
    private Integer status;
}

GoodsPageQuery.java - 查询类

java
package com.youlai.boot.pms.model.query;

import com.youlai.boot.common.base.BasePageQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class GoodsPageQuery extends BasePageQuery {

    /**
     * 商品名称(模糊查询)
     */
    private String name;

    /**
     * 分类ID
     */
    private Long categoryId;

    /**
     * 状态
     */
    private Integer status;
}

GoodsVO.java - 视图对象

java
package com.youlai.boot.pms.model.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class GoodsVO {

    /**
     * 商品ID
     */
    private Long id;

    /**
     * 商品名称
     */
    private String name;

    /**
     * 分类ID
     */
    private Long categoryId;

    /**
     * 分类名称
     */
    private String categoryName;

    /**
     * 价格
     */
    private BigDecimal price;

    /**
     * 库存
     */
    private Integer stock;

    /**
     * 商品图片
     */
    private String picUrl;

    /**
     * 状态(1:上架 0:下架)
     */
    private Integer status;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
}

第五步:创建 Service

GoodsService.java - 接口

java
package com.youlai.boot.pms.service;

import com.youlai.boot.common.model.PageResult;
import com.youlai.boot.pms.model.form.GoodsForm;
import com.youlai.boot.pms.model.query.GoodsPageQuery;
import com.youlai.boot.pms.model.vo.GoodsVO;

public interface GoodsService {

    /**
     * 商品分页列表
     */
    PageResult<GoodsVO> getGoodsPage(GoodsPageQuery query);

    /**
     * 商品详情
     */
    GoodsForm getGoodsDetail(Long id);

    /**
     * 新增商品
     */
    boolean saveGoods(GoodsForm form);

    /**
     * 更新商品
     */
    boolean updateGoods(Long id, GoodsForm form);

    /**
     * 删除商品
     */
    boolean deleteGoods(String ids);
}

GoodsServiceImpl.java - 实现类

java
package com.youlai.boot.pms.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.model.PageResult;
import com.youlai.boot.pms.converter.GoodsConverter;
import com.youlai.boot.pms.mapper.PmsGoodsMapper;
import com.youlai.boot.pms.model.entity.PmsGoods;
import com.youlai.boot.pms.model.form.GoodsForm;
import com.youlai.boot.pms.model.query.GoodsPageQuery;
import com.youlai.boot.pms.model.vo.GoodsVO;
import com.youlai.boot.pms.service.GoodsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

@Service
@RequiredArgsConstructor
public class GoodsServiceImpl implements GoodsService {

    private final PmsGoodsMapper goodsMapper;
    private final GoodsConverter goodsConverter;

    /**
     * 商品分页列表
     */
    @Override
    public PageResult<GoodsVO> getGoodsPage(GoodsPageQuery query) {
        Page<GoodsVO> page = goodsMapper.getGoodsPage(
            new Page<>(query.getPageNum(), query.getPageSize()),
            query
        );
        return PageResult.of(page);
    }

    /**
     * 商品详情
     */
    @Override
    public GoodsForm getGoodsDetail(Long id) {
        PmsGoods entity = goodsMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("商品不存在");
        }
        return goodsConverter.entity2Form(entity);
    }

    /**
     * 新增商品
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveGoods(GoodsForm form) {
        PmsGoods entity = goodsConverter.form2Entity(form);
        return goodsMapper.insert(entity) > 0;
    }

    /**
     * 更新商品
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateGoods(Long id, GoodsForm form) {
        PmsGoods entity = goodsMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("商品不存在");
        }

        goodsConverter.updateEntity(entity, form);
        return goodsMapper.updateById(entity) > 0;
    }

    /**
     * 删除商品
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteGoods(String ids) {
        List<Long> idList = Arrays.stream(ids.split(","))
            .map(Long::parseLong)
            .toList();
        return goodsMapper.deleteBatchIds(idList) > 0;
    }
}

第六步:创建 Converter

java
package com.youlai.boot.pms.converter;

import com.youlai.boot.pms.model.entity.PmsGoods;
import com.youlai.boot.pms.model.form.GoodsForm;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;

@Mapper(componentModel = "spring")
public interface GoodsConverter {

    GoodsForm entity2Form(PmsGoods entity);

    PmsGoods form2Entity(GoodsForm form);

    void updateEntity(@MappingTarget PmsGoods entity, GoodsForm form);
}

第七步:创建 Controller

java
package com.youlai.boot.pms.controller;

import com.youlai.boot.common.model.PageResult;
import com.youlai.boot.common.model.Result;
import com.youlai.boot.pms.model.form.GoodsForm;
import com.youlai.boot.pms.model.query.GoodsPageQuery;
import com.youlai.boot.pms.model.vo.GoodsVO;
import com.youlai.boot.pms.service.GoodsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Tag(name = "商品管理")
@RestController
@RequestMapping("/api/v1/goods")
@RequiredArgsConstructor
public class GoodsController {

    private final GoodsService goodsService;

    @Operation(summary = "商品分页列表")
    @GetMapping("/page")
    public Result<PageResult<GoodsVO>> getGoodsPage(GoodsPageQuery query) {
        PageResult<GoodsVO> result = goodsService.getGoodsPage(query);
        return Result.success(result);
    }

    @Operation(summary = "商品详情")
    @GetMapping("/{id}/form")
    public Result<GoodsForm> getGoodsDetail(
        @Parameter(description = "商品ID") @PathVariable Long id
    ) {
        GoodsForm formData = goodsService.getGoodsDetail(id);
        return Result.success(formData);
    }

    @Operation(summary = "新增商品")
    @PostMapping
    @PreAuthorize("hasAuthority('pms:goods:add')")
    public Result<Void> saveGoods(@Validated @RequestBody GoodsForm form) {
        goodsService.saveGoods(form);
        return Result.success();
    }

    @Operation(summary = "更新商品")
    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('pms:goods:edit')")
    public Result<Void> updateGoods(
        @Parameter(description = "商品ID") @PathVariable Long id,
        @Validated @RequestBody GoodsForm form
    ) {
        goodsService.updateGoods(id, form);
        return Result.success();
    }

    @Operation(summary = "删除商品")
    @DeleteMapping("/{ids}")
    @PreAuthorize("hasAuthority('pms:goods:delete')")
    public Result<Void> deleteGoods(
        @Parameter(description = "商品ID,多个以英文逗号(,)分割")
        @PathVariable String ids
    ) {
        goodsService.deleteGoods(ids);
        return Result.success();
    }
}

第八步:测试接口

启动项目后,访问接口文档:http://localhost:8080/doc.html

  1. 测试分页查询:GET /api/v1/goods/page
  2. 测试新增:POST /api/v1/goods
  3. 测试详情:GET /api/v1/goods/{id}/form
  4. 测试更新:PUT /api/v1/goods/{id}
  5. 测试删除:DELETE /api/v1/goods/{ids}

开发规范

1. RESTful 接口设计

操作HTTP方法路径说明
列表GET/api/v1/goods/page分页列表
详情GET/api/v1/goods/{id}获取详情
表单GET/api/v1/goods/{id}/form获取表单数据
新增POST/api/v1/goods新增数据
更新PUT/api/v1/goods/{id}更新数据
删除DELETE/api/v1/goods/{ids}删除数据

2. 参数校验

使用 jakarta.validation 注解进行参数校验:

  • @NotNull - 不能为空
  • @NotBlank - 不能为空字符串
  • @Size - 长度限制
  • @Min/@Max - 数值范围
  • @Pattern - 正则表达式

3. 异常处理

java
// 业务异常
throw new BusinessException("商品不存在");

// 参数验证异常会被全局异常处理器自动处理

4. 事务管理

在需要事务的方法上添加 @Transactional 注解:

java
@Transactional(rollbackFor = Exception.class)
public boolean saveGoods(GoodsForm form) {
    // ...
}

最佳实践

  1. 统一返回格式:使用 Result<T> 封装返回结果
  2. 分页查询:继承 BasePageQuery,使用 PageResult<T> 封装结果
  3. 对象转换:使用 MapStruct 进行对象转换
  4. 权限控制:使用 @PreAuthorize 注解控制接口权限
  5. 接口文档:使用 Swagger 注解生成文档

下一步

基于 MIT 许可发布