简介
本文件面向DouPHP用户管理系统开发者与客服人员,系统化说明“用户联系信息管理”的表结构设计,重点围绕用户联系人表 dou_user_contact 的字段设计、默认联系人机制、分组/标签能力、导入导出与批量操作的数据支撑,以及与订单、预约、配送等业务的关联关系。文档同时给出关键流程的时序图与流程图,帮助快速定位问题与扩展功能。
项目结构与范围
- 数据层:以系统表结构定义与备份脚本为准,结合升级脚本对历史字段迁移与时间字段统一进行说明。
- 服务层:前台地址簿服务负责用户侧增删改查与默认地址切换;后台联系人服务提供管理端列表、编辑、删除能力。
- 业务层:结账下单时将用户联系人信息快照至订单收货地址表,确保订单生命周期内地址稳定可追溯。
graph TB
A["用户(前端/后台)"] --> B["前台地址簿服务<br/>ContactService"]
A --> C["后台联系人服务<br/>ContactService"]
B --> D["联系人表<br/>dou_user_contact"]
C --> D
E["结账服务<br/>CheckoutService"] --> F["订单收货地址快照<br/>order_address"]
D --> E
G["订单详情读取<br/>UserService"] --> F
核心数据模型:dou_user_contact
-
表名:dou_user_contact(亦在代码中以 user_contact 引用)
-
主键:id(自增)
-
关键字段
- user_id:所属会员ID
- name:联系人姓名(兼容旧 contact 语义)
- first_name / last_name:姓/名拆分(国际化展示友好)
- phone:联系电话
- id_card:身份证号(用于合规或特定业务场景)
- country / province / city / district / address / postcode:国家、省、市、区/县、详细地址、邮编
- tag:标签(如“家人/同事”,可用于分组筛选)
- is_default:是否默认联系人(1是,0否)
- sort:排序权重(越小越靠前)
- created_at:创建时间(已统一为datetime)
-
字段演进与兼容性
- 历史 add_time 已迁移为 created_at,并做幂等转换与清理。
- 原 dou_user 主表的收货平拍字段(first_name/last_name/contact/country/province/address/postcode/phone)已下线,统一落到 dou_user_contact 的默认行(is_default=1)。
- 读路径通过 UserContactQuery::defaultContactSnapshot 将默认联系人字段合并回用户视图,保证模板向后兼容。
-
分组与权限
- 分组:通过 tag 字段实现轻量分组(如“家人/同事/公司”),可在列表与查询中按标签过滤。
- 权限:联系人归属 user_id,读写均受用户身份校验保护;默认联系人仅当前用户可见/可改。
架构总览
- 读模型:UserContactQuery 提供联系人列表、完整地址拼装、默认联系人快照与默认行写入。
- 写模型:前台 ContactService 负责用户地址簿CRUD与默认切换;后台 ContactService 提供管理端列表、编辑、删除。
- 业务集成:结账时 CheckoutService 将用户联系人信息快照到 order_address,订单详情读取从 order_address 恢复。
classDiagram
class UserContactQuery {
+contactList(user_id, current_contact_id) array
+addressFull(contact) string
+defaultContactSnapshot(user_id) array
+upsertDefaultContact(user_id, fields) void
}
class FrontContactService {
+buildContactListData(userId, page, pagerUrl) array
+insertContact(userId, data) int
+updateContact(userId, id, data) void
+delete(userId, id) bool
+setDefaultContact(userId, id) void
+getJsonList(userId, contactId) array
+getJsonInfo(userId, contactId) array
+resolveContactId(userId, contactId) int
}
class AdminContactService {
+buildContactListData(username, name, phone, pageUrl, page, rejectFilter) array
+buildContactEditData(id) array|null
+update(data) void
+delete(id, post) array
}
class CheckoutService {
+checkout(...)
}
UserContactQuery <.. FrontContactService : "使用"
FrontContactService --> "写入/读取" UserContactQuery
AdminContactService --> "管理" UserContactQuery
CheckoutService --> "快照到" OrderAddress
详细组件分析
联系人读模型:UserContactQuery
- 联系人列表:按 user_id 查询并按 id 倒序返回,附带 address_full 与当前选中标记 cur。
- 地址拼装:根据站点语言决定顺序与分隔符,中文直拼,英文逗号+逆序。
- 默认联系人快照:优先 is_default=1,否则按 sort/id 倒序取首条,补齐缺字段为空串,供模板兼容读取。
- 默认行写入:upsertDefaultContact 支持空字段不覆盖,避免误清空;不存在则插入默认行并设置 created_at。
sequenceDiagram
participant U as "调用方"
participant Q as "UserContactQuery"
participant DB as "数据库"
U->>Q : defaultContactSnapshot(user_id)
Q->>DB : 查询 user_contact WHERE user_id ORDER BY is_default DESC, sort ASC, id ASC
DB-->>Q : 默认联系人行
Q-->>U : 标准化8字段快照
前台地址簿:ContactService
- 列表:分页返回用户联系人,包含 address_full 与默认标记。
- 新增:XSS清洗后写入,若设为默认则先清除同用户其他默认标记。
- 更新:仅更新传入字段,避免覆盖未传字段。
- 删除:按用户隔离删除。
- 默认切换:先将同用户全部置为非默认,再设置目标行为默认。
- JSON接口:提供列表与详情JSON,供订单/预约下拉选择。
- 解析contact_id:优先使用传入ID,否则回退到默认/最新联系人。
flowchart TD
Start(["开始"]) --> CheckDefault{"是否设为默认?"}
CheckDefault --> |是| ClearOld["清除同用户其他默认标记"]
CheckDefault --> |否| Insert["插入新联系人"]
ClearOld --> Insert
Insert --> End(["结束"])
后台联系人管理:ContactService
- 列表:支持按用户名(手机号/邮箱/user_sn/联系人)、姓名、手机号模糊搜索,分页返回。
- 编辑:填充表单数据,字段白名单由请求校验器约束。
- 更新:仅更新允许字段,记录管理日志。
- 删除:二次确认后删除,记录管理日志。
与订单/预约/配送的业务关联
- 下单快照:结账时将用户联系人信息冻结到 order_address,包含 contact_name、phone、id_card、country、province、city、district、address、postcode、created_at。
- 订单详情:读取时从 order_address 恢复 contact/phone/country/province/address/postcode,确保历史订单地址稳定。
- 配送:配送单/运单通常基于 order_address 快照生成,不受后续联系人修改影响。
sequenceDiagram
participant C as "客户端"
participant O as "CheckoutService"
participant UC as "UserContactQuery"
participant DB as "数据库"
C->>O : 提交订单
O->>UC : 获取默认联系人快照
UC->>DB : 查询 user_contact (is_default=1)
DB-->>UC : 联系人数据
UC-->>O : 标准化联系人数据
O->>DB : 写入 order_address快照
O-->>C : 返回订单结果
依赖关系分析
- 表级依赖
- dou_user_contact.user_id 关联用户主体(逻辑上对应 user.id)。
- 订单相关通过 order_address 与 dou_user_contact 解耦,保证订单地址不可变。
- 代码依赖
- 前台/后台服务均依赖各自 Model 与 ORM 查询。
- 读模型 UserContactQuery 被多处复用,保证默认联系人读取一致性。
- 潜在循环依赖
- 无直接循环;服务层单向依赖读/写模型。
graph LR
U["用户(user)"] --> C["联系人(user_contact)"]
C --> O["订单地址(order_address)"]
O -.-> V["订单视图/邮件通知"]
性能与索引建议
- 现有索引
- 主键 id。
- 建议索引
- user_id:高频查询条件,应建立普通索引以提升列表与默认联系人查找性能。
- is_default + sort + id:复合索引可优化默认联系人选择排序。
- tag:如需按标签筛选,建议建立普通索引。
- created_at:便于按创建时间排序或审计。
- 查询优化
- 列表页尽量使用分页与必要字段投影。
- 默认联系人读取优先 is_default=1,避免全表扫描。
故障排查指南
- 默认联系人缺失
- 现象:模板 {$user.contact} 为空。
- 排查:确认是否存在 is_default=1 的行;若无,检查 upsertDefaultContact 是否执行成功。
- 参考:UserContactQuery.php(核心读模型):92-137
- 地址显示异常
- 现象:英文站地址顺序不对。
- 排查:确认 addressFull 分支逻辑与站点语言配置。
- 参考:UserContactQuery.php(核心读模型):74-90
- 订单地址不一致
- 现象:订单详情与联系人修改不一致。
- 排查:确认 checkout 时是否写入 order_address;订单详情应从 order_address 读取。
- 参考:CheckoutService.php(结账下单):536-550, UserService.php(订单详情读取):288-309
- 时间字段异常
- 现象:add_time 与 created_at 不一致。
- 排查:运行升级脚本完成时间字段统一。
- 参考:upgrade.php(升级脚本):339-348
结论
- dou_user_contact 作为用户联系人核心表,承载了联系人基本信息、分组标签、默认联系人与排序等能力。
- 通过 UserContactQuery 与前后端服务,实现了联系人管理的完整闭环,并与订单/预约/配送等业务通过 order_address 解耦,保障数据一致性与可追溯性。
- 建议在数据库层面补充必要索引,提升查询性能;在业务层面遵循“下单即快照”的原则,避免历史订单地址漂移。
附录:字段与业务映射
- 联系人基本信息
- name:联系人姓名(兼容旧 contact)
- first_name / last_name:姓/名拆分
- phone:联系电话
- id_card:身份证号
- 地址信息
- country / province / city / district / address / postcode:国家、省、市、区/县、详细地址、邮编
- 分组与排序
- tag:标签(分组)
- sort:排序权重
- 默认联系人
- is_default:是否默认(1是,0否)
- 时间戳
- created_at:创建时间(已统一为datetime)