用户、资金库表和架构设计

接着订单说,订单结算时要给用户加款、给平台加手续费。涉及用户和资金。

用户

只有一套用户怎么设计都行,至少两张表,一张主表只存用户账密、名称等基本信息,再扩展一张表存用户实名等其他属性。

登录的时候只查主表,主表 id 作为整个系统的用户 id,不要用手机号等其他标识作为用户 id。我之前有个业务场景手机号是唯一键,于是我用手机号做业务主键,结果后来业务改成允许一个手机号多个用户,我就傻眼了。

在这些基础架构上要技术主导。

sql 复制代码
-- 用户主表 - 存储基本登录信息
CREATE TABLE `user_main` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID,系统唯一标识',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(255) NOT NULL COMMENT '密码(加密存储)',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号(非唯一,允许多用户共享)',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账户状态:1-正常,0-禁用',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  KEY `idx_phone` (`phone`),
  KEY `idx_email` (`email`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户主表-基本登录信息';

-- 用户扩展表 - 存储实名等其他属性
CREATE TABLE `user_profile` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID,关联user_main.id',
  `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
  `gender` tinyint(1) DEFAULT NULL COMMENT '性别:1-男,2-女,0-未知',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
  `address` varchar(200) DEFAULT NULL COMMENT '地址',
  `company` varchar(100) DEFAULT NULL COMMENT '公司',
  `position` varchar(50) DEFAULT NULL COMMENT '职位',
  `bio` text DEFAULT NULL COMMENT '个人简介',
  `is_verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否实名认证:1-已认证,0-未认证',
  `verified_at` timestamp NULL DEFAULT NULL COMMENT '实名认证时间',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_id` (`user_id`),
  UNIQUE KEY `uk_id_card` (`id_card`),
  KEY `idx_real_name` (`real_name`),
  KEY `idx_is_verified` (`is_verified`),
  CONSTRAINT `fk_user_profile_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_main` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户扩展表-实名等其他属性'; 

但有时候一个系统有多套用户,怎么设计取决于业务场景,如果业务要求一个角色在页面切换其他角色,那数据库的设计是 1 张用户主表 + 多张角色扩展表。

但是更建议使用两套独立的用户表。上面的实现方式会在业务的各个场景增加复杂度,业务发展越大,问题越多。现在随便打开一个多角色 App,比如 BOSS 直聘,招人和应聘登录都不在一起。像美团这样的,点餐的客户和跑腿的骑手都区分 App 了。

我之前做过一个业务最开始用的方式 1,随着用户相关的需求迭代,越来越难改,最后索性停止迭代了😂。

资金

资金的事先让财务设计流向链路,我之前做过一个业务,因资金流向问题,扣了大量的税,业务挣的钱基本上都交税了。

资金流向避免作为钱入平台,再从平台分账,这样流水太大,财务必定很难搞。

可以设计成担保 + 手续费模式,像咸鱼这样,钱是从买家到卖家账户,平台只收取手续费。

模式一:资金入平台模式(不推荐)

graph LR A[买家] -->|100元| B[平台] B -->|95元| C[卖家] style B fill:#ffcccc

模式二:担保+手续费模式(推荐)

graph LR A[买家] -->|95元| C[卖家] A -->|5元手续费| B[平台] style B fill:#ccffcc

库表设计

sql 复制代码
-- 资金账户表
CREATE TABLE fund_account (
    account_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '账户ID',
    account_no VARCHAR(32) NOT NULL UNIQUE COMMENT '账户编号',
    account_name VARCHAR(100) NOT NULL COMMENT '账户名称',
    account_type TINYINT NOT NULL DEFAULT 1 COMMENT '账户类型:1-现金账户,2-银行账户,3-支付宝,4-微信,5-其他',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    balance DECIMAL(15,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额(可用余额)',
    frozen_amount DECIMAL(15,2) NOT NULL DEFAULT 0.00 COMMENT '冻结金额',
    currency VARCHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
    remark VARCHAR(255) COMMENT '备注',
    created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    INDEX idx_user_id (user_id),
    INDEX idx_account_no (account_no),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金账户表';

-- 资金流水表
CREATE TABLE fund_transaction (
    transaction_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '流水ID',
    transaction_no VARCHAR(64) NOT NULL UNIQUE COMMENT '流水号',
    account_id BIGINT NOT NULL COMMENT '账户ID',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    transaction_type TINYINT NOT NULL COMMENT '交易类型:1-收入,2-支出,3-转入,4-转出,5-冻结,6-解冻',
    amount DECIMAL(15,2) NOT NULL COMMENT '交易金额',
    balance_before DECIMAL(15,2) NOT NULL COMMENT '变动前账户余额',
    balance_after DECIMAL(15,2) NOT NULL COMMENT '变动后账户余额',
    frozen_before DECIMAL(15,2) NOT NULL COMMENT '变动前冻结金额',
    frozen_after DECIMAL(15,2) NOT NULL COMMENT '变动后冻结金额',
    business_type VARCHAR(32) NOT NULL COMMENT '业务类型:recharge-充值,withdraw-提现,transfer-转账,payment-支付,refund-退款等',
    business_id VARCHAR(64) COMMENT '业务单号',
    counterpart_account_id BIGINT COMMENT '对方账户ID(转账时使用)',
    currency VARCHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-失败,1-成功,2-处理中',
    remark VARCHAR(255) COMMENT '备注',
    transaction_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '交易时间',
    created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    INDEX idx_account_id (account_id),
    INDEX idx_user_id (user_id),
    INDEX idx_transaction_no (transaction_no),
    INDEX idx_business_type (business_type),
    INDEX idx_business_id (business_id),
    INDEX idx_transaction_time (transaction_time),
    INDEX idx_status (status),
    FOREIGN KEY (account_id) REFERENCES fund_account(account_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水表'; 

核心就这两张表,资金里面至少有两种账户类型,余额和冻结。

根据业务类型还可以添加更多类型,我曾做过一个业务里面除了这两个还包含分配、退回等。

账户表里有多少个分类,流水表就加多少前后快照。

任何资金操作,充值、购买、申请退款等,这两张表都是用悲观锁 + 事物包裹的,外层再加个分布式锁。分布式锁的 key 可以用用户 id,保证同一个用户同一时刻只有一个资金操作。(一般的业务场景这样都够用了,并发高就用 redis + kafka,实时操作换成,异步 kafka 入库,我之前好像写过,你可以翻翻我之前的文章。)

资金操作类型根据业务场景定,不要做通用的。比如叫冻结,在申请退款和押金的时候都用,虽然在数据库层面都是减少余额增加冻结,但不要这样写,太耦合了后期迭代搞死人。

比如我会操作类型拆为

以下是购买的流程。

如果涉及资金从 A 账户流向 B 账户,比如 A 转账给 B,那就写个转账的类型,把 A、B 的两张表放到同一个事物。

如果一个操作同时涉及余额减少和冻结增加,写在一条流水里面,不要拆分。

以上做完后数据就不可能出问题,资金的重要性大于系统,系统可以崩,资金不能乱。

对账

每一个操作完成,异步发个延时任务对账,根据不同操作,执行不同的对账逻辑。

像 AICD 一样检查操作的原子性、一致性。

通常还有个日对账,每日结束后,核对系统层面一共收了多少钱、只出了多少钱,内部扭转了多少钱,对账逻辑可以让财务出。

对账可以保证万一出问题,出问题的那一刻就能察觉。

相关推荐
徐子童15 分钟前
《Spring Cloud Gateway 快速入门:从路由到自定义 Filter 的完整教程》
java·开发语言·spring cloud·nacos·gateway
无名之逆18 分钟前
[特殊字符]For Speed Enthusiasts: The Ultimate Evolution of Rust HTTP Engines
开发语言·前端·后端·网络协议·http·rust
Maxwellhang1 小时前
【音频处理】java流式调用ffmpeg命令
java·ffmpeg·音视频
Maỿbe2 小时前
阻塞队列的学习以及模拟实现一个阻塞队列
java·数据结构·线程
we风3 小时前
【SpringCache 提供的一套基于注解的缓存抽象机制】
java·缓存
LiRuiJie5 小时前
深入剖析HBase架构
数据库·架构·hbase
趙卋傑5 小时前
网络编程套接字
java·udp·网络编程·tcp
两点王爷5 小时前
Java spingboot项目 在docker运行,需要含GDAL的JDK
java·开发语言·docker
二进制coder7 小时前
芯片:数字时代的算力引擎——鲲鹏、升腾、海光、Intel 全景解析
arm开发·架构·硬件架构
万能螺丝刀18 小时前
java helloWord java程序运行机制 用idea创建一个java项目 标识符 关键字 数据类型 字节
java·开发语言·intellij-idea