接口开发指南
本文档介绍如何在 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
- 测试分页查询:GET
/api/v1/goods/page - 测试新增:POST
/api/v1/goods - 测试详情:GET
/api/v1/goods/{id}/form - 测试更新:PUT
/api/v1/goods/{id} - 测试删除: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) {
// ...
}最佳实践
- 统一返回格式:使用
Result<T>封装返回结果 - 分页查询:继承
BasePageQuery,使用PageResult<T>封装结果 - 对象转换:使用 MapStruct 进行对象转换
- 权限控制:使用
@PreAuthorize注解控制接口权限 - 接口文档:使用 Swagger 注解生成文档
