一、地址簿功能
1.1 需求分析
地址簿用于存储用户的收货地址信息。用户登录后可以有多个地址,但仅允许一个默认地址。
1.1.1 产品原型
1.1.2 接口设计
功能需求:
- 地址的CRUD
- 设置默认地址
- 查询默认地址
根据需求:
- 新增地址
接口地址:/user/addressBook
请求方式:POST
请求数据类型:application/json
请求示例:
{
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| addressBook | addressBook | body | true | AddressBook | AddressBook |
| cityCode | false | string | |||
| cityName | false | string | |||
| consignee | false | string | |||
| detail | false | string | |||
| districtCode | false | string | |||
| districtName | false | string | |||
| id | false | integer(int64) | |||
| isDefault | false | integer(int32) | |||
| label | false | string | |||
| phone | false | string | |||
| provinceCode | false | string | |||
| provinceName | false | string | |||
| sex | false | string | |||
| userId | false | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
- 查询当前用户所有地址
接口地址:/user/addressBook/list
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | array | AddressBook | |
| cityCode | string | ||
| cityName | string | ||
| consignee | string | ||
| detail | string | ||
| districtCode | string | ||
| districtName | string | ||
| id | integer(int64) | ||
| isDefault | integer(int32) | ||
| label | string | ||
| phone | string | ||
| provinceCode | string | ||
| provinceName | string | ||
| sex | string | ||
| userId | integer(int64) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": [
{
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
}
],
"msg": ""
}
- 查询默认地址
接口地址:/user/addressBook/default
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | AddressBook | AddressBook | |
| cityCode | string | ||
| cityName | string | ||
| consignee | string | ||
| detail | string | ||
| districtCode | string | ||
| districtName | string | ||
| id | integer(int64) | ||
| isDefault | integer(int32) | ||
| label | string | ||
| phone | string | ||
| provinceCode | string | ||
| provinceName | string | ||
| sex | string | ||
| userId | integer(int64) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
},
"msg": ""
}
- 修改地址
接口地址:/user/addressBook
请求方式:PUT
请求数据类型:application/json
请求示例:
{
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| addressBook | addressBook | body | true | AddressBook | AddressBook |
| cityCode | false | string | |||
| cityName | false | string | |||
| consignee | false | string | |||
| detail | false | string | |||
| districtCode | false | string | |||
| districtName | false | string | |||
| id | false | integer(int64) | |||
| isDefault | false | integer(int32) | |||
| label | false | string | |||
| phone | false | string | |||
| provinceCode | false | string | |||
| provinceName | false | string | |||
| sex | false | string | |||
| userId | false | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
- 根据 ID 删除地址
接口地址:/user/addressBook
请求方式:DELETE
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| id | id | query | false | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
- 根据 ID 查询地址
接口地址:/user/addressBook/{id}
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| id | id | path | true | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | AddressBook | AddressBook | |
| cityCode | string | ||
| cityName | string | ||
| consignee | string | ||
| detail | string | ||
| districtCode | string | ||
| districtName | string | ||
| id | integer(int64) | ||
| isDefault | integer(int32) | ||
| label | string | ||
| phone | string | ||
| provinceCode | string | ||
| provinceName | string | ||
| sex | string | ||
| userId | integer(int64) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
},
"msg": ""
}
- 设置默认地址
接口地址:/user/addressBook/default
请求方式:PUT
请求数据类型:application/json
请求示例:
{
"cityCode": "",
"cityName": "",
"consignee": "",
"detail": "",
"districtCode": "",
"districtName": "",
"id": 0,
"isDefault": 0,
"label": "",
"phone": "",
"provinceCode": "",
"provinceName": "",
"sex": "",
"userId": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| addressBook | addressBook | body | true | AddressBook | AddressBook |
| cityCode | false | string | |||
| cityName | false | string | |||
| consignee | false | string | |||
| detail | false | string | |||
| districtCode | false | string | |||
| districtName | false | string | |||
| id | false | integer(int64) | |||
| isDefault | false | integer(int32) | |||
| label | false | string | |||
| phone | false | string | |||
| provinceCode | false | string | |||
| provinceName | false | string | |||
| sex | false | string | |||
| userId | false | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
1.2 功能实现
1.2.1 Controller 层
@RestController
@RequestMapping("/user/addressBook")
@Api(tags = "C端地址簿接口")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 查询当前登录用户的所有地址信息
*
* @return
*/
@GetMapping("/list")
@ApiOperation("查询当前登录用户的所有地址信息")
public Result<List<AddressBook>> list() {
AddressBook addressBook = new AddressBook();
addressBook.setUserId(BaseContext.getCurrentId());
List<AddressBook> list = addressBookService.list(addressBook);
return Result.success(list);
}
/**
* 新增地址
*
* @param addressBook
* @return
*/
@PostMapping
@ApiOperation("新增地址")
public Result save(@RequestBody AddressBook addressBook) {
addressBookService.save(addressBook);
return Result.success();
}
@GetMapping("/{id}")
@ApiOperation("根据id查询地址")
public Result<AddressBook> getById(@PathVariable Long id) {
AddressBook addressBook = addressBookService.getById(id);
return Result.success(addressBook);
}
/**
* 根据id修改地址
*
* @param addressBook
* @return
*/
@PutMapping
@ApiOperation("根据id修改地址")
public Result update(@RequestBody AddressBook addressBook) {
addressBookService.update(addressBook);
return Result.success();
}
/**
* 设置默认地址
*
* @param addressBook
* @return
*/
@PutMapping("/default")
@ApiOperation("设置默认地址")
public Result setDefault(@RequestBody AddressBook addressBook) {
addressBookService.setDefault(addressBook);
return Result.success();
}
/**
* 根据id删除地址
*
* @param id
* @return
*/
@DeleteMapping
@ApiOperation("根据id删除地址")
public Result deleteById(Long id) {
addressBookService.deleteById(id);
return Result.success();
}
/**
* 查询默认地址
*/
@GetMapping("default")
@ApiOperation("查询默认地址")
public Result<AddressBook> getDefault() {
//SQL:select * from address_book where user_id = ? and is_default = 1
AddressBook addressBook = new AddressBook();
addressBook.setIsDefault(1);
addressBook.setUserId(BaseContext.getCurrentId());
List<AddressBook> list = addressBookService.list(addressBook);
if (list != null && list.size() == 1) {
return Result.success(list.get(0));
}
return Result.error("没有查询到默认地址");
}
}
1.2.2 Service 层
public interface AddressBookService {
List<AddressBook> list(AddressBook addressBook);
void save(AddressBook addressBook);
AddressBook getById(Long id);
void update(AddressBook addressBook);
void setDefault(AddressBook addressBook);
void deleteById(Long id);
}
1.2.3 Service 实现类
AddressBookService.java
public interface AddressBookService {
List<AddressBook> list(AddressBook addressBook);
void save(AddressBook addressBook);
AddressBook getById(Long id);
void update(AddressBook addressBook);
void setDefault(AddressBook addressBook);
void deleteById(Long id);
}
AddressBookServiceImpl.java
@Service
@Slf4j
public class AddressBookServiceImpl implements AddressBookService {
@Autowired
private AddressBookMapper addressBookMapper;
public List<AddressBook> list(AddressBook addressBook) { ... }
public void save(AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
addressBook.setIsDefault(0);
addressBookMapper.insert(addressBook);
}
public AddressBook getById(Long id) { ... }
public void update(AddressBook addressBook) { ... }
@Transactional
public void setDefault(AddressBook addressBook) {
// 全部设为非默认
addressBook.setIsDefault(0);
addressBook.setUserId(BaseContext.getCurrentId());
addressBookMapper.updateIsDefaultByUserId(addressBook);
// 当前地址设为默认
addressBook.setIsDefault(1);
addressBookMapper.update(addressBook);
}
public void deleteById(Long id) { ... }
}
1.2.3 Mapper 层
@Mapper
public interface AddressBookMapper {
List<AddressBook> list(AddressBook addressBook);
@Insert("insert into address_book ...")
void insert(AddressBook addressBook);
@Select("select * from address_book where id = #{id}")
AddressBook getById(Long id);
void update(AddressBook addressBook);
@Update("update address_book set is_default = #{isDefault} where user_id = #{userId}")
void updateIsDefaultByUserId(AddressBook addressBook);
@Delete("delete from address_book where id = #{id}")
void deleteById(Long id);
}
AddressBookMapper.xml
<mapper namespace="com.sky.mapper.AddressBookMapper">
<select id="list" parameterType="AddressBook" resultType="AddressBook">
select * from address_book
<where>
<if test="userId != null">and user_id = #{userId}</if>
<if test="phone != null">and phone = #{phone}</if>
<if test="isDefault != null">and is_default = #{isDefault}</if>
</where>
</select>
<update id="update" parameterType="addressBook">
update address_book
<set>
<!-- 按需更新字段 -->
</set>
where id = #{id}
</update>
</mapper>
AddressBookMapper.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">
<select id="list" parameterType="AddressBook" resultType="AddressBook">
select * from address_book
<where>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="phone != null">
and phone = #{phone}
</if>
<if test="isDefault != null">
and is_default = #{isDefault}
</if>
</where>
</select>
<update id="update" parameterType="addressBook">
update address_book
<set>
<if test="consignee != null">
consignee = #{consignee},
</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="phone != null">
phone = #{phone},
</if>
<if test="detail != null">
detail = #{detail},
</if>
<if test="label != null">
label = #{label},
</if>
<if test="isDefault != null">
is_default = #{isDefault},
</if>
</set>
where id = #{id}
</update>
</mapper>
1.3 联调测试
1.3.1 新增收货地址
添加两条收货地址:

数据库验证:
1.3.2 删除收货地址
编辑地址:

删除地址:
二、用户下单
2.1 需求分析
2.1.1 产品原型
业务说明
在电商系统中,用户通过下单的方式通知商家自己已购买商品,商家需根据订单进行备货和发货。
用户下单后会产生订单相关数据,订单需包含以下信息:
- 商品信息
- 收货信息
- 支付方式与状态
- 订单状态与金额等
业务流程:
- 用户将菜品或套餐加入购物车
- 点击购物车中的 “去结算” 按钮,跳转至订单确认页面
- 点击 “去支付” 按钮,完成下单操作
用户点餐业务流程图(效果图):

2.1.2 接口设计
接口地址:/user/order/submit
请求方式:POST
请求数据类型:application/json
请求示例:
{
"addressBookId": 0,
"amount": 0,
"deliveryStatus": 0,
"estimatedDeliveryTime": "yyyy-MM-dd HH:mm:ss",
"packAmount": 0,
"payMethod": 0,
"remark": "",
"tablewareNumber": 0,
"tablewareStatus": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| ordersSubmitDTO | ordersSubmitDTO | body | true | OrdersSubmitDTO | OrdersSubmitDTO |
| addressBookId | false | integer(int64) | |||
| amount | false | number | |||
| deliveryStatus | false | integer(int32) | |||
| estimatedDeliveryTime | false | string | |||
| packAmount | false | integer(int32) | |||
| payMethod | false | integer(int32) | |||
| remark | false | string | |||
| tablewareNumber | false | integer(int32) | |||
| tablewareStatus | false | integer(int32) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | OrderSubmitVO | OrderSubmitVO | |
| id | integer(int64) | ||
| orderAmount | number | ||
| orderNumber | string | ||
| orderTime | string(date-time) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {
"id": 0,
"orderAmount": 0,
"orderNumber": "",
"orderTime": ""
},
"msg": ""
}
2.2 功能实现
2.2.1 设计DTO
OrdersSubmitDTO.java
@Data
public class OrdersSubmitDTO implements Serializable {
private Long addressBookId; // 地址簿id
private int payMethod; // 付款方式
private String remark; // 备注
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime estimatedDeliveryTime; // 预计送达时间
private Integer deliveryStatus; // 配送状态
private Integer tablewareNumber; // 餐具数量
private Integer tablewareStatus; // 餐具数量状态
private Integer packAmount; // 打包费
private BigDecimal amount; // 总金额
}
2.2.2 设计VO
OrderSubmitVO.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSubmitVO implements Serializable {
private Long id;
private String orderNumber;
private BigDecimal orderAmount;
private LocalDateTime orderTime;
}
2.2.3 Controller 层
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "C端-订单接口")
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);
}
}
2.2.4 Service 层
public interface OrderService {
OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}
2.2.5 Service 实现类
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//异常情况的处理(收货地址为空、超出配送范围、购物车为空)
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if (addressBook == null) {
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(userId);
//查询当前用户的购物车数据
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if (shoppingCartList == null || shoppingCartList.size() == 0) {
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//构造订单数据
Orders order = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO,order);
order.setPhone(addressBook.getPhone());
order.setAddress(addressBook.getDetail());
order.setConsignee(addressBook.getConsignee());
order.setNumber(String.valueOf(System.currentTimeMillis()));
order.setUserId(userId);
order.setStatus(Orders.PENDING_PAYMENT);
order.setPayStatus(Orders.UN_PAID);
order.setOrderTime(LocalDateTime.now());
//向订单表插入1条数据
orderMapper.insert(order);
//订单明细数据
List<OrderDetail> orderDetailList = new ArrayList<>();
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(order.getId());
orderDetailList.add(orderDetail);
}
//向明细表插入n条数据
orderDetailMapper.insertBatch(orderDetailList);
//清理购物车中的数据
shoppingCartMapper.deleteByUserId(userId);
//封装返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(order.getId())
.orderNumber(order.getNumber())
.orderAmount(order.getAmount())
.orderTime(order.getOrderTime())
.build();
return orderSubmitVO;
}
}
2.2.6 Mapper层
OrderMapper.java
@Mapper
public interface OrderMapper {
void insert(Orders order);
}
OrderMapper.xml
<mapper namespace="com.qi.mapper.OrderMapper">
<insert id="insert" parameterType="Orders" useGeneratedKeys="true" keyProperty="id">
insert into orders
(number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status, amount, remark,
phone, address, consignee, estimated_delivery_time, delivery_status, pack_amount, tableware_number,
tableware_status)
values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},
#{payStatus}, #{amount}, #{remark}, #{phone}, #{address}, #{consignee},
#{estimatedDeliveryTime}, #{deliveryStatus}, #{packAmount}, #{tablewareNumber}, #{tablewareStatus})
</insert>
</mapper>
OrderDetailMapper.java
@Mapper
public interface OrderDetailMapper {
void insertBatch(List<OrderDetail> orderDetails);
}
OrderDetailMapper.xml
<mapper namespace="com.qi.mapper.OrderDetailMapper">
<insert id="insertBatch" parameterType="list">
insert into order_detail
(name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)
values
<foreach collection="orderDetails" item="od" separator=",">
(#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},
#{od.number},#{od.amount},#{od.image})
</foreach>
</insert>
</mapper>
2.3 联调测试
- 登录小程序
- 完成下单操作(此时会删除购物车中的数据)
- 检查
shopping_cart表数据是否清空 - 测试流程:去结算 → 去支付
测试截图占位:
- 购物车页面

- 购物车表

- 支付页面

- order_detail 表

- 确定清空购物车表
关于下单的相关部分就基本实现了,下一章小柒味来-订单支付实现
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com