分销代理多端触达系统 · 二次开发教程(01)
📌 转载声明:本文为《分销代理多端触达系统 · 二次开发教程》系列第 01 篇。转载请注明出处;可运行的配套代码与最新版本见官方开源仓库(Apache 2.0):atomgit.com/pangzi_zi/distributor-touch
环境搭建与本地跑通
上一篇:00 架构地图与二开路径总纲 本篇目标:10 分钟把四端跑起来,并知道数据存在哪、怎么重置。
一、前置条件
| 组件 | 版本 | 必需性 | 说明 |
|---|---|---|---|
| Node.js | ≥ 22 | 必需 | 后端与小程序构建都靠它 |
| PHP | ≥ 8.0 | 二开 B/S 门户时需要 | 无框架,只需要能跑内置服务器或 FPM |
| Docker + Compose | 任意近期版本 | 全栈联调时推荐 | 一键起 nginx + 后端 + 门户 |
| Git | 任意 | 必需 |
验证:
node -v # v22.x 或更高
php -v # PHP 8.x
docker compose version
后端是零依赖的,不需要 npm install。这是本项目最爽的一点。
二、方式一:只跑后端(最快,推荐二开后端时用它)
cd backend
node src/server.js
看到这段输出就成了:
[persist] 无快照文件,将执行种子初始化
[server] 无持久化数据,执行种子初始化...
[persist] 快照已保存 (28 模块)
[distributor-touch] 后端 MVP α 监听 http://0.0.0.0:3000/api
[distributor-touch] 实例ID: your-hostname-12345
[distributor-touch] 认证: 已启用
[distributor-touch] 持久化: 已启用(自动落盘 + 关闭保存)
[distributor-touch] 环境: development
验证:
# 健康检查(免鉴权)
curl http://localhost:3000/api/health
# 登录拿 token
curl -X POST http://localhost:3000/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}'
# 带 token 查店铺
TOKEN=<上一步拿到的 accessToken>
curl http://localhost:3000/api/stores -H "Authorization: Bearer $TOKEN"
三、方式二:Docker Compose 全栈(推荐做前后端联调时用它)
cd services/docker
docker compose up -d
docker compose ps
起出来的服务(正式站 main + 演示站 show 双实例):
| 容器 | 端口 | 作用 |
|---|---|---|
disttouch-nginx | 80 / 443 | 反向代理,多域名分发 |
disttouch-backend | 3000 | 正式站后端 |
disttouch-portal | 7080 | 正式站 PHP 门户 |
disttouch-backend-show | 3001 | 演示站后端(独立数据卷) |
disttouch-portal-show | 7081 | 演示站 PHP 门户 |
访问:
- 管理端:
http://localhost/portal/login(账号admin / admin123) - 代理端:
http://localhost/agent/dashboard(免登录) - C 端商城:
http://localhost/portal/u/shop(免登录) - 后端 API:
http://localhost/api/health
⚠️ 改了services/php/portal/下的任何文件,必须 rebuild 才生效: ``bash docker compose up -d --build php-portal`因为门户是COPY` 进镜像的,不是 volume 挂载。
四、方式三:单独跑 PHP 门户(改前端页面时最快)
cd services/php/portal
php -S localhost:8080
然后门户会去连 config.php 里配置的后端地址,默认需要后端在 3000 端口跑着。
五、数据存在哪:两套存储机制,别混了
这是新手最容易困惑的地方,必须讲清楚。项目里有两套独立的持久化:
5.1 业务数据 → db.json(与 STORAGE_MODE 无关,一直生效)
// persist.js —— 业务模块快照
saveSnapshot() // 把所有注册模块的 _internalState() 写进 db.json
loadSnapshot() // 启动时读回来,有数据就跳过 seed
- 文件位置:
config.dataDir,默认backend/.data/db.json - 触发时机:所有写操作后调用
scheduleSave()(2 秒去抖) - 覆盖范围:
products/orders/stores/coupons/agents/commission等 28 个模块
5.2 用户账号 → users.json(只有 STORAGE_MODE=file 时才持久化)
// auth/user-store.js
function persistUsers() {
if (config.storageMode === 'file') { // ← 关键判断
saveJSON(USERS_FILE, Object.fromEntries(users));
}
}
STORAGE_MODE 的默认值是 memory:
// config.js
storageMode: getEnv('STORAGE_MODE', 'memory'),
这意味着:
| 场景 | 业务数据 | 用户账号 |
|---|---|---|
裸跑 node src/server.js | ✅ 落 db.json,重启恢复 | ❌ 每次重启重新种子(改的密码会丢) |
Docker(STORAGE_MODE=file) | ✅ 落 db.json | ✅ 落 users.json |
单元测试(NODE_ENV=test) | 内存态,跑完即弃 | 内存态 |
所以本地开发建议显式开 file 模式:
cd backend
STORAGE_MODE=file node src/server.js
或者写进 backend/src/.env.local(该文件已被 .gitignore 忽略,不会误提交):
# backend/src/.env.local
STORAGE_MODE=file
PORT=3000
NODE_ENV=development
AUTH_ENABLED=true
DEFAULT_ADMIN_USER=admin
DEFAULT_ADMIN_PASS=admin123
配置优先级:环境变量 >.env.local>.env> 代码默认值。.env.local用来放本地私密覆盖,.env放团队共享的默认值,两者都不入库。
六、重置数据
二开时经常需要把数据打回初始状态:
# 停掉服务后
rm -rf backend/.data/ # 业务快照
rm -f backend/.data/users.json # 用户(file 模式才有)
# 重启,会自动重新跑种子
cd backend && node src/server.js
也可以只补种商城数据(保留代理商数据):服务启动时会自动检测,如果快照里没有商品/订单就调用 seedMallIfEmpty() 补种。
七、跑测试
cd backend
node --test "src/tests/*.test.js"
预期看到 500+ 个用例全绿。
⚠️ Windows 专属坑:package.json里写的是node --test src/tests/(带尾斜杠), 在 Windows 上会报MODULE_NOT_FOUND。必须用带通配符的写法: ``bash node --test "src/tests/*.test.js"`` macOS / Linux 上两种写法都行。
只看某个模块:
node --test src/tests/stores.test.js
node --test src/tests/orders.test.js
八、环境变量速查
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT | 3000 | 后端端口 |
HOST | 0.0.0.0 | 监听地址 |
NODE_ENV | development | test / production |
AUTH_ENABLED | true | 关闭后所有接口免鉴权(仅本地调试) |
STORAGE_MODE | memory | 设为 file 持久化用户账号 |
DATA_DIR | backend/.data | 数据目录,多机部署可指向共享存储 |
JWT_SECRET | 随机 | 生产必须显式设置,否则每次启动都变,token 全失效 |
ACCESS_TOKEN_TTL | 28800(8h) | 访问令牌有效期(秒) |
REFRESH_TOKEN_TTL | 604800(7d) | 刷新令牌有效期 |
CORS_ORIGIN | * | 生产应收敛到具体域名 |
DEFAULT_ADMIN_USER | admin | 首次启动种子管理员 |
DEFAULT_ADMIN_PASS | admin123 | 上线前必须改 |
PAYMENT_ACTIVE_METHODS | mock | 启用的支付方式,逗号分隔 |
INSTANCE_ID | hostname-pid | 多机部署标识 |
九、跑通之后,验证这 6 个端点
按顺序敲一遍,确认整条链路是通的:
BASE=http://localhost:3000/api
# 1. 健康检查(公开)
curl $BASE/health
# 2. 登录
TOKEN=$(curl -s -X POST $BASE/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}' | jq -r .accessToken)
# 3. 店铺列表(GET 公开,C 端要用)
curl $BASE/stores
# 4. 商品列表(带 storeName 富化)
curl $BASE/products -H "Authorization: Bearer $TOKEN"
# 5. 合规护栏清单
curl $BASE/../compliance/guardrails -H "Authorization: Bearer $TOKEN"
# 6. 审计日志(确认操作有留痕)
curl $BASE/../audit -H "Authorization: Bearer $TOKEN"
全通说明环境没问题,可以开始二开了。
十、常见启动问题排查
| 现象 | 原因 | 解决 |
|---|---|---|
MODULE_NOT_FOUND 跑测试 | Windows 路径尾斜杠 | 改用 node --test "src/tests/*.test.js" |
EADDRINUSE :3000 | 端口被占 | PORT=3001 node src/server.js 或杀掉占用进程 |
| 登录返回 401 | 用户没种子 / 密码被改过 | 删 backend/.data/ 重启 |
| 改了门户代码没效果 | php-portal 是 COPY 进镜像 | docker compose up -d --build php-portal |
| 重启后数据没了 | STORAGE_MODE=memory | 设 STORAGE_MODE=file |
| 接口返回 403 | 新增路由没注册权限 | 查 middleware.js 的 PUBLIC_ROUTES / ADMIN_ONLY_ROUTES |
| 门户页面 403 | demo 版禁透传 | 在 index.php 路由表显式注册该 /api/xxx |
下一篇
02 新增业务模块端到端实战 —— 以真实的「店铺」模块为范例,从 handler 写到路由、持久化、测试,并给出可直接复制的骨架模板。这是整套教程最核心的一篇。