分销代理多端触达系统 · 二次开发教程(00)

📌 转载声明:本文为《分销代理多端触达系统 · 二次开发教程》系列第 00 篇。转载请注明出处;可运行的配套代码与最新版本见官方开源仓库(Apache 2.0):atomgit.com/pangzi_zi/distributor-touch

架构地图与二开路径总纲

项目:Distributor Touch(distributor-touch 开源项目)· 快消 B2B 分销 SaaS 许可证:Apache 2.0 · 后端 Node.js 22+ 零运行时依赖 本文是整套二次开发教程的总纲,先建立全局认知,再按场景跳转对应篇章。

一、先回答一个问题:这套系统解决什么

一句话:品牌商 → 代理商 → 消费者 的一条链路打透,并且从架构上保证不会踩传销红线。

它不是一个单纯的商城,也不是一个单纯的代理管理系统,而是四端一体的组合:

端使用者形态职责
管理端品牌商运营B/S(PHP 门户页)管商品、订单、代理商、佣金、店铺、合规
代理端代理商B/S + 小程序看业绩、佣金、素材、培训、海报
C 端商城消费者B/S + 小程序逛店、加购、下单、支付
小程序代理 + 消费者UniApp 一套代码编译到 H5 / 微信小程序 / App / 企微侧边栏

在线体验:https://www.chaoyi.club/mp-demo.html(演示站 show.chaoyi.club 数据独立,账号 admin / admin123)


二、目录结构:改哪里先看这里

distributor-touch/
├── backend/                      # Node.js 后端(零依赖,ESM)
│   └── src/
│       ├── server.js             # ★ 单一路由分发入口(1500+ 行,所有 API 都在这)
│       ├── persist.js            # ★ 持久化注册中心
│       ├── seed.js               # 种子数据
│       ├── config.js             # .env / 环境变量
│       ├── handlers/             # ★ 业务模块(30 个,一个领域一个文件)
│       ├── domain/               # ★ 领域规则(合规护栏、佣金引擎、代理层级)
│       ├── auth/                 # JWT + API Key + 中间件
│       ├── agent/                # AI 助手(意图解析 / 工具表 / 编排)
│       ├── store/file-store.js   # 原子写入的 JSON 存储层
│       ├── utils/id-gen.js       # ID 生成器
│       └── tests/                # node:test 单元测试(30 个文件)
│
├── services/
│   ├── php/portal/               # ★ B/S 门户(无框架)
│   │   ├── index.php             #   路由表
│   │   ├── config.php            #   配置 + 导航项
│   │   ├── controllers/          #   PortalController / AgentController / AuthController
│   │   └── views/                #   38 个视图模板
│   ├── docker/                   # Docker Compose(nginx + 多服务)
│   │   ├── nginx/nginx.conf
│   │   ├── www/                  #   官网静态站
│   │   └── docker-compose.yml
│   ├── go/ java/                 # 多语言服务端示例
│
├── frontend/                     # 管理端前端资源
├── harmony/                      # 鸿蒙端
├── uniapp/                       # ★ UniApp 小程序(vite 结构,源码在 src/)
│   └── src/
│       ├── pages.json            #   路由 + tabBar
│       ├── manifest.json         #   多端编译配置
│       ├── pages/                #   页面(含 shop/ 商城 5 页)
│       ├── utils/                #   api / cart / i18n / auth / config / nav
│       └── static/tabbar/        #   图标
│
└── docs/ openapi.yaml            # 文档与 API 契约

三、技术栈与三条设计铁律

3.1 后端零运行时依赖

backend/package.json 的 dependencies 是空的。所有能力用 Node 内置模块实现:

  • HTTP 服务:node:http
  • 路由:手写 if (path === '...' && method === '...') 分发
  • JWT:node:crypto 手写 HMAC
  • 持久化:node:fs 原子写 JSON
  • 测试:node:test 内置

这对你意味着:克隆下来 node src/server.js 就能跑,没有 npm install 的等待,也没有供应链风险。但代价是没有框架帮你做约定 —— 所以下面的规范必须自己遵守。

3.2 三条铁律

铁律说明违反后果
写操作必须 scheduleSave()改了内存里的 Map 一定要触发落盘,否则重启丢数据数据丢失
新增可持久化模块必须注册三处handler 导出 _internalState/_restoreState → persist.js MODULES → server.js 路由重启数据不恢复
领域规则放 domain/,不放 handler合规校验、佣金计算属于领域层护栏被绕过

四、二次开发场景速查表

拿到源码后,最常做的 9 件事和对应要改的地方:

#场景主要改动点教程篇章
1加一个业务实体(如店铺、仓库、会员等级)handlers/xxx.js + persist.js + server.js + tests/02
2加一个管理端页面index.php 路由 + PortalController 方法 + views/ + config.php 导航03
3改商城业务规则(满减、拆单、优惠券)handlers/orders.js + coupons.js + 门户 cart.js + 小程序 utils/cart.js04
4小程序加页面 / 改 UIpages.json + pages/xxx.vue + utils/api.js + utils/i18n.js05
5接支付 / 短信 / 物流handlers/payment.js + gateway.js + config.js07
6加一种语言门户 i18n/ + 小程序 utils/i18n.js05
7改主题 / 品牌换肤handlers/theme.js + frontend/ 样式变量07
8扩展合规校验domain/compliance-guardrails.js06
9加 AI 助手能力agent/tools.js + agent/intent-parser.js + agent/orchestrator.js02 附

五、核心链路:一个请求是怎么走完的

理解这条链路,二开时就知道该在哪一层动手。

5.1 管理端打开「店铺管理」页

浏览器 GET /portal/stores
   ↓ nginx: location /portal/ { rewrite ^/portal/(.*)$ /$1 break; proxy_pass php-portal }
   ↓ PHP: index.php 路由表查到 '/stores' => PortalController::adminStores
   ↓ PortalController::adminStores()
        ├─ ensureToken()          会话里没 token 就用 admin 账号换一个
        ├─ apiCall('/api/stores')      → 后端
        ├─ apiCall('/api/products')    → 后端(用来统计每店商品数)
        └─ render('admin_stores', $data)
   ↓ 后端 GET /api/stores
        ├─ authenticate()         中间件:token 有效?角色够吗?
        └─ storesMod.listStores() 读内存 Map
   ↓ 返回 JSON → PHP 组装 → 渲染 views/admin_stores.php

关键点:PHP 门户不直接碰数据库,它只是后端的透传层 + 视图渲染层。所有业务规则都在 Node 后端。

⚠️ 很多二开同学在门户改了半天发现没效果,就是因为改错了层 —— 表单字段要生效,必须同时改 PHP 的转发链和后端的 handler,少一头字段就静默丢失。

5.2 消费者下一笔跨店订单

购物车(前端 localStorage,按 storeId 分组)
   ↓ 点「结算」→ 逐店调用 POST /api/orders(一店一单)
   ↓ orders.js: 校验 storeId 存在 → 计算金额 → 落库 → scheduleSave()
   ↓ 前端拿到 N 个订单号 → 依次调用 POST /api/orders/:id/pay
   ↓ 成功一个就从购物车移除该店商品;失败的店保留

六、数据是怎么存的

没有数据库,用的是单文件 JSON 快照 db.json。

写操作 → 改内存 Map → scheduleSave()(2 秒去抖)→ saveSnapshot() → db.json
启动   → loadSnapshot() 读 db.json → 各模块 _restoreState() → 跳过 seed

存储层 store/file-store.js 做了三件重要的事:

  1. 原子写入:先写 db.json.tmp 再 rename,避免进程被杀时留下半截文件
  2. Map/Set 序列化:JSON 不认 Map,写入时自动转 Object / Array
  3. 多机支持:config.dataDir 可指向共享文件系统

快照结构(每个注册的模块占一个 key):

{
  "_savedAt": "2026-08-29T04:00:00.000Z",
  "agents": [...],
  "products": [...],
  "orders": [...],
  "stores": [...],
  "coupons": [...]
}
💡 这个设计的取舍:零依赖、易备份、易调试(db.json 直接 cat);代价是不适合百万级数据和高并发写。如果你的客户量级上来了,把 store/file-store.js 换成 MySQL/PostgreSQL 实现即可,上层 handler 代码一行都不用改 —— 这是预留好的替换点。

七、权限模型

三级角色 + 两种认证方式:

角色能做什么
admin全部(品牌商管理员)
agent只看自己的数据(佣金、业绩、素材)
userC 端公开数据

认证中间件 auth/middleware.js 用两张表控制:

const PUBLIC_ROUTES = [        // 无需登录
  { method: 'GET', path: '/api/health' },
  { method: 'GET', path: '/api/themes' },
  // ...
];

const ADMIN_ONLY_ROUTES = [    // 必须 admin 角色
  { method: 'POST', path: '/api/materials' },
  { method: 'POST', path: '/api/orders' },
  // ...
];

二开时加接口,第一件事就是想清楚它属于哪一类,然后决定:

  • 公开 → 加进 PUBLIC_ROUTES(C 端匿名能访问)
  • 管理员 → 加进 ADMIN_ONLY_ROUTES,或在 handler 里判断 currentUser?.role !== 'admin'
  • 其他 → 只要求登录态即可
⚠️ 别图省事把接口全设成公开。C 端页面是完全匿名的,公开接口等于对互联网开放。

八、建议的阅读顺序


九、先记住这 5 个坑

这 5 个坑是实战中真踩过的,写在最前面帮你省时间:

  1. Windows 下 npm test 会 MODULE_NOT_FOUND —— node --test src/tests/ 的尾斜杠在 Windows 解析不了,要用 node --test "src/tests/*.test.js"。
  2. createProduct() 返回的是 { product, warnings } 不是商品本身 —— 直接拿返回值读 .id 会 undefined。
  3. assert.throws() 不返回错误对象 —— 想断言 err.status 得自己包一层 expectError(fn, re)。
  4. PHP 门户的 demo 版禁止接口透传 —— 新增的 /api/xxx 必须在 index.php 路由表里显式注册,否则 403。
  5. 改了 php-portal 的代码必须 rebuild 镜像 —— 它是 COPY 进容器的,不是 volume 挂载,改文件不重建不生效。

下一篇

01 环境搭建与本地跑通 —— 三种启动方式、种子数据、健康检查,以及 Windows 下的专属坑。