# 收货信息设计方案 ## 概述 本文档详细说明了电商系统中收货信息的设计方案,采用**地址快照 + 地址引用混合模式**,确保订单收货信息的完整性和一致性。 ## 设计原则 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 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 ``` ## 优势分析 ### 1. 数据一致性 - 订单创建时保存地址快照,确保历史订单信息不受用户后续地址修改影响 - 同时保留地址ID引用,便于数据关联和分析 ### 2. 用户体验 - 自动读取用户默认地址,减少用户操作步骤 - 支持临时修改收货信息,满足特殊需求 - 智能地址选择逻辑,确保总能找到合适的收货地址 ### 3. 系统稳定性 - 完善的异常处理机制,避免因地址问题导致下单失败 - 详细的日志记录,便于问题排查和系统监控 ### 4. 扩展性 - 支持多种地址类型(家、公司、学校等) - 预留经纬度字段,支持地图定位功能 - 灵活的排序和默认地址设置 ## 注意事项 1. **地址验证**:建议在前端和后端都进行地址完整性验证 2. **默认地址管理**:确保用户只能有一个默认地址 3. **地址数量限制**:建议限制用户地址数量,避免数据冗余 4. **隐私保护**:敏感信息如手机号需要适当脱敏处理 5. **性能优化**:对于高频查询的地址信息,可考虑适当缓存 ## 总结 本设计方案通过地址快照机制确保了订单数据的一致性,通过智能地址选择提升了用户体验,通过完善的异常处理保证了系统稳定性。该方案已在 `OrderBusinessService` 中实现,可以直接投入使用。