把这些字母拆成两个维度来看:
| 维度 | 选项 | 含义 |
|---|
| 供给方 | B(企业) / C(个人) | 谁提供商品或服务 |
| 需求方 | B(企业) / C(个人) | 谁购买商品或服务 |
| 交付方式 | Online / Offline | 线上交付还是线下履约 |
所以 6 种排列组合:
| 模式 | 供给→需求 | 典型例子 | 交付 |
|---|
| B2B | 企业→企业 | 1688、找钢网 | 线下物流 |
| B2C | 企业→个人 | 京东自营、天猫 | 线下物流 |
| C2C | 个人→个人 | 闲鱼、转转 | 线下快递 |
| C2B | 个人→企业 | 猪八戒、Upwork | 线上/线下 |
| O2O | 线上交易→线下履约 | 美团、滴滴 | 线下 |
| B2B2C | 平台→商家→消费者 | 京东POP、拼多多 | 线下物流 |
O2O 其实不是第五种,而是"交付方式=线下"的修饰词。 任何 B2C/C2C/B2B 只要线下履约,都可以叫 O2O。
京东的演化就是最好的例子:
京东 1.0(纯B2C)
京东自己采购 → 卖给消费者
账号:京东采购员(内部) + 消费者(sys_user)
京东 2.0(B2C + B2B2C)
新增:第三方商家入驻(POP),商家自己定价发货
账号新增:商家后台账号(sys_employee + provider_id)
京东 3.0(B2C + B2B2C + O2O)
新增:京东到家,附近超市/药店 → 即时配送
账号新增:线下门店账号(也是 sys_employee + provider_id)
+ 骑手端账号(配送员)
京东 4.0(B2C + B2B2C + O2O + C2C)
新增:拍拍二手/闲置交易
账号新增:个人卖家(入驻为 freelancer type provider)
最后京东一张 sys_user 就能承载所有消费者,一张 sys_provider + sys_employee 承载所有供给方——因为底层结构是一样的。
无论你怎么组合,本质上就是 三类人 + 一张关系表:
平台方 (Platform)
sys_employee (provider_id=NULL)
│
│ 管理
▼
┌──────────────────────┐
│ 供给方 (Provider) │
│ sys_provider │
│ │
│ type枚举决定身份: │
│ · self(自营) │
│ · merchant(商家) │
│ · freelancer(个人) │
│ · store(线下门店) │
│ │
│ 背后是: │
│ sys_employee │
│ (商家后台账号) │
└──────────┬───────────┘
│
│ 提供服务/商品
▼
┌──────────────────────┐
│ 需求方 (Consumer) │
│ sys_user │
│ │
│ 可以是: │
│ · 个人消费者(C) │
│ · 企业采购员(B) │
│ · 发布约拍的用户 │
└──────────────────────┘
关键洞察:B和C在表结构上不需要两张不同的表。 因为一个账号的行为取决于它在某笔交易中扮演的角色,而不是它天生是什么类型。
| 像匠的业务 | 模式标签 | 供给方 provider.type | 需求方 |
|---|
| 自营门店拍照 | B2C + O2O | self | sys_user |
| 加盟店拍照 | B2B2C + O2O | franchise | sys_user |
| 共享图像工具 | B2B + B2C | 工具不需要供给方概念 | sys_employee + sys_user |
| 自由摄影师接单 | C2C + O2O | freelancer | sys_user |
| 用户约拍 | C2B(用户找服务) | 反向:sys_user发布需求 | freelancer/self/franchise 竞标 |
| 企业团购拍照 | B2B | self/franchise | sys_user(企业采购员身份) |
所以像匠一张 sys_provider 用 type 字段全装下了,和京东的架构本质上一样。
所有平台模式的本质 =
三个角色: 平台方 + 供给方 + 需求方
三张表: sys_employee + sys_provider + sys_user
type字段决定了供给方是B(企业)还是C(个人)
业务场景决定了需求方是B还是C
O2O只是告诉你:交付在线下,需要加地址/预约/核销
京东从 B2C 变成大杂烩组合,底层账号表并没有从 2 张变成 6 张。它只是 sys_provider.type 多了几个枚举值,sys_employee 的权限体系更丰富了。真正变化的不是账号结构,是业务流程——配送、结算、售后这些链路。
这是一个典型的平台演进路径,从单品牌连锁 → 多边服务市场。让我梳理清楚。
像匠平台
│
┌───────┼───────┬──────────┐
▼ ▼ ▼ ▼
自营 加盟 自由职业 共享工具
(自己人) (外部) (个人接单) (所有人用)
这四种供给方对系统的需求有 80% 相同、20% 不同。
核心结论:一张 sys_provider 表全部装下,用 type + source 区分。
class Provider(Base, TimestampMixin, SoftDeleteMixin):
"""服务提供方(自营/加盟/自由职业者 统一表)"""
__tablename__ = "sys_provider"
id: int
name: str # 名称(自营=门店名,加盟=企业名,个人=昵称)
code: str # 唯一编码
type: str # self | franchise | freelancer
source: str # invite(邀请入驻) | apply(自主申请) | platform(平台创建)
# --- 企业信息(自营/加盟必填,个人可空) ---
business_license: str | None
legal_person: str | None
registered_capital: int | None
# --- 个人认证(自由职业者必填,企业可空) ---
real_name: str | None # 真实姓名
id_card: str | None # 身份证
certification_type: str | None # photographer/makeup/retoucher
# --- 共用字段 ---
logo: str | None # 头像/logo
contact_name: str
contact_phone: str
status: int # 0待审核 1启用 2禁用 3驳回
settle_ratio: float # 结算比例
max_employees: int # 员工上限(个人=1)
四种类型的差异只需用 type 字段区分,后端返回时可附带不同表单验证规则、不同审核流程,无需多张表。
┌────────────────────────────────────────────────────────────────────┐
│ 像匠平台账号体系 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 【后台管理端】入口:admin.xiangjiang.com │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 表:sys_employee + sys_provider_id │ │
│ │ │ │
│ │ provider_id=NULL → 像匠平台运营 │ │
│ │ · 平台超管、运营、财务、客服 │ │
│ │ · 权限:审核入驻、管理所有供给方、配置抽佣、仲裁纠纷 │ │
│ │ │ │
│ │ provider_id=1, type=self → 自营店(如"像匠·朝阳大悦城店") │ │
│ │ provider_id=2, type=franchise → 加盟店(如"小陈摄影工作室") │ │
│ │ provider_id=3, type=freelancer → 自由摄影师张三 │ │
│ │ │ │
│ │ 每个供给方的后台功能 80% 一致: │ │
│ │ ✅ 员工管理(个人型最多1人,企业型可多人) │ │
│ │ ✅ 服务/产品管理(上架/下架/改价) │ │
│ │ ✅ 订单管理(接单/履约/退款) │ │
│ │ ✅ 收入结算(查看流水/提现) │ │
│ │ ✅ 共享工具(图像处理工具全部可用) │ │
│ │ ✅ 约拍广场(查看需求/报价接单)← 新功能 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 【C端用户端】入口:xiangjiang.com / 小程序 / App │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 表:sys_user(已有) │ │
│ │ │ │
│ │ ✅ 浏览服务商/作品 → 下单 → 支付 → 评价 │ │
│ │ ✅ 发布约拍需求 → 查看报价 → 选人成交 ← 新功能 │ │
│ │ ✅ 查看订单/约拍记录 │ │
│ │ ✅ 个人中心(收藏/足迹/优惠券) │ │
│ │ ✅ 成为供给方(用户可申请入驻为自由职业者) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
class BookingRequest(Base, TimestampMixin):
"""用户发布的约拍需求"""
__tablename__ = "sys_booking_request"
id: int
user_id: int # 发布需求的人
title: str # 如"6月婚礼跟拍"
category: str # wedding/portrait/family/commercial
description: str # 详细需求描述
budget_min: int | None # 预算下限(分)
budget_max: int | None # 预算上限(分)
city: str # 服务城市
district: str | None # 区域
shoot_date: str # 拍摄日期 YYYY-MM-DD
shoot_time_start: str | None # 开始时间 HH:mm
shoot_duration: int | None # 预计时长(分钟)
# 状态流转
status: int # 0待审核 1待报价 2已有报价 3已选服务商
# 4进行中 5已完成 6已取消 7已过期
expire_at: datetime # 需求过期时间
selected_provider_id: int | None # 选定的服务商
selected_bid_id: int | None # 选定的报价
# 辅助信息
tags: str | None # 标签(json数组)
image_urls: str | None # 参考图
view_count: int # 浏览次数
bid_count: int # 报价数
class BookingBid(Base, TimestampMixin):
"""服务商对约拍需求的报价"""
__tablename__ = "sys_booking_bid"
id: int
request_id: int # 约拍需求ID
provider_id: int # 报价的服务商
employee_id: int | None # 报价的具体员工(企业型才有)
price: int # 报价金额(分)
message: str | None # 报价附言/方案说明
available_time: str | None # 可服务时间段
status: int # 0待查看 1已查看 2已接受 3已拒绝 4已撤回
可以,但分开账号。 张三用手机号注册了 sys_user 是消费者,浏览并下了个约拍单。后来他发现可以入驻当自由摄影师,申请入驻后平台创建:
sys_provider 记录(自由摄影师张三)sys_employee 记录(provider_id=张三, 他就是他自己店唯一员工)- 前端登录页提供角色切换:用户端(消费者) / 管理端(供给方后台)
但 sys_user.id 和 sys_employee.id 是两张独立的表,这是正确的设计——消费者身份和服务商身份是不同的数据域,权限体系、展示信息、操作路径都不一样。
| 层级 | 表 | 谁用 | 典型用户 |
|---|
| 平台运营层 | sys_employee (provider_id=NULL) | 后台管理 | 像匠员工 |
| 供给方管理层 | sys_employee (provider_id=有值) | 后台管理 | 自营/加盟/个人摄影师的后台账号 |
| 供给方主体 | sys_provider | 元数据 | 每个店/个人的主体信息 |
| C端消费者 | sys_user | 用户端 | 来拍照/发约拍的普通用户 |
你的核心设计其实只需要 sys_provider 一张表加 type 字段,sys_employee 加 provider_id。约拍需求另开两张表 sys_booking_request + sys_booking_bid。需要我开始写代码吗?
因为 sys_tenant 和 sys_provider 不是同一个概念的两种叫法,而是两种完全不同的架构模式。
sys_tenant → 竖井隔离模型
sys_provider → 平台共享模型
用一句话说:
Tenant 解决的是"你看不见我的数据",Provider 解决的是"我能在平台上卖东西"。
sys_tenant 的世界:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 租户A │ │ 租户B │ │ 租户C │
│ 金蝶云 │ │ 用友云 │ │ SAP云 │
│ │ │ │ │ │
│ ·员工 │ │ ·员工 │ │ ·员工 │
│ ·报表 │ │ ·报表 │ │ ·报表 │
│ ·审批流 │ │ ·审批流 │ │ ·审批流 │
│ │ │ │ │ │
│ 完全不 │ │ 完全不 │ │ 完全不 │
│ 知道A/B │ │ 知道B │ │ 知道A/B │
│ 的存在 │ │ 的存在 │ │ 的存在 │
└─────────┘ └─────────┘ └─────────┘
↑ ↑ ↑
└───────────┴───────────┘
数据库
各自只查自己的 tenant_id
sys_provider 的世界:
┌─────────────────────────────────────────┐
│ 平台 │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │摄影A │ │化妆B │ │修图C │ │
│ │ ★4.8 │ │ ★4.7 │ │ ★4.9 │ │
│ │ ¥299 │ │ ¥199 │ │ ¥99 │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │
│ └─────────┼─────────┘ │
│ ▼ │
│ 消费者搜索、比价、下单 │
│ │
│ 供给方知道彼此存在,且在竞争! │
└─────────────────────────────────────────┘
| 维度 | sys_tenant | sys_provider |
|---|
| 核心诉求 | 数据隔离,安全合规 | 服务曝光,获取订单 |
| 是否可见 | 租户不知其他租户存在 | 供给方公开竞争 |
| 是否有 C 端 | 没有,只对内 | 必须有,对外展示 |
| 计费方式 | 按功能/席位/存储收费 | 按成交抽佣/入驻费 |
| 个性化 | 字段/流程/界面可定制 | 标准服务项,差异化靠内容和口碑 |
| 关系 | 垂直独立,井水不犯河水 | 水平竞争,用户比价选择 |
典型 Tenant 场景:
- 飞书/钉钉/企业微信 — 每个企业一个租户,内部管理自己的组织架构
- 金蝶云 — 每家企业独立账套
- Jira/Slack/Notion 的 Workspace 概念
典型 Provider 场景:
- 淘宝/美团/滴滴 — 商家入驻,平台撮合交易
- 猪八戒/Upwork — 自由职业者竞标项目
- 像匠 — 门店和个人入驻,服务消费者
如果你的平台具备以下特征,就是 Provider 模式(其实是你现在的情况):
- 供给方之间存在竞争关系(用户比价)
- 供给方需要对 C 端曝光(评分、作品、价格)
- 平台按成交抽佣而非收 SaaS 年费
- 供给方的服务内容不同但同属一个品类
如果有一天,你开始给供给方提供 SaaS 工具(独立的进销存、独立的CRM、独立的财务报表),且供给方之间不应看到彼此数据——那时才需要 Tenant 隔离。
美团外卖:
├── sys_provider 模式 → 商家互相竞争,用户比价选店
└── sys_tenant 模式 → 每个商家后台独立,看不到隔壁店的订单明细
滴滴出行:
├── sys_provider 模式 → 司机在乘客面前是可选的(派单/抢单)
└── sys_tenant 模式 → 司机A永远看不到司机B的收入明细
像匠的混合:
├── sys_provider 模式 → 供给方面向用户端展示、竞争
├── sys_tenant 模式 → 每个供给方后台管理自己的员工/角色/菜单(就是你现在
│ 用 provider_id 隔离数据的部分)
└── 共享工具 → 图像工具对所有供给方可见可用,没有数据隔离
Provider 不是升级版的 Tenant,它们是两个正交的维度。
Tenant = 解决数据归属(这条记录是谁的?)
Provider = 解决交易参与(谁在提供服务?)
你的 sys_provider 模型同时做到了 Provider(前端展示、竞争接单)和 Tenant(后台用 provider_id 做数据隔离),所以看起来"万能",但实际上是把两道题做进了一道。之所以这样做是对的,是因为 O2O 服务市场的供给方天然需要这两重身份。