分销代理多端触达系统 · 二次开发教程(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-nginx80 / 443反向代理,多域名分发
disttouch-backend3000正式站后端
disttouch-portal7080正式站 PHP 门户
disttouch-backend-show3001演示站后端(独立数据卷)
disttouch-portal-show7081演示站 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

八、环境变量速查

变量默认值说明
PORT3000后端端口
HOST0.0.0.0监听地址
NODE_ENVdevelopmenttest / production
AUTH_ENABLEDtrue关闭后所有接口免鉴权(仅本地调试)
STORAGE_MODEmemory设为 file 持久化用户账号
DATA_DIRbackend/.data数据目录,多机部署可指向共享存储
JWT_SECRET随机生产必须显式设置,否则每次启动都变,token 全失效
ACCESS_TOKEN_TTL28800(8h)访问令牌有效期(秒)
REFRESH_TOKEN_TTL604800(7d)刷新令牌有效期
CORS_ORIGIN*生产应收敛到具体域名
DEFAULT_ADMIN_USERadmin首次启动种子管理员
DEFAULT_ADMIN_PASSadmin123上线前必须改
PAYMENT_ACTIVE_METHODSmock启用的支付方式,逗号分隔
INSTANCE_IDhostname-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
门户页面 403demo 版禁透传在 index.php 路由表显式注册该 /api/xxx

下一篇

02 新增业务模块端到端实战 —— 以真实的「店铺」模块为范例,从 handler 写到路由、持久化、测试,并给出可直接复制的骨架模板。这是整套教程最核心的一篇。