# 收货信息设计方案

## 概述

本文档详细说明了电商系统中收货信息的设计方案，采用**地址快照 + 地址引用混合模式**，确保订单收货信息的完整性和一致性。

## 设计原则

1. **数据一致性**：用户下单时保存收货地址快照，避免后续地址修改影响历史订单
2. **用户体验**：自动读取用户默认地址，减少用户输入
3. **灵活性**：支持用户在下单时临时修改收货信息
4. **可追溯性**：保留地址ID引用关系，便于数据分析和问题排查

## 数据库设计

### 1. 用户地址表 (shop_user_address)

```sql
CREATE TABLE `shop_user_address` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
  `country` varchar(50) DEFAULT NULL COMMENT '所在国家',
  `province` varchar(50) DEFAULT NULL COMMENT '所在省份',
  `city` varchar(50) DEFAULT NULL COMMENT '所在城市',
  `region` varchar(50) DEFAULT NULL COMMENT '所在辖区',
  `address` varchar(500) DEFAULT NULL COMMENT '收货地址',
  `full_address` varchar(500) DEFAULT NULL COMMENT '完整地址',
  `lat` varchar(50) DEFAULT NULL COMMENT '纬度',
  `lng` varchar(50) DEFAULT NULL COMMENT '经度',
  `gender` int(11) DEFAULT NULL COMMENT '1先生 2女士',
  `type` varchar(20) DEFAULT NULL COMMENT '家、公司、学校',
  `is_default` tinyint(1) DEFAULT 0 COMMENT '默认收货地址',
  `sort_number` int(11) DEFAULT NULL COMMENT '排序号',
  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
  `tenant_id` int(11) DEFAULT NULL COMMENT '租户id',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_is_default` (`is_default`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址';
```

### 2. 订单表收货信息字段 (shop_order)

```sql
-- 订单表中的收货信息字段
`address_id` int(11) DEFAULT NULL COMMENT '收货地址ID（引用关系）',
`address` varchar(500) DEFAULT NULL COMMENT '收货地址快照',
`real_name` varchar(100) DEFAULT NULL COMMENT '收货人姓名快照',
`address_lat` varchar(50) DEFAULT NULL COMMENT '地址纬度',
`address_lng` varchar(50) DEFAULT NULL COMMENT '地址经度',
```

## 业务流程设计

### 1. 下单时收货地址处理流程

```mermaid
flowchart TD
    A[用户下单] --> B{前端是否传入完整地址?}
    B -->|是| C[使用前端传入地址]
    B -->|否| D{是否指定地址ID?}
    D -->|是| E[根据地址ID获取地址]
    E --> F{地址是否存在且属于当前用户?}
    F -->|是| G[使用指定地址]
    F -->|否| H[获取用户默认地址]
    D -->|否| H[获取用户默认地址]
    H --> I{是否有默认地址?}
    I -->|是| J[使用默认地址]
    I -->|否| K[获取用户第一个地址]
    K --> L{是否有地址?}
    L -->|是| M[使用第一个地址]
    L -->|否| N[抛出异常：请先添加收货地址]
    C --> O[创建地址快照]
    G --> O
    J --> O
    M --> O
    O --> P[保存订单]
```

### 2. 地址优先级

1. **前端传入的完整地址信息**（最高优先级）
2. **指定的地址ID对应的地址**
3. **用户默认收货地址**
4. **用户的第一个收货地址**
5. **无地址时抛出异常**

## 核心实现

### 1. 收货地址处理方法

```java
/**
 * 处理收货地址信息
 * 优先级：前端传入地址 > 指定地址ID > 用户默认地址
 */
private void processDeliveryAddress(ShopOrder shopOrder, OrderCreateRequest request, User loginUser) {
    // 1. 如果前端已经传入了完整的收货地址信息，直接使用
    if (isAddressInfoComplete(request)) {
        return;
    }

    // 2. 如果指定了地址ID，获取该地址信息
    if (request.getAddressId() != null) {
        ShopUserAddress userAddress = shopUserAddressService.getById(request.getAddressId());
        if (userAddress != null && userAddress.getUserId().equals(loginUser.getUserId())) {
            copyAddressToOrder(userAddress, shopOrder, request);
            return;
        }
    }

    // 3. 获取用户默认收货地址
    ShopUserAddress defaultAddress = shopUserAddressService.getDefaultAddress(loginUser.getUserId());
    if (defaultAddress != null) {
        copyAddressToOrder(defaultAddress, shopOrder, request);
        return;
    }

    // 4. 如果没有默认地址，获取用户的第一个地址
    List<ShopUserAddress> userAddresses = shopUserAddressService.getUserAddresses(loginUser.getUserId());
    if (!userAddresses.isEmpty()) {
        copyAddressToOrder(userAddresses.get(0), shopOrder, request);
        return;
    }

    // 5. 如果用户没有任何收货地址，抛出异常
    throw new BusinessException("请先添加收货地址");
}
```

### 2. 地址快照创建

```java
/**
 * 将用户地址信息复制到订单中（创建快照）
 */
private void copyAddressToOrder(ShopUserAddress userAddress, ShopOrder shopOrder, OrderCreateRequest request) {
    // 保存地址ID引用关系
    shopOrder.setAddressId(userAddress.getId());
    request.setAddressId(userAddress.getId());

    // 创建地址信息快照
    if (request.getAddress() == null || request.getAddress().trim().isEmpty()) {
        StringBuilder fullAddress = new StringBuilder();
        if (userAddress.getProvince() != null) fullAddress.append(userAddress.getProvince());
        if (userAddress.getCity() != null) fullAddress.append(userAddress.getCity());
        if (userAddress.getRegion() != null) fullAddress.append(userAddress.getRegion());
        if (userAddress.getAddress() != null) fullAddress.append(userAddress.getAddress());
        
        shopOrder.setAddress(fullAddress.toString());
        request.setAddress(fullAddress.toString());
    }

    // 复制收货人信息
    if (request.getRealName() == null || request.getRealName().trim().isEmpty()) {
        shopOrder.setRealName(userAddress.getName());
        request.setRealName(userAddress.getName());
    }

    // 复制经纬度信息
    if (request.getAddressLat() == null && userAddress.getLat() != null) {
        shopOrder.setAddressLat(userAddress.getLat());
        request.setAddressLat(userAddress.getLat());
    }
    if (request.getAddressLng() == null && userAddress.getLng() != null) {
        shopOrder.setAddressLng(userAddress.getLng());
        request.setAddressLng(userAddress.getLng());
    }
}
```

## 前端集成建议

### 1. 下单页面地址选择

```javascript
// 获取用户地址列表
const getUserAddresses = async () => {
  const response = await api.get('/api/shop/user-address/my');
  return response.data;
};

// 获取默认地址
const getDefaultAddress = async () => {
  const addresses = await getUserAddresses();
  return addresses.find(addr => addr.isDefault) || addresses[0];
};

// 下单时的地址处理
const createOrder = async (orderData) => {
  // 如果用户没有选择地址，使用默认地址
  if (!orderData.addressId && !orderData.address) {
    const defaultAddress = await getDefaultAddress();
    if (defaultAddress) {
      orderData.addressId = defaultAddress.id;
    }
  }
  
  return api.post('/api/shop/order/create', orderData);
};
```

### 2. 地址选择组件

```vue
<template>
  <div class="address-selector">
    <div v-if="selectedAddress" class="selected-address">
      <div class="address-info">
        <span class="name">{{ selectedAddress.name }}</span>
        <span class="phone">{{ selectedAddress.phone }}</span>
      </div>
      <div class="address-detail">{{ selectedAddress.fullAddress }}</div>
    </div>
    <button @click="showAddressList = true">选择收货地址</button>
  </div>
</template>
```

## 优势分析

### 1. 数据一致性
- 订单创建时保存地址快照，确保历史订单信息不受用户后续地址修改影响
- 同时保留地址ID引用，便于数据关联和分析

### 2. 用户体验
- 自动读取用户默认地址，减少用户操作步骤
- 支持临时修改收货信息，满足特殊需求
- 智能地址选择逻辑，确保总能找到合适的收货地址

### 3. 系统稳定性
- 完善的异常处理机制，避免因地址问题导致下单失败
- 详细的日志记录，便于问题排查和系统监控

### 4. 扩展性
- 支持多种地址类型（家、公司、学校等）
- 预留经纬度字段，支持地图定位功能
- 灵活的排序和默认地址设置

## 注意事项

1. **地址验证**：建议在前端和后端都进行地址完整性验证
2. **默认地址管理**：确保用户只能有一个默认地址
3. **地址数量限制**：建议限制用户地址数量，避免数据冗余
4. **隐私保护**：敏感信息如手机号需要适当脱敏处理
5. **性能优化**：对于高频查询的地址信息，可考虑适当缓存

## 总结

本设计方案通过地址快照机制确保了订单数据的一致性，通过智能地址选择提升了用户体验，通过完善的异常处理保证了系统稳定性。该方案已在 `OrderBusinessService` 中实现，可以直接投入使用。
