用户审核系统的表结构拆分与业务流程重组实践
一、背景
在近期设计用户信息审核流程时,由于对数据表职责划分理解不足,导致设计出现偏差。具体表现为:审核表中不仅包含审核相关字段,还冗余了大量用户信息(如证件名称、图片链接等),而用户主表仅存储基础信息,职责边界模糊。
此外,用户在提交申请阶段就会自动创建系统账户(即使处于停用状态),将"提交请求"与"账户生成"过早耦合。字段语义也存在不一致,例如 is_delete 字段默认值设定混乱,部分表设为1表示未删除,部分设为0,容易引发逻辑歧义。
二、问题梳理
1. 数据表职责交叉:用户主表与审核表均包含状态字段(如 status),且逻辑相互依赖;审核表中存储了本应属于用户信息的字段(如姓名、手机号、公司名称等),导致维护混乱。
示例表结构(简化版):
-- 用户主表(部分字段)
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(255) NOT NULL COMMENT '姓名',
user_phone VARCHAR(13) NOT NULL COMMENT '手机号',
status TINYINT DEFAULT 1 NOT NULL COMMENT '1正常 0异常',
is_delete TINYINT DEFAULT 1 NOT NULL COMMENT '1未删除 0已删除'
-- 审核表(部分字段)
audit_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NULL COMMENT '用户ID',
status TINYINT DEFAULT 2 NULL COMMENT '0停用 1正常 2未审核 3拒绝',
applicant_name VARCHAR(100) NULL COMMENT '申请人姓名',
company_name VARCHAR(200) NULL COMMENT '公司名称',
audit_version TINYINT UNSIGNED DEFAULT 1 NOT NULL COMMENT '审核版本号'
2. 审核表充当"当前数据源":通过 audit_version 获取最新记录来代表用户的当前信息,本质上是将审核记录当作了主业务表,导致多次提审时数据判断复杂。
3. 用户账户提前创建:用户在未通过审核时已生成系统账户(停用状态),未来若审核失败,该账户无法销毁,产生大量无效数据。
4. 状态字段分散且易冲突:用户主表、审核表、账户系统中均有 status 字段,更新时需联动,若某一步失败则产生不一致。
5. 无事务控制与并发保护:申请流程中涉及"查询最新版本+插入新记录+更新多表",但未使用事务或乐观锁,并发下易出现版本冲突或数据覆盖。
6. 审核记录非追加式:设计中对审核记录进行更新操作(如修改状态),而非仅追加新记录,导致历史审核信息被覆盖,丧失可追溯性。
7. 字段语义不统一:如 is_delete 在不同表中默认值相反,增加理解与维护成本。
三、优化方案
3.1 表结构职责重构
重新划分表职责:用户主表仅存储"已通过审核"的最终用户基础信息,所有字段均代表生效数据;审核表作为流水日志,仅记录每次的申请内容、审核结果、审核人及时间,不承担业务数据表达职责。删除审核表中冗余的申请人字段(如姓名、公司名称等),改为存储申请时的完整快照(如JSON格式的申请内容),或关联具体的申请表单。
示例优化后结构:
-- 用户主表(仅生效数据)
user_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(100) NOT NULL COMMENT '真实姓名',
user_phone VARCHAR(20) NOT NULL COMMENT '手机号',
status TINYINT DEFAULT 1 NOT NULL COMMENT '1正常 0异常',
is_deleted TINYINT DEFAULT 0 NOT NULL COMMENT '0未删除 1已删除'
-- 审核日志表(纯记录)
log_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
request_id BIGINT UNSIGNED NOT NULL COMMENT '关联请求ID',
apply_content JSON NOT NULL COMMENT '申请时的完整数据',
status TINYINT DEFAULT 2 NOT NULL COMMENT '2待审核 1通过 3拒绝',
auditor VARCHAR(64) NULL COMMENT '审核人',
audit_time DATETIME NULL COMMENT '审核时间',
version INT DEFAULT 1 COMMENT '版本号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
3.2 解耦申请行为与用户生成
用户提交申请时,仅生成一条请求记录,不创建用户主表记录,也不创建系统账户。系统仅将请求内容存入审核日志表中(JSON格式)。审核通过后,系统依据审核日志中的合法数据,在用户主表中插入一条新记录,并同步创建正常的系统账户。若审核失败,仅记录失败日志,不产生任何用户数据。
流程调整示例(伪代码):
public void auditRequest(Request request, boolean approve) {
// 1. 保存请求数据到审核日志
AuditLog log = new AuditLog();
log.setApplyContent(request.toJson());
log.setStatus(2); // 未审核
auditLogDao.insert(log);
// 2. 审核提供结果
if (approve) {
// 从applyContent中提取有效数据
User user = new User();
user.setName(request.getName());
user.setPhone(request.getPhone());
userDao.insert(user);
// 创建系统账户(状态正常)
Account account = new Account();
account.setUserId(user.getId());
account.setStatus(1);
accountDao.insert(account);
log.setStatus(1);
log.setAuditor(currentUser);
log.setAuditTime(new Date());
auditLogDao.updateById(log); // 仅更新状态,保留历史
} else {
log.setStatus(3);
log.setAuditor(currentUser);
log.setAuditTime(new Date());
auditLogDao.updateById(log);
}
}
3.3 增加事务与并发控制
在审核通过后的用户创建与账户创建步骤中,使用数据库事务保证原子性。同时,利用版本号或唯一索引避免重复审核。例如,审核日志表中增加 request_id + version 联合唯一索引,防止同一请求被多次处理。
对于多次提审场景,每次提审生成新的审核请求,并设置状态为"待审核",旧请求自动变为"已失效",但不影响历史记录。
3.4 统一字段语义
数据库字段命名统一含义:is_deleted 统一为0表示未删除,1表示已删除;status 字段在用户表中与账户系统中保持一致的逻辑(1=正常,0=异常/封禁)。在审核日志中,status 使用独立枚举(如0=待审核,1=通过,2=拒绝)。
四、总结
通过上述重构,实现了用户数据、审核行为与账户体系的彻底解耦。用户主表只存储最终生效数据,审核日志专注过程记录,账户系统仅在审核完成后介入。整体设计从"过程驱动数据"转变为"结果驱动数据",减少了无效数据生成,降低了状态一致风险,提升了系统的可维护性与可扩展性,为后续的历史追踪、多次提审等需求奠定基础。

