后端开发的核心产出就两样:接口和业务逻辑。前者是对外的约定,后者是内部的规则。这一节我们聊如何在实际项目中高效、稳妥地完成这两部分工作。
6.2.1 接口开发:先约定,后实现
别急着写代码,先和服务端/前端同事对齐接口文档。推荐先用 Swagger 或 YApi 定义好请求响应格式,再开始编码。
标准接口的三件套:
# 以 Python FastAPI 为例,其他语言思路相同
@app.post("/api/v1/orders")
async def create_order(
request: OrderCreateRequest, # 1. 入参模型(自动校验)
current_user: User = Depends(get_current_user) # 2. 通用鉴权
):
# 3. 统一响应包装
try:
order = await order_service.create(request, current_user.id)
return Response(data=order, msg="创建成功")
except BizException as e:
return Response(code=e.code, msg=e.message)
关键细节:
- 参数校验放在最外层:用 Pydantic、Java Bean Validation 或 Go Validator,别让脏数据进入业务层
- HTTP 状态码别太随意:200 代表成功,400 是客户端问题,500 是服务端问题,别所有接口都返回 200 然后在 body 里写 code=-1
- 版本号提前设计:v1、v2 的路径前缀,避免后期重构时互相打架
6.2.2 业务逻辑:分层是底线
一个典型的后端请求处理流程:
路由层(Controller) → 业务层(Service) → 数据层(DAO/Repository) → 数据库
各层职责要清晰:
- Controller 只干三件事:接收参数、调用 Service、返回结果。不要在这里写 if-else 业务判断
- Service 是核心战场:事务控制在这里,复杂逻辑在这里,对外部服务的调用也在这里
- DAO 只负责 SQL:别在 SQL 里写业务逻辑,复杂的可以用 ORM 的 query builder,但别拼字符串 SQL
真实案例:订单创建流程
// Java Spring Boot 示例
@Service
public class OrderService {
@Transactional // 事务边界明确
public OrderVO createOrder(OrderDTO dto, Long userId) {
// 1. 参数预处理(幂等性校验)
if (redisTemplate.hasKey("order:idempotent:" + dto.getIdempotentKey())) {
throw new BizException("重复提交");
}
// 2. 业务规则校验
Product product = productDao.findById(dto.getProductId());
if (product.getStock() < dto.getQuantity()) {
throw new BizException("库存不足");
}
// 3. 数据持久化
Order order = new Order();
BeanUtils.copyProperties(dto, order);
order.setUserId(userId);
order.setStatus(OrderStatus.PENDING);
orderDao.save(order);
// 4. 异步处理(发送消息,不阻塞主流程)
rabbitTemplate.convertAndSend("order.created", order);
// 5. 返回 VO(脱敏,不直接返回 DO)
return convertToVO(order);
}
}
6.2.3 常见坑与避坑指南
1. 空指针地狱
// 烂代码
String city = user.getAddress().getCity(); // 随时 NPE
// 好代码
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
2. 事务边界过大
别在 @Transactional 方法里调外部接口,否则外部接口超时会导致数据库连接被长期占用。
3. 循环查库
// 性能灾难
for (Long id : ids) {
userDao.findById(id); // 发 N 次 SQL
}
// 正确做法
userDao.findByIdIn(ids); // 一次 IN 查询,或走批量查询
4. 异常处理混乱
定义统一的异常体系:
BizException:业务异常(参数错误、状态不符),返回 400,记 WARN 日志SystemException:系统异常(数据库挂了、第三方服务超时),返回 500,记 ERROR 日志并告警
6.2.4 调试技巧
本地联调:
- 用 Postman 或 Insomnia 保存接口集合,别每次都手写 curl
- 数据库加
@Profile("dev")的自动填充,插入测试数据时自动设置 create_time、update_time
日志规范:
// 入口处打参数
log.info("创建订单开始, userId={}, request={}", userId, JSON.toJSONString(request));
// 关键节点打状态
log.info("库存扣减成功, orderId={}, stock={}", orderId, remainStock);
// 异常处打上下文
log.error("支付回调处理失败, orderNo={}, thirdPartyResp={}", orderNo, resp, exception);
6.2.5 小结
写后端代码就一个原则:让别人(包括三个月后的自己)一眼能看懂数据是怎么流转的。
- 接口要稳:入参校验严格,出参格式统一
- 逻辑要清:分层明确,事务边界清晰,异常处理到位
- 性能要顾:N+1 查询、大事务、同步阻塞都是隐形炸弹
写完代码后,用 SonarLint 扫一遍,把明显的代码异味先修了,再提交 PR。