首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Day | 08【苍穹外卖:订单地址,用户下单功能开发】

Day | 08【苍穹外卖:订单地址,用户下单功能开发】

作者头像
北极的代码
发布2026-04-22 16:47:47
发布2026-04-22 16:47:47
670
举报
前言:我们前面主要根据用户的需求和体验感方面,进行业务的优化和升级,这里我们继续从用户端的角度进行业务开发,进行地址的导入和微信支付功能的开发。

思维导图:

地址模块功能开发:

第一步:创建实体类(Entity)
代码语言:javascript
复制
java

package com.sky.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 地址簿实体类
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddressBook implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;                // 主键
    private Long userId;            // 用户id
    private String consignee;       // 收货人
    private String phone;           // 手机号
    private String sex;             // 性别
    private String provinceCode;    // 省级区划编号
    private String provinceName;    // 省级名称
    private String cityCode;        // 市级区划编号
    private String cityName;        // 市级名称
    private String districtCode;    // 区级区划编号
    private String districtName;    // 区级名称
    private String detail;          // 详细地址
    private String label;           // 标签
    private Integer isDefault;      // 默认 0:否 1:是
    
    // 公共字段
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Long createUser;
    private Long updateUser;
}
3. 第二步:创建DTO(数据传输对象)
代码语言:javascript
复制
java

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

/**
 * 地址簿DTO - 用于接收前端参数
 */
@Data
public class AddressBookDTO implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private Long id;                // 地址id(更新时使用)
    private String consignee;       // 收货人
    private String phone;           // 手机号
    private String sex;             // 性别
    private String provinceCode;    // 省级区划编号
    private String provinceName;    // 省级名称
    private String cityCode;        // 市级区划编号
    private String cityName;        // 市级名称
    private String districtCode;    // 区级区划编号
    private String districtName;    // 县级名称
    private String detail;          // 详细地址
    private String label;           // 标签
    private Integer isDefault;      // 是否默认
}
4. 第三步:创建Mapper接口
代码语言:javascript
复制
java

package com.sky.mapper;

import com.sky.entity.AddressBook;
import org.apache.ibatis.annotations.*;
import java.util.List;

/**
 * 地址簿Mapper接口
 */
@Mapper
public interface AddressBookMapper {

    /**
     * 新增地址
     */
    @Insert("INSERT INTO address_book (user_id, consignee, phone, sex, province_code, province_name, " +
            "city_code, city_name, district_code, district_name, detail, label, is_default, " +
            "create_time, update_time, create_user, update_user) " +
            "VALUES (#{userId}, #{consignee}, #{phone}, #{sex}, #{provinceCode}, #{provinceName}, " +
            "#{cityCode}, #{cityName}, #{districtCode}, #{districtName}, #{detail}, #{label}, " +
            "#{isDefault}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(AddressBook addressBook);

    /**
     * 根据id查询地址
     */
    @Select("SELECT * FROM address_book WHERE id = #{id}")
    AddressBook getById(Long id);

    /**
     * 查询当前用户的所有地址
     */
    @Select("SELECT * FROM address_book WHERE user_id = #{userId} ORDER BY is_default DESC, update_time DESC")
    List<AddressBook> list(AddressBook addressBook);

    /**
     * 根据id修改地址
     */
    void update(AddressBook addressBook);

    /**
     * 根据id删除地址
     */
    @Delete("DELETE FROM address_book WHERE id = #{id}")
    void deleteById(Long id);

    /**
     * 根据用户id删除地址
     */
    @Delete("DELETE FROM address_book WHERE user_id = #{userId}")
    void deleteByUserId(Long userId);

    /**
     * 获取当前用户的默认地址
     */
    @Select("SELECT * FROM address_book WHERE user_id = #{userId} AND is_default = 1 LIMIT 1")
    AddressBook getDefaultByUserId(Long userId);

    /**
     * 将该用户的所有地址设为非默认
     */
    @Update("UPDATE address_book SET is_default = 0 WHERE user_id = #{userId}")
    void updateNonDefaultByUserId(Long userId);
}
5. 第四步:创建Mapper XML文件
代码语言:javascript
复制
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.sky.mapper.AddressBookMapper">

    <!-- 动态更新地址 -->
    <update id="update" parameterType="com.sky.entity.AddressBook">
        UPDATE address_book
        <set>
            <if test="consignee != null">consignee = #{consignee},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="provinceCode != null">province_code = #{provinceCode},</if>
            <if test="provinceName != null">province_name = #{provinceName},</if>
            <if test="cityCode != null">city_code = #{cityCode},</if>
            <if test="cityName != null">city_name = #{cityName},</if>
            <if test="districtCode != null">district_code = #{districtCode},</if>
            <if test="districtName != null">district_name = #{districtName},</if>
            <if test="detail != null">detail = #{detail},</if>
            <if test="label != null">label = #{label},</if>
            <if test="isDefault != null">is_default = #{isDefault},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{updateUser}</if>
        </set>
        WHERE id = #{id}
    </update>
    
    <!-- 条件查询地址列表 -->
    <select id="list" resultType="com.sky.entity.AddressBook">
        SELECT * FROM address_book
        <where>
            <if test="userId != null">
                AND user_id = #{userId}
            </if>
            <if test="isDefault != null">
                AND is_default = #{isDefault}
            </if>
        </where>
        ORDER BY is_default DESC, update_time DESC
    </select>

</mapper>
6. 第五步:创建Service接口
代码语言:javascript
复制
java

package com.sky.service;

import com.sky.dto.AddressBookDTO;
import com.sky.entity.AddressBook;
import java.util.List;

public interface AddressBookService {

    /**
     * 新增地址
     */
    void save(AddressBookDTO addressBookDTO);

    /**
     * 查询当前用户的所有地址
     */
    List<AddressBook> list();

    /**
     * 查询默认地址
     */
    AddressBook getDefault();

    /**
     * 根据id查询地址
     */
    AddressBook getById(Long id);

    /**
     * 更新地址
     */
    void update(AddressBookDTO addressBookDTO);

    /**
     * 设置默认地址
     */
    void setDefault(Long id);

    /**
     * 根据id删除地址
     */
    void deleteById(Long id);
}
7. 第六步:创建Service实现类
代码语言:javascript
复制
java

package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.dto.AddressBookDTO;
import com.sky.entity.AddressBook;
import com.sky.mapper.AddressBookMapper;
import com.sky.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 地址簿Service实现类
 */
@Service
@Slf4j
public class AddressBookServiceImpl implements AddressBookService {

    @Autowired
    private AddressBookMapper addressBookMapper;

    /**
     * 新增地址
     */
    @Override
    public void save(AddressBookDTO addressBookDTO) {
        log.info("新增地址:{}", addressBookDTO);
        
        AddressBook addressBook = new AddressBook();
        BeanUtils.copyProperties(addressBookDTO, addressBook);
        
        // 设置当前用户id
        addressBook.setUserId(BaseContext.getCurrentId());
        
        // 设置默认值
        addressBook.setIsDefault(0);  // 默认不是默认地址
        
        // 设置公共字段
        addressBook.setCreateTime(LocalDateTime.now());
        addressBook.setUpdateTime(LocalDateTime.now());
        addressBook.setCreateUser(BaseContext.getCurrentId());
        addressBook.setUpdateUser(BaseContext.getCurrentId());
        
        addressBookMapper.insert(addressBook);
    }

    /**
     * 查询当前用户的所有地址
     */
    @Override
    public List<AddressBook> list() {
        Long userId = BaseContext.getCurrentId();
        log.info("查询用户{}的地址列表", userId);
        
        AddressBook addressBook = new AddressBook();
        addressBook.setUserId(userId);
        
        return addressBookMapper.list(addressBook);
    }

    /**
     * 查询默认地址
     */
    @Override
    public AddressBook getDefault() {
        Long userId = BaseContext.getCurrentId();
        log.info("查询用户{}的默认地址", userId);
        
        return addressBookMapper.getDefaultByUserId(userId);
    }

    /**
     * 根据id查询地址
     */
    @Override
    public AddressBook getById(Long id) {
        log.info("查询地址:id={}", id);
        return addressBookMapper.getById(id);
    }

    /**
     * 更新地址
     */
    @Override
    public void update(AddressBookDTO addressBookDTO) {
        log.info("更新地址:{}", addressBookDTO);
        
        AddressBook addressBook = new AddressBook();
        BeanUtils.copyProperties(addressBookDTO, addressBook);
        
        // 设置更新时间
        addressBook.setUpdateTime(LocalDateTime.now());
        addressBook.setUpdateUser(BaseContext.getCurrentId());
        
        addressBookMapper.update(addressBook);
    }

    /**
     * 设置默认地址(事务管理)
     */
    @Override
    @Transactional
    public void setDefault(Long id) {
        Long userId = BaseContext.getCurrentId();
        log.info("用户{}设置默认地址:id={}", userId, id);
        
        // 1. 先将该用户的所有地址设为非默认
        addressBookMapper.updateNonDefaultByUserId(userId);
        
        // 2. 再将指定地址设为默认
        AddressBook addressBook = AddressBook.builder()
                .id(id)
                .isDefault(1)
                .updateTime(LocalDateTime.now())
                .updateUser(userId)
                .build();
        
        addressBookMapper.update(addressBook);
    }

    /**
     * 根据id删除地址
     */
    @Override
    public void deleteById(Long id) {
        Long userId = BaseContext.getCurrentId();
        log.info("用户{}删除地址:id={}", userId, id);
        
        // 先查询地址是否存在且属于当前用户
        AddressBook addressBook = addressBookMapper.getById(id);
        if (addressBook == null) {
            throw new RuntimeException("地址不存在");
        }
        if (!addressBook.getUserId().equals(userId)) {
            throw new RuntimeException("无权删除他人地址");
        }
        
        addressBookMapper.deleteById(id);
    }
}
8. 第七步:创建Controller
代码语言:javascript
复制
java

package com.sky.controller.user;

import com.sky.dto.AddressBookDTO;
import com.sky.entity.AddressBook;
import com.sky.result.Result;
import com.sky.service.AddressBookService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 地址簿Controller(用户端)
 */
@RestController
@RequestMapping("/user/addressBook")
@Api(tags = "地址簿接口")
@Slf4j
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增地址
     */
    @PostMapping
    @ApiOperation("新增地址")
    public Result<String> save(@RequestBody AddressBookDTO addressBookDTO) {
        log.info("新增地址:{}", addressBookDTO);
        addressBookService.save(addressBookDTO);
        return Result.success("新增成功");
    }

    /**
     * 查询当前用户的所有地址
     */
    @GetMapping("/list")
    @ApiOperation("查询地址列表")
    public Result<List<AddressBook>> list() {
        List<AddressBook> list = addressBookService.list();
        return Result.success(list);
    }

    /**
     * 查询默认地址
     */
    @GetMapping("/default")
    @ApiOperation("查询默认地址")
    public Result<AddressBook> getDefault() {
        AddressBook addressBook = addressBookService.getDefault();
        return Result.success(addressBook);
    }

    /**
     * 根据id查询地址
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询地址")
    public Result<AddressBook> getById(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        return Result.success(addressBook);
    }

    /**
     * 更新地址
     */
    @PutMapping
    @ApiOperation("更新地址")
    public Result<String> update(@RequestBody AddressBookDTO addressBookDTO) {
        log.info("更新地址:{}", addressBookDTO);
        addressBookService.update(addressBookDTO);
        return Result.success("更新成功");
    }

    /**
     * 设置默认地址
     */
    @PutMapping("/default/{id}")
    @ApiOperation("设置默认地址")
    public Result<String> setDefault(@PathVariable Long id) {
        log.info("设置默认地址:id={}", id);
        addressBookService.setDefault(id);
        return Result.success("设置成功");
    }

    /**
     * 根据id删除地址
     */
    @DeleteMapping
    @ApiOperation("根据id删除地址")
    public Result<String> deleteById(Long id) {
        log.info("删除地址:id={}", id);
        addressBookService.deleteById(id);
        return Result.success("删除成功");
    }
}

用户下单功能

功能需求分析

text

代码语言:javascript
复制
用户点击"去支付"
    ↓
1. 获取用户购物车中的菜品
2. 计算订单总金额
3. 生成订单号
4. 保存订单信息(orders表)
5. 保存订单明细(order_detail表)
6. 清空购物车
7. 返回订单信息给前端

开发步骤

第1步:创建相关DTO/VO
1.1 订单提交DTO(接收前端参数)
代码语言:javascript
复制
java

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 订单提交DTO
 */
@Data
public class OrdersSubmitDTO implements Serializable {
    
    private Long addressBookId;     // 地址簿id
    private Integer payMethod;       // 支付方式
    private String remark;           // 备注
    private Integer packAmount;      // 打包费
    private Integer tablewareNumber; // 餐具数量
    private Integer tablewareStatus; // 餐具状态
}
1.2 订单提交VO(返回前端)
代码语言:javascript
复制
java

package com.sky.vo;

import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单提交VO
 */
@Data
@Builder
public class OrderSubmitVO implements Serializable {
    
    private Long id;                // 订单id
    private String orderNumber;     // 订单号
    private BigDecimal orderAmount; // 订单金额
    private LocalDateTime orderTime;// 下单时间
}

第2步:创建Controller
代码语言:javascript
复制
java

package com.sky.controller.user;

import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单控制器(用户端)
 */
@RestController
@RequestMapping("/user/order")
@Api(tags = "订单相关接口")
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 用户下单
     */
    @PostMapping("/submit")
    @ApiOperation("用户下单")
    public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
        log.info("用户下单:{}", ordersSubmitDTO);
        OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
        return Result.success(orderSubmitVO);
    }
}

第3步:创建Service接口
代码语言:javascript
复制
java

package com.sky.service;

import com.sky.dto.OrdersSubmitDTO;
import com.sky.vo.OrderSubmitVO;

public interface OrderService {
    
    /**
     * 用户下单
     * @param ordersSubmitDTO 订单提交数据
     * @return 订单提交结果
     */
    OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}

第4步:Service实现类(核心业务逻辑)
代码语言:javascript
复制
java

package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.entity.AddressBook;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.entity.ShoppingCart;
import com.sky.enumeration.OrderStatus;
import com.sky.enumeration.PayStatus;
import com.sky.mapper.AddressBookMapper;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 订单Service实现类
 */
@Service
@Slf4j
@Transactional  // 事务注解,保证数据一致性
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    
    @Autowired
    private AddressBookMapper addressBookMapper;
    
    @Override
    public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
        
        // ========== 1. 获取当前登录用户ID ==========
        Long userId = BaseContext.getCurrentId();
        log.info("用户{}开始下单", userId);
        
        // ========== 2. 获取用户地址信息 ==========
        AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if (addressBook == null) {
            throw new RuntimeException("地址不存在");
        }
        
        // ========== 3. 获取用户购物车数据 ==========
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(userId);
        List<ShoppingCart> cartList = shoppingCartMapper.list(shoppingCart);
        
        if (CollectionUtils.isEmpty(cartList)) {
            throw new RuntimeException("购物车为空,不能下单");
        }
        
        // ========== 4. 计算订单总金额 ==========
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (ShoppingCart cart : cartList) {
            // 金额 = 单价 × 数量
            BigDecimal amount = cart.getAmount().multiply(new BigDecimal(cart.getNumber()));
            totalAmount = totalAmount.add(amount);
        }
        
        // 加上打包费
        if (ordersSubmitDTO.getPackAmount() != null) {
            totalAmount = totalAmount.add(new BigDecimal(ordersSubmitDTO.getPackAmount()));
        }
        
        // ========== 5. 创建订单对象并保存 ==========
        Orders order = new Orders();
        
        // 设置订单基本信息
        order.setNumber(String.valueOf(System.currentTimeMillis()));  // 订单号:时间戳
        order.setStatus(OrderStatus.PENDING_PAYMENT);  // 待支付
        order.setUserId(userId);
        order.setAddressBookId(ordersSubmitDTO.getAddressBookId());
        order.setOrderTime(LocalDateTime.now());
        order.setPayMethod(ordersSubmitDTO.getPayMethod());
        order.setPayStatus(PayStatus.UNPAID);  // 未支付
        order.setAmount(totalAmount);
        order.setRemark(ordersSubmitDTO.getRemark());
        
        // 设置地址相关信息
        order.setPhone(addressBook.getPhone());
        order.setAddress(buildAddress(addressBook));
        order.setConsignee(addressBook.getConsignee());
        order.setUserName(addressBook.getConsignee());
        
        // 设置配送相关
        order.setEstimatedDeliveryTime(LocalDateTime.now().plusHours(1));  // 预计1小时后送达
        order.setDeliveryStatus(0);  // 未配送
        
        // 设置餐具相关
        order.setPackAmount(ordersSubmitDTO.getPackAmount());
        order.setTablewareNumber(ordersSubmitDTO.getTablewareNumber());
        order.setTablewareStatus(ordersSubmitDTO.getTablewareStatus());
        
        // 设置公共字段
        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        order.setCreateUser(userId);
        order.setUpdateUser(userId);
        
        // 保存订单(返回主键ID)
        orderMapper.insert(order);
        Long orderId = order.getId();  // 获取生成的订单ID
        log.info("订单创建成功,订单ID:{}", orderId);
        
        // ========== 6. 创建订单明细并批量保存 ==========
        List<OrderDetail> orderDetailList = new ArrayList<>();
        
        for (ShoppingCart cart : cartList) {
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setName(cart.getName());
            orderDetail.setImage(cart.getImage());
            orderDetail.setOrderId(orderId);  // 关联订单ID
            orderDetail.setDishId(cart.getDishId());
            orderDetail.setSetmealId(cart.getSetmealId());
            orderDetail.setDishFlavor(cart.getDishFlavor());
            orderDetail.setNumber(cart.getNumber());
            orderDetail.setAmount(cart.getAmount());
            
            // 设置公共字段
            orderDetail.setCreateTime(LocalDateTime.now());
            orderDetail.setUpdateTime(LocalDateTime.now());
            orderDetail.setCreateUser(userId);
            orderDetail.setUpdateUser(userId);
            
            orderDetailList.add(orderDetail);
        }
        
        // 批量插入订单明细
        orderDetailMapper.insertBatch(orderDetailList);
        log.info("订单明细保存成功,共{}条", orderDetailList.size());
        
        // ========== 7. 清空购物车 ==========
        shoppingCartMapper.deleteByUserId(userId);
        log.info("购物车已清空");
        
        // ========== 8. 返回订单信息给前端 ==========
        OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
                .id(order.getId())
                .orderNumber(order.getNumber())
                .orderAmount(order.getAmount())
                .orderTime(order.getOrderTime())
                .build();
        
        return orderSubmitVO;
    }
    
    /**
     * 拼接完整地址
     */
    private String buildAddress(AddressBook addressBook) {
        StringBuilder sb = new StringBuilder();
        sb.append(addressBook.getProvinceName())
          .append(addressBook.getCityName())
          .append(addressBook.getDistrictName())
          .append(addressBook.getDetail());
        return sb.toString();
    }
}

第5步:Mapper层补充
5.1 ShoppingCartMapper 新增方法
代码语言:javascript
复制
java

package com.sky.mapper;

import com.sky.entity.ShoppingCart;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface ShoppingCartMapper {
    
    /**
     * 查询购物车
     */
    @Select("SELECT * FROM shopping_cart WHERE user_id = #{userId}")
    List<ShoppingCart> list(ShoppingCart shoppingCart);
    
    /**
     * 根据用户ID清空购物车
     */
    @Delete("DELETE FROM shopping_cart WHERE user_id = #{userId}")
    void deleteByUserId(Long userId);
}
5.2 AddressBookMapper 新增方法
代码语言:javascript
复制
java

package com.sky.mapper;

import com.sky.entity.AddressBook;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface AddressBookMapper {
    
    /**
     * 根据ID查询地址
     */
    @Select("SELECT * FROM address_book WHERE id = #{id}")
    AddressBook getById(Long id);
}

第6步:枚举类定义
代码语言:javascript
复制
java

package com.sky.enumeration;

/**
 * 订单状态枚举
 */
public class OrderStatus {
    public static final Integer PENDING_PAYMENT = 1;  // 待支付
    public static final Integer PENDING_DELIVERY = 2; // 待接单
    public static final Integer DELIVERING = 3;       // 配送中
    public static final Integer COMPLETED = 4;        // 已完成
    public static final Integer CANCELLED = 5;        // 已取消
}

/**
 * 支付状态枚举
 */
public class PayStatus {
    public static final Integer UNPAID = 0;   // 未支付
    public static final Integer PAID = 1;     // 已支付
}

三、完整流程图

text

代码语言:javascript
复制
[用户点击下单]
    ↓
[Controller接收参数]
    ↓
[Service开始处理]
    ↓
┌─────────────────────────────────────┐
│ 1. 获取用户ID(ThreadLocal)        │
│ 2. 获取收货地址信息                  │
│ 3. 获取购物车商品列表                │
│ 4. 计算订单总金额                    │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 5. 创建订单对象                      │
│    - 生成订单号(时间戳)            │
│    - 设置订单状态(待支付)          │
│    - 保存到 orders 表                │
│    - 获取生成的订单ID                │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 6. 创建订单明细列表                  │
│    - 遍历购物车                      │
│    - 为每个菜品创建明细              │
│    - 关联订单ID                      │
│    - 批量插入 order_detail 表        │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 7. 清空购物车                        │
│    - 删除该用户的购物车记录          │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│ 8. 返回订单信息给前端                │
│    - 订单ID、订单号、金额、时间      │
└─────────────────────────────────────┘
    ↓
[前端跳转到支付页面]

四、测试接口

使用Postman测试

http

代码语言:javascript
复制
POST http://localhost:8080/user/order/submit
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

{
    "addressBookId": 1,
    "payMethod": 1,
    "remark": "少辣",
    "packAmount": 2,
    "tablewareNumber": 2,
    "tablewareStatus": 1
}
期望返回结果

json

代码语言:javascript
复制
{
    "code": 1,
    "msg": "success",
    "data": {
        "id": 10086,
        "orderNumber": "1710923456789",
        "orderAmount": 88.00,
        "orderTime": "2026-03-20 14:30:00"
    }
}

五、开发总结

步骤

开发内容

文件

说明

1

创建DTO

OrdersSubmitDTO.java

接收前端参数

2

创建VO

OrderSubmitVO.java

返回给前端

3

创建Controller

OrderController.java

处理HTTP请求

4

创建Service接口

OrderService.java

定义业务方法

5

Service实现

OrderServiceImpl.java

核心业务逻辑

6

Mapper方法

ShoppingCartMapper等

数据访问

7

枚举类

OrderStatus等

状态常量

8

接口测试

Postman

验证功能


六、关键点总结

1. 事务管理

java

代码语言:javascript
复制
@Transactional  // 必须加!保证订单和明细同时成功或失败
2. 返回主键ID

java

代码语言:javascript
复制
orderMapper.insert(order);
Long orderId = order.getId();  // 必须获取,用于订单明细
3. 批量插入

java

代码语言:javascript
复制
orderDetailMapper.insertBatch(orderDetailList);  // 性能优化
4. 数据一致性
  • 购物车为空不能下单
  • 地址不存在不能下单
  • 金额计算准确
5. 订单号生成

java

代码语言:javascript
复制
order.setNumber(String.valueOf(System.currentTimeMillis()));

核心重要点 ⭐⭐⭐⭐⭐

1. 事务管理必须加
  • 必须使用 @Transactional 注解,保证订单表、订单明细表、购物车清空三个操作要么全部成功,要么全部失败
  • 不加事务会导致数据不一致:可能出现订单保存成功但明细保存失败,或者订单保存成功但购物车没清空的情况
  • 事务注解加在Service层方法上,不是Controller层
2. 必须返回主键ID(非常重要,坑)
  • 插入订单表后,必须获取数据库自动生成的主键ID
  • 使用 @Options(useGeneratedKeys = true, keyProperty = "id") 配置
  • 订单明细表需要这个ID作为外键关联,没有它明细无法保存
3. 批量插入订单明细
  • 使用MyBatis的 <foreach> 标签实现批量插入,而不是循环调用单条插入
  • 批量插入只执行一次SQL,性能远高于循环插入
  • 用户购物车可能有多个菜品,必须一次性保存所有明细
4. 购物车数据校验
  • 下单前必须校验购物车是否为空,为空时不能下单
  • 地址信息必须存在且属于当前用户
  • 金额计算要准确:单价 × 数量 + 打包费
5. 订单号生成规则
  • 订单号必须唯一,不能重复
  • 使用时间戳(System.currentTimeMillis())或UUID
  • 不能使用数据库自增ID作为订单号暴露给用户

结语:感谢大家能看到这里,如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 地址模块功能开发:
    • 第一步:创建实体类(Entity)
    • 3. 第二步:创建DTO(数据传输对象)
    • 4. 第三步:创建Mapper接口
    • 5. 第四步:创建Mapper XML文件
    • 6. 第五步:创建Service接口
    • 7. 第六步:创建Service实现类
    • 8. 第七步:创建Controller
  • 用户下单功能
    • 功能需求分析
    • 开发步骤
      • 第1步:创建相关DTO/VO
      • 第2步:创建Controller
      • 第3步:创建Service接口
      • 第4步:Service实现类(核心业务逻辑)
      • 第5步:Mapper层补充
      • 第6步:枚举类定义
    • 三、完整流程图
    • 四、测试接口
      • 使用Postman测试
      • 期望返回结果
    • 五、开发总结
    • 六、关键点总结
      • 1. 事务管理
      • 2. 返回主键ID
      • 3. 批量插入
      • 4. 数据一致性
      • 5. 订单号生成
    • 核心重要点 ⭐⭐⭐⭐⭐
      • 1. 事务管理必须加
      • 2. 必须返回主键ID(非常重要,坑)
      • 3. 批量插入订单明细
      • 4. 购物车数据校验
      • 5. 订单号生成规则
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档