详解Spring Boot 接口相关知识

java (15) 2026-09-21 14:39:16

一次 HTTP 请求在 Spring Boot 里从外到内穿过 5 层,每层职责单一:
详解Spring Boot 接口相关知识_https://www.tiejiang.org_java_第1张
下面用一个用户查询 + 新增用户的完整例子,包含「暴露接口给别人调」和「我调别人的接口」两件事。

一、项目结构

com.example.demo
├── controller   UserController.java     对外暴露 HTTP 接口
├── service      UserService.java        业务逻辑
├── mapper       UserMapper.java         数据库访问
├── entity       User.java               数据表映射
├── dto          UserQueryDTO / Result   入参 / 统一返回
└── client       RemoteClient.java       调用别人的接口

二、统一返回体(先定这个,项目才规范)

1. Controller(只做三件事:接参数、调 service、包结果)

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    // GET /api/users/1            → 路径参数
    @GetMapping("/{id}")
    public Result<User> getById(@PathVariable Long id) {
        return Result.ok(userService.getById(id));
    }

    // GET /api/users?name=张&page=1  → 查询参数
    @GetMapping
    public Result<List<User>> list(@RequestParam(required = false) String name,
                                   @RequestParam(defaultValue = "1") int page) {
        return Result.ok(userService.findByName(name, page));
    }

    // POST /api/users  body 传 JSON   → 新增
    @PostMapping
    public Result<Long> create(@RequestBody @Valid UserCreateDTO dto) {
        return Result.ok(userService.create(dto));
    }

    // PUT /api/users/1              → 修改
    @PutMapping("/{id}")
    public Result<Void> update(@PathVariable Long id, @RequestBody UserUpdateDTO dto) {
        userService.update(id, dto);
        return Result.ok(null);
    }

    // DELETE /api/users/1           → 删除
    @DeleteMapping("/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return Result.ok(null);
    }
}

注解速记

注解 作用
@RestController = @Controller + @ResponseBody,返回值自动转 JSON
@RequestMapping 类级别的公共路径前缀
@GetMapping / @PostMapping 限定 HTTP 方法,GET 查、POST 增、PUT 改、DELETE 删
@PathVariable 取 URL 里的值 /users/{id}
@RequestParam 取 ?key=value 里的值
@RequestBody 把请求体 JSON 反序列化成对象
@Valid 触发 DTO 上的校验(@NotBlank、@Size 等)

2. Service(业务和事务在这里)

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserMapper userMapper;

    public User getById(Long id) {
        User u = userMapper.selectById(id);
        if (u == null) throw new BizException("用户不存在");
        return u;
    }

    @Transactional          // 多条写操作必须加,保证同成功同失败
    public Long create(UserCreateDTO dto) {
        if (userMapper.existsByPhone(dto.phone())) throw new BizException("手机号已存在");
        User user = new User(null, dto.name(), dto.phone(), LocalDateTime.now());
        userMapper.insert(user);
        return user.getId();   // MyBatis-Plus 插入后自动回填主键
    }
}

3. Mapper(只写 SQL)

@Mapper
public interface UserMapper extends BaseMapper<User> {   // MyBatis-Plus 自带 CRUD

    @Select("SELECT COUNT(1) FROM user WHERE phone = #{phone}")
    int existsByPhone(String phone);
}

application.yml:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 开发期打印 SQL

自测

curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"张三","phone":"13800000000"}'

四、调用接口:你去调别人

方式一:RestTemplate(最简单,适合少量调用)

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
                .setConnectTimeout(Duration.ofSeconds(3))
                .setReadTimeout(Duration.ofSeconds(10))
                .build();
    }
}

@Service
@RequiredArgsConstructor
public class WeatherService {
    private final RestTemplate restTemplate;

    public String getWeather(String city) {
        String url = "https://api.example.com/weather?city={city}";
        // getForObject:GET 请求,返回值直接映射成对象
        return restTemplate.getForObject(url, String.class, city);
    }

    public String postOrder(OrderDTO dto) {
        // postForObject:POST 请求,自动把 dto 序列化成 JSON body
        return restTemplate.postForObject("https://api.example.com/order", dto, String.class);
    }
}

方式二:OpenFeign(微服务推荐,像调本地方法一样调远程)

依赖:spring-cloud-starter-openfeign,启动类加 @EnableFeignClients。

@FeignClient(name = "remoteApi", url = "${remote.api.url}")
public interface RemoteClient {

    @GetMapping("/users/{id}")
    Result<User> getUser(@PathVariable Long id);

    @PostMapping("/orders")
    Result<Long> createOrder(@RequestBody OrderDTO dto);
}

// 直接注入使用,异常用 Fallback 兜底
@Service
@RequiredArgsConstructor
public class OrderService {
    private final RemoteClient remoteClient;

    public Result<Long> submit(OrderDTO dto) {
        return remoteClient.createOrder(dto);
    }
}

对比怎么选:偶尔调一两个外部接口 → RestTemplate;要调一堆同服务的接口(微服务内部)→ OpenFeign;高并发响应式场景 → WebClient。

五、最容易踩的 5 个坑

  1. Controller 里写业务逻辑 → 复用不了、无法加事务,业务一律下沉到 Service。
  2. GET 用 @RequestBody → GET 没有请求体,应该用 @RequestParam / @PathVariable。
  3. 参数校验写在方法里堆 if → 用 @Valid + DTO 注解,代码干净且能统一返回错误信息。
  4. RestTemplate 不设超时 → 对方服务挂了会拖垮你自己的线程池,必须设 connect/read timeout。
  5. 直接抛异常给前端 → 加 @RestControllerAdvice 全局异常处理,把 BizException 转成统一 JSON。
THE END

Leave a Reply

下一篇

已是最新文章