Skip to content

商城后端接口实现

把商城核心接口完整写出来。数据库表结构见第 7 部分,这里直接使用。顺序:用户 → 商品 → 购物车 → 订单。

一、用户模块(Auth)

java
// controller/AuthController.java
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final UserService userService;

    public AuthController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public Result<Void> register(@RequestBody @Valid RegisterDTO dto) {
        userService.register(dto);
        return Result.ok(null);
    }

    @PostMapping("/login")
    public Result<LoginVO> login(@RequestBody @Valid LoginDTO dto) {
        return Result.ok(userService.login(dto));
    }
}
java
// service/UserService.java(核心逻辑)
@Service
public class UserService {

    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;

    public void register(RegisterDTO dto) {
        // 1. 校验用户名唯一
        Long count = userMapper.selectCount(
            new LambdaQueryWrapper<User>().eq(User::getUsername, dto.getUsername()));
        if (count > 0) {
            throw new BizException("用户名已存在");
        }
        // 2. 密码加密存储(绝不明文)
        User user = new User();
        user.setUsername(dto.getUsername());
        user.setPassword(passwordEncoder.encode(dto.getPassword()));
        user.setNickname(dto.getUsername());
        userMapper.insert(user);
    }

    public LoginVO login(LoginDTO dto) {
        User user = userMapper.selectOne(
            new LambdaQueryWrapper<User>().eq(User::getUsername, dto.getUsername()));
        if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
            throw new BizException("用户名或密码错误");
        }
        // 签发 JWT(第 6 章详细讲)
        String token = JwtUtil.generateToken(user.getId());
        LoginVO vo = new LoginVO();
        vo.setToken(token);
        vo.setNickname(user.getNickname());
        return vo;
    }
}

二、商品模块

java
// controller/ProductController.java
@RestController
@RequestMapping("/api/product")
public class ProductController {

    private final ProductService productService;

    @GetMapping("/list")
    public Result<IPage<ProductVO>> list(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Long categoryId,
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size) {
        return Result.ok(productService.pageVO(keyword, categoryId, page, size));
    }

    @GetMapping("/{id}")
    public Result<ProductVO> detail(@PathVariable Long id) {
        return Result.ok(productService.detail(id));
    }
}

三、购物车模块(核心业务)

java
// service/CartService.java
@Service
public class CartService {

    private final CartItemMapper cartItemMapper;
    private final ProductMapper productMapper;

    // 加入购物车:已有则数量 +1,没有则新增
    public void add(Long userId, CartAddDTO dto) {
        CartItem exist = cartItemMapper.selectOne(new LambdaQueryWrapper<CartItem>()
                .eq(CartItem::getUserId, userId)
                .eq(CartItem::getProductId, dto.getProductId()));
        if (exist != null) {
            exist.setCount(exist.getCount() + dto.getCount());
            cartItemMapper.updateById(exist);
            return;
        }
        CartItem item = new CartItem();
        item.setUserId(userId);
        item.setProductId(dto.getProductId());
        item.setCount(dto.getCount());
        cartItemMapper.insert(item);
    }

    // 查询购物车(联查商品信息)
    public List<CartItemVO> list(Long userId) {
        // 查购物车条目
        List<CartItem> items = cartItemMapper.selectList(
            new LambdaQueryWrapper<CartItem>().eq(CartItem::getUserId, userId));
        // 组装商品信息(join product)
        return items.stream().map(item -> {
            Product product = productMapper.selectById(item.getProductId());
            CartItemVO vo = new CartItemVO();
            vo.setId(item.getId());
            vo.setProductId(product.getId());
            vo.setProductName(product.getName());
            vo.setPrice(product.getPrice());
            vo.setImage(product.getImage());
            vo.setStock(product.getStock());
            vo.setCount(item.getCount());
            return vo;
        }).toList();
    }
}

四、订单模块(事务 + 库存扣减)

下单是最典型的"事务场景":扣库存、建订单必须同时成功或同时失败。

java
// service/OrderService.java
@Service
public class OrderService {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final CartItemMapper cartItemMapper;
    private final ProductMapper productMapper;

    @Transactional   // 事务:任何一个环节失败,全部回滚
    public Order create(Long userId, OrderCreateDTO dto) {
        BigDecimal total = BigDecimal.ZERO;
        Order order = new Order();
        order.setUserId(userId);
        order.setOrderNo(OrderNoUtil.generate());   // 订单号:yyyyMMdd+时间戳+随机
        order.setStatus("PENDING");                  // 待支付
        order.setAddress(dto.getAddress());

        // 遍历购物车条目,扣库存 + 生成订单明细
        for (Long cartId : dto.getCartIds()) {
            CartItem item = cartItemMapper.selectById(cartId);
            Product product = productMapper.selectById(item.getProductId());

            // 校验库存
            if (product.getStock() < item.getCount()) {
                throw new BizException(product.getName() + " 库存不足");
            }
            // 扣库存(乐观锁防超卖,第 7 部分详讲)
            int rows = productMapper.reduceStock(product.getId(), item.getCount());
            if (rows == 0) {
                throw new BizException(product.getName() + " 库存不足");
            }

            total = total.add(product.getPrice().multiply(BigDecimal.valueOf(item.getCount())));

            OrderItem oi = new OrderItem();
            oi.setOrderId(order.getId());
            oi.setProductId(product.getId());
            oi.setProductName(product.getName());   // 快照:改价/下架不影响历史订单
            oi.setPrice(product.getPrice());
            oi.setCount(item.getCount());
            orderItemMapper.insert(oi);

            cartItemMapper.deleteById(item.getId()); // 从购物车移除
        }

        order.setTotalAmount(total);
        orderMapper.insert(order);
        return order;
    }
}

乐观锁防超卖

java
// ProductMapper.java
@Update("UPDATE product SET stock = stock - #{count} " +
        "WHERE id = #{id} AND stock >= #{count}")
int reduceStock(@Param("id") Long id, @Param("count") Integer count);

WHERE stock >= #{count} 是乐观锁思路:更新时校验条件,条件不满足返回 0 行,说明库存不够,抛异常回滚。并发下也不会超卖(面试高频考点)。

五、订单状态流转

java
public void updateStatus(Long orderId, String from, String to) {
    int rows = orderMapper.updateStatusWithCheck(orderId, from, to);
    if (rows == 0) {
        throw new BizException("订单状态已变更,操作失败");
    }
}
java
// OrderMapper.java
@Update("UPDATE `order` SET status = #{to} WHERE id = #{id} AND status = #{from}")
int updateStatusWithCheck(@Param("id") Long id,
                          @Param("from") String from,
                          @Param("to") String to);

六、接口验收清单

接口方法说明
/api/auth/registerPOST注册,密码加密存储
/api/auth/loginPOST登录,返回 token
/api/product/listGET分页 + 搜索 + 分类
/api/product/{id}GET详情
/api/cartGET/POST查询 / 加购
/api/cart/{id}PATCH/DELETE改数量 / 删除
/api/orderPOST下单(事务 + 扣库存)
/api/order/listGET我的订单

待办

现在接口还没有鉴权——任何请求都能访问任意用户的购物车。第 6 章讲 JWT 后,把"当前登录用户"从 token 里取出来替换 userId 参数。

基于 MIT 协议发布,可自由学习与修改