一、先想清楚:B2B2C和B2C的本质区别

B2C 商城只有一个卖家,平台自己就是卖家。B2B2C 多用户商城里,平台是“场”的提供者,商家是“货”的供给者,买家是“人”。平台不直接卖货,而是通过招商、审核、服务来赚取佣金和服务费。

所以,B2B2C 的核心是“商家生命周期管理”。一个商家从注册到退出,要经历:

入驻申请 → 资质上传 → 平台审核 → 签约 → 开店 → 店铺装修 → 商品发布 → 平台审核 → 上架 → 接单 → 发货 → 结算 → 提现 → 售后处理 → 退出

每一步都要有数据记录、状态流转、权限控制。这不是简单加一个 seller_id 字段就能搞定的。

二、商家入驻流程怎么设计

以 Java 开源商城为例,入驻流程通常设计为状态机:

APPLY(申请中)→ PENDING_REVIEW(待审核)→ APPROVED(已通过)→ ACTIVATED(已激活)
                                    ↓
                              REJECTED(已驳回,可重新提交)

关键表设计:

商家表(seller):seller_id、member_id(关联用户表)、store_name、status、create_time。

店铺表(store):store_id、seller_id、store_logo、store_desc、store_status。

入驻申请表(seller_apply):id、seller_id、company_name、license_number、license_img、legal_person、contact_mobile、audit_status、audit_remark。

关键点:商家表与用户表通过 member_id 关联,但业务数据必须用 seller_id 隔离。用户表是“人”的维度,商家表是“店”的维度,两者不能混为一谈。


三、数据隔离:tenant_id 还是独立库

多商户数据隔离有三种方案:

方案一:独立数据库。每个商家一个库,隔离最彻底,但成本高,运维复杂,适合金融级客户。

方案二:独立 Schema。每个商家一个 Schema,隔离较好,但 MySQL 的 Schema 数量有限制,不适合大量商家。

方案三:共享库 + 共享表 + tenant_id 字段。所有商家数据在同一张表,通过 tenant_id 区分。这是绝大多数开源商城的选择。

实现时,在 MyBatis-Plus 中配置租户插件:

java
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { // 从当前登录商家上下文获取 tenant_id return new LongValue(SecurityUtils.getSellerId());
            } @Override public String getTenantIdColumn() { return "tenant_id";
            } @Override public boolean ignoreTable(String tableName) { // 平台表不隔离 return "platform_config".equals(tableName);
            }
        })); return interceptor;
    }
}

这样,所有 SQL 会自动追加WHERE tenant_id = ?,商家之间数据天然隔离,不需要每个查询手动加条件。

四、订单分账:核心中的核心

B2B2C 最复杂的业务逻辑就是分账。用户支付 100 元,平台抽佣 10%,商家拿 90 元。但资金不能直接转给商家,必须走“平台收款 → 冻结 → 分账 → 结算 → 提现”的流程。

订单表设计

平台订单表(platform_order):order_id、buyer_id、total_amount、status。

商家订单表(seller_order):seller_order_id、platform_order_id、seller_id、goods_amount、freight_amount、commission_amount、settlement_amount、status。

一个平台订单对应多个商家订单(用户可能在一个购物车中同时购买 A 店和 B 店的商品)。

分账流程

  1. 用户支付 100 元 → 资金进入平台微信/支付宝商户号。
  2. 平台订单状态变为 PAID。
  3. 系统按商家订单拆分:A 店 60 元,B 店 40 元。
  4. 按佣金比例计算:A 店佣金 6 元,结算 54 元;B 店佣金 4 元,结算 36 元。
  5. 结算金额进入商家“可提现余额”。
  6. 商家申请提现 → 平台审核 → 打款到商家银行卡。

退款怎么办

用户申请退款时,如果订单已分账,需要从商家冻结资金中扣回。所以商家余额要区分“可提现余额”和“冻结余额”:

sql
-- 提现时,冻结金额 UPDATE seller_account SET available_balance = available_balance - #{amount},
    frozen_balance = frozen_balance + #{amount} WHERE seller_id = #{sellerId}; -- 确认打款后,扣减冻结金额 UPDATE seller_account SET frozen_balance = frozen_balance - #{amount} WHERE seller_id = #{sellerId};

微信/支付宝分账接口

支付宝有“分账”接口,微信支付有“商家分账”功能。核心逻辑是:平台收款后,调用分账接口将资金按比例分给商家子商户。如果使用开源项目,需要确认分账模块是否对接了真实支付通道,还是仅做了内部记账。很多开源项目的分账只是“账面分账”,实际资金流转需要二次开发对接。

五、部署实践:Docker 一键启动

以 Lilishop 为例,Docker Compose 部署:

yaml
version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: lilishop ports: - "3306:3306" volumes: - ./mysql_data:/var/lib/mysql redis: image: redis:7.0 ports: - "6379:6379" backend: build: ./backend depends_on: - mysql - redis ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/lilishop?useUnicode=true&characterEncoding=utf-8 SPRING_REDIS_HOST: redis frontend: build: ./frontend depends_on: - backend ports: - "80:80"

启动命令:

bash
docker-compose up -d

启动后访问管理后台,创建平台管理员账号,然后在后台开启“商家入驻”功能,前端注册商家账号,提交入驻申请,平台审核通过后,商家即可发布商品。

六、二开避坑指南

坑1:商品表没加 tenant_id。商家 A 能看到商家 B 的商品,数据串了。检查所有业务表是否都有租户字段。

坑2:分账只做账面,没对接真实支付通道。用户付款后资金全在平台账户,商家提现靠人工转账,无法规模化。

坑3:商家端权限没做。商家 A 的账号能修改商家 B 的店铺信息。确保商家端接口都校验当前登录商家的 seller_id。

坑4:退款流程没考虑已分账订单。用户退款时,商家已经提现了,平台需要垫资。设计退款时要考虑“冻结期”或“保证金”机制。

七、总结

从 0 到 1 搭建 Java 版 B2B2C 商家入驻商城,核心不是写代码,而是理解商家生命周期、数据隔离和资金分账这三件事。选一个成熟的开源项目做底座,把入驻流程跑通,把分账逻辑想清楚,再根据业务做二开,是最稳妥的路径。

开源项目给你的是“地基”,但“资金安全”这堵墙,必须自己砌牢。