当前位置:首页 > 随笔 > 正文内容

用户审核系统的表结构拆分与业务流程重组实践

访客 随笔 2026年7月17日 2

一、背景

在近期设计用户信息审核流程时,由于对数据表职责划分理解不足,导致设计出现偏差。具体表现为:审核表中不仅包含审核相关字段,还冗余了大量用户信息(如证件名称、图片链接等),而用户主表仅存储基础信息,职责边界模糊。

此外,用户在提交申请阶段就会自动创建系统账户(即使处于停用状态),将"提交请求"与"账户生成"过早耦合。字段语义也存在不一致,例如 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=拒绝)。

四、总结

通过上述重构,实现了用户数据、审核行为与账户体系的彻底解耦。用户主表只存储最终生效数据,审核日志专注过程记录,账户系统仅在审核完成后介入。整体设计从"过程驱动数据"转变为"结果驱动数据",减少了无效数据生成,降低了状态一致风险,提升了系统的可维护性与可扩展性,为后续的历史追踪、多次提审等需求奠定基础。

标签: 数据库设计

相关文章

在 iPhone 上下载国外App

地区/国家限制App Store 会根据 Apple ID 的国家或地区限制应用下载。如果你的 Apple ID 绑定的是中国大陆,就可能无法下载 OpenAI 官方的 ChatGPT 应用,因为它在大陆 App Store 不上架。解决办法:换成美国、加拿大、香港等地区的 Apple ID。或者在现有 Apple ID 上更改地区。注册一个国外 Apple ID(推荐)比如注册 美国区 Appl...

Node.js 中的异步编程:回调与 Promise

Node.js 是一个基于 JavaScript 构建的单线程、非阻塞运行环境,它通过异步编程机制来高效处理多个操作。在执行如文件读取、API 请求或数据库查询等任务时,Node.js 不会等待这些操作完成,而是使用回调函数和 Promise 来避免阻塞主线程。 回调方式实现异步 那么当异步操作完成后,Node.js 如何知道接下来要做什么呢?这就要用到 回调函数(callback)。 回调本质上...

Selenium自动化测试入门指南

Selenium自动化测试入门指南

什么是自动化测试? 自动化测试是指利用软件工具自动执行测试用例,模拟用户操作,如打开网页、点击链接、输入文本等,并验证结果是否符合预期。 其主要优点包括: 大幅减少人工成本 测试速度快 可以在非工作时间运行 支持持续集成和交付 然而,它也存在一些局限性,例如开发成本较高、不适合快速变化的项目、依赖稳定的UI界面等。 自动化测试的应用条件 适合引入自动化测试的情况包括: 手动测试耗时且需要大量...

MariaDB Galera集群故障快速恢复指南

OpenStack控制节点采用三节点MariaDB Galera集群架构。当数据库集群因故障重启时,有时会出现Galera集群无法正常启动的问题。虽然有多种方法可以恢复数据库服务,但如何实现快速启动同时确保数据完整性呢? 通过分析日志发现,MariaDB Galera集群节点宕机时会在日志中输出以下信息: [Note] WSREP: 新集群视图:全局状态: 874d8e7e-5980-11e8-8...

Android 中 EventBus 的通信机制与实现原理深度解析

EventBus 核心设计思想 EventBus 是一个基于观察者模式的事件总线框架,广泛应用于 Android 平台以实现组件解耦。它通过中心化的消息分发机制,使不同层级、不同线程的对象能够以"发布-订阅"方式通信,避免了传统接口回调或广播带来的强依赖问题。 核心角色说明 事件(Event):任意 Java 对象,作为数据载体,如网络状态变更通知、用户登录信息等。 发布者(Publi...

二叉树基础操作实现(C语言)

二叉树基础操作实现(C语言)

二叉树遍历方法 以下为二叉树结构示例 前序访问实现: void traversePreOrder(TreeNode* node) { if (node == NULL) { printf("N "); return; } printf("%d ", node->value); trave...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。