文档目录
用户联系表

简介

本文件面向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)
添加日期:2026-10-05