Execution Plan · 2026-07-05 · v9 增量修订

Nexora ERP
合并执行方案

把 4 套 fork ERP(龙凤 / 摆件 / 妈祖 / 囍铺)合并为一份 Nexora ERP 主干代码,配合一租户一库物理隔离和模块化配置,通过 3 个阶段完成收敛。本文档是直接可执行的作战地图,每一步任务都带完成命令和验证判据。

owner: mike · target repo: nexora/nexora-erp(GitLab gid=75, private, 空)· baseline: xylferp_php
01 / 概览

一句话说清楚要做什么

龙凤 ERP(xylferp_php)为骨架起点建立 nexora/nexora-erp 主干 · 通过 3 个阶段依次接入龙凤(未启用客户,验证用)、摆件(已停,验证加租户流程)、妈祖(主战场,深度并入并抽模块) · 每个租户独立一个 DB(物理隔离) · 老的 xipugold/* repo 在完成迁移前原样保留 · 迁移完成后归档 read-only。

3 条硬约束

一份代码
所有租户跑同一份 Nexora 主干代码。客户差异化通过「租户配置 + 可选模块」实现,禁止为某个客户复制/修改主干代码。
一租户一库
每个客户独立 MySQL DB(nexora_<slug>),物理隔离。业务表现有 aid 字段不动,值恒等于该库租户 id,退化为"防呆兜底"(错连别家库查不到)。
模块化差异
客户独有功能(贵金属 / 妈祖商城 / 阿里云图搜)拆为可选模块,通过中心库 tenant_modules 表按租户启用。关闭模块 = 该租户看不到任何相关菜单/接口/数据表。
02 / 现状

哪里出发

2.1 客户与主干仓库

客户 主干 repo 首次 commit Commits Ctrl 近 30 天活动 血缘
龙凤 xylferp_php 2025-11-22 113 75 0 commits · 客户尚未启用 原始仓 · 唯一有真实开发历史 · 业务面最广(批发+零售)
摆件 baijian_php 2026-02-09 4 73 0 commits · 客户已停 从 xylferp squash · 几乎没改动
妈祖 pyerp_php 2026-02-09 104 112 27 commits · 极活跃 从 xylferp squash 后深度定制 · 独有 58 controller(Ymetal 贵金属 / Ep* 妈祖商城 / Xipu 跨接) · 加了 pt_type 第三层租户
囍铺 xipu_erp_php 2026-02-09 17 75 10 commits · 客户正在生产 从 xylferp squash · 独有金价 3 方法 + 阿里云图搜 2 controller

2.2 关键实证数据

2.3 关键漏洞·数据契约差异(v9 补)

⚠️ v8 → v9 修订原因

v8 §2.2 只做了 controller 文件级 md5 diff,假设了 4 家 fork 血缘相同 = 数据契约相同。执行到 Phase 2B 时暴露:妈祖 DB schema 与其他 3 家完全不同,主干 login 逻辑覆盖不了妈祖。v9 补此对比表 + Phase 2B v2 修复方案(见 §11)。

契约字段 龙凤 xylferp 摆件 baijian 囍铺 xipu 妈祖 pyerp
表前缀 xipunum_ xipunum_ xipunum_ is_
user 表名 xipunum_erp_user is_user
密码字段 password pwd
密码盐 bwqinr xipunum → 已迁 bwqinr xipunum → 已迁 bwqinr erpsm
密码算法 md5(pwd+salt) md5(md5(pwd)+salt) 双重
Login 入口 Api::login Api::login + Apiadmin
用户过滤 state=0 + aid + bid + department 无 state · aid/bid header

🎯 教训条(v9 新增)

数据契约 first · 代码血缘 second。任何跨仓合并战略,方案必须先做数据契约对比表(schema / 字段名 / 密码算法 / 认证过滤逻辑),再做代码血缘 md5 diff。反过来做 = v8 到 v9 的痛苦教训。

03 / 目标架构

一张图看懂 Nexora

L3 · 外部入口 B 端管理后台 Vue 2 · 各租户独立域名 app_xiao_shou_kotlin UniApp 多客户 App 妈祖商城 pyerp_shop_php · 未来所有小程序基座 妈祖商城小程序 pyerp_shop_wx L2.5 · 统一 API 网关 鉴权 · 租户路由 · WS 转发 L2 · NEXORA ERP 核心 · 一份代码 PHP 主干 (ThinkPHP 6) 账号 · 权限 aid+bid+pt_type 功能开关 模块 · 菜单级 商品 · 库存 通用 54 ctrl 订单 · 财务 采购 · 销售 PHP 可选模块(按租户启用) ymetal 贵金属 50 ctrl myhw-shop 妈祖商城对接 aliyun-image 图搜 · OSS mall-sync 商城 API Kotlin 后端(变薄后) rn.rw.erp · WS 长连接 · 打印下发 · 消息推送 · ≤15 controller L2 · 中心库 nexora_platform tenants(租户目录 + DB 连接串) tenant_modules(启用清单) users · user_tenants(跨租户账号) schema_migrations(全局版本号) 全局 · 跨租户 · 只存目录/账号/开关,不存业务 L1 · 租户业务库 · 一租户一库 · 物理隔离 🗄️ nexora_xylferp 🗄️ nexora_baijian 🗄️ nexora_pyerp 🗄️ nexora_xipu(择机) 🗄️ ... 未来新客户 每库保留原 aid + bid + pt_type 字段作"防呆",值恒等于该库租户 id
04 / 仓库处置总表

34 个仓库 · 每个一个动作

执行时遇到"这个 repo 要不要动 / 怎么动"的问题,以本表为准。Action 列 = 强制约束

ERP 主干仓库

RepoAction阶段说明
xylferp_phpMERGEPhase 1骨架起点 · clone 为 nexora-erp
xylferp_webMERGEPhase 1前端骨架起点 · clone 为 nexora-erp-web
baijian_phpMERGEPhase 2验证「加租户」流程
baijian_webARCHIVEPhase 290 天 0 commits
pyerp_phpMERGEPhase 2主战场 · Ymetal / Ep* 抽模块 · pt_type 合入主干
pyerp_webMERGEPhase 2妈祖前端合入 nexora-erp-web
xipu_erp_phpLATERPhase 373/75 controller 在龙凤里 · 视客户情况
xipu_erp_webLATERPhase 3同 xipu_erp_php

商城仓库

RepoAction阶段说明
pyerp_shop_phpMERGEPhase 3唯一商城基座 · 合入 Nexora mall-sync 模块
pyerp_shop_wxMERGEPhase 3唯一小程序基座 · 未来所有小程序基于此
baijian_shop_phpARCHIVEPhase 320G · 客户已停

手机端仓库

RepoAction阶段说明
app_xiao_shou_kotlinKEEPPhase 3669 commits 多客户 UniApp 壳 · Phase 3 演化为 Nexora 统一手机端 · Phase 1-2 保留原样
pyerp_appKEEPPhase 3妈祖手机端(和 app_xiao_shou_kotlin 目前无代码合并)· Phase 3 把功能重写进后再归档
kotlinapi_uniappKEEPPhase 3囍铺原版手机端 · 已被 app_xiao_shou_kotlin 演化替代 · Phase 3 直接归档
sprin-boot-kotlin219KEEPPhase 3Kotlin 后端 · Phase 3 变薄:52 controller → ≤15
sprin-boot-kotlin219-xylfKEEPPhase 32 commits 分叉 · 合入主线
fastify_spring_app_apiARCHIVEPhase 3废弃分支
fastify_ts_app_apiARCHIVEPhase 3废弃分支

弃用仓库(不投入工作)

RepoAction说明
laiya_phpDROP莱雅商城主站 · 客户不再使用
laiya_wxDROP莱雅商城主小程序
laiya_jxs_wxDROP莱雅经销商小程序
laiya_lss_wxDROP莱雅零食师小程序
lyshop_wxDROP莱雅关联小程序
hjhsDROP黄金回收(CRMEB)· 客户已停 · 不同基因不合并

其他(战略无关)

RepoAction说明
pyerp_php_apiARCHIVE2 commits · API 抽出实验未成气候
pyerp_shop_h5_distOUT-OF-SCOPE前端构建产物
wl_php / wl_webOUT-OF-SCOPE未评估(疑似"物流")
waimao_phpOUT-OF-SCOPE未评估(疑似"外贸")
supererp / hjt / xipu_newOUT-OF-SCOPE历史产物 · 未评估
electron_exe / python-auto-print-desktop / passwordOUT-OF-SCOPE桌面工具
05 / 模块清单

Nexora 主干由核心+可选模块组成

5.1 核心模块(所有租户必装)

模块包含内容
account账号 / 角色 / 权限 · 复用现有 aid/bid/pt_type 上下文注入
feature-flag模块级 + 菜单级功能开关引擎 · 读中心库 tenant_modules / tenant_features
product商品 / SKU / 分类 · 通用 54 controller 里商品相关部分
inventory库存 / 库位 / 调拨 / 盘点 / 出入库 · 含龙凤零售出库特有的 outboundBack/queryIsTuihuo
order采购 / 销售 / 审核状态机 / Bill / 财务
store门店 / 仓库 / 收银
supplier-customer供应商 / 客户 / 联系人 / 会员

5.2 可选模块(按租户启用)

模块建于包含内容 · 目标租户
aliyun-imagePhase 1阿里云图搜 + OSS(囍铺 2 独有 controller)· 未来给囍铺租户用
ymetalPhase 2贵金属:50 个 Ymetal* controller + 40+ 张表 + 18884 行代码 · 妈祖专属
myhw-shopPhase 2妈祖商城对接:Epuser / Eprole / Epservice / Epxipu* · 妈祖专属
gold-pricePhase 3金价管理(get/save_gold_price + get_gold_code)· 囍铺独有,合入时抽出
mall-syncPhase 3ERP ↔ 妈祖商城对接:products.sync / orders.push / stock.query
mobile-apiPhase 3Kotlin 变薄后的 WS + 打印 + 推送对接

5.3 模块生命周期规范

每个模块目录 modules/<name>/ 自包含:

06 / Phase 1

Phase 1 · 龙凤先入

PHASE 1 骨架 + 首个租户

目标 · 以 xylferp_php 为骨架建立 nexora/nexora-erp 主干,把龙凤客户作为第一个租户接入,验证「一份代码 + 一租户一库」框架成立。

前置条件

  • GitLab nexora group 已建(gid=75, private)· 已完成
  • 4 个开发已加入 group Developer 权限(root / dev-alice / dev-bob / dev-simon)· 已完成

任务清单

1.1建 nexora-erp repo,以 xylferp_php 为起点
ssh://git@gitlab.nexora.restry.cn:18826/nexora/nexora-erp.git
# 从本地 clone xylferp,推到 nexora-erp
git clone ssh://git@gitlab.nexora.restry.cn:18826/xipugold/xylferp_php.git nexora-erp
cd nexora-erp
git remote set-url origin ssh://git@gitlab.nexora.restry.cn:18826/nexora/nexora-erp.git
git push -u origin main --tags
完成判据 · curl -s $URL/api/v4/projects/nexora%2Fnexora-erp 返回 200
· git log --oneline | wc -l ≥ 113(保留原始 git 历史)
1.2同步建 nexora-erp-web(前端骨架)
git clone ssh://git@gitlab.nexora.restry.cn:18826/xipugold/xylferp_web.git nexora-erp-web
cd nexora-erp-web
git remote set-url origin ssh://git@gitlab.nexora.restry.cn:18826/nexora/nexora-erp-web.git
git push -u origin main --tags
完成判据 · curl -s $URL/api/v4/projects/nexora%2Fnexora-erp-web 返回 200
1.3建中心库 schema
CREATE DATABASE nexora_platform DEFAULT CHARSET utf8mb4;
USE nexora_platform;

CREATE TABLE tenants (
  id INT PRIMARY KEY AUTO_INCREMENT,
  slug VARCHAR(32) UNIQUE NOT NULL,   -- xylferp / baijian / pyerp / xipu
  name VARCHAR(128) NOT NULL,          -- 龙凤 / 摆件 / 妈祖 / 囍铺
  db_dsn VARCHAR(255) NOT NULL,        -- mysql://user:pass@host:port/nexora_xylferp
  aid INT NOT NULL,                    -- 该租户在业务库的 aid 值(防呆参考)
  bid INT DEFAULT 0,
  pt_type TINYINT DEFAULT 1,
  status ENUM('active','paused','archived') DEFAULT 'active',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE tenant_modules (
  tenant_id INT,
  module VARCHAR(64),   -- aliyun-image / ymetal / myhw-shop / gold-price / mall-sync / mobile-api
  enabled TINYINT DEFAULT 1,
  PRIMARY KEY (tenant_id, module)
);

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(64) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  name VARCHAR(128)
);

CREATE TABLE user_tenants (
  user_id INT,
  tenant_id INT,
  role VARCHAR(32),
  PRIMARY KEY (user_id, tenant_id)
);

CREATE TABLE schema_migrations (
  version VARCHAR(64) PRIMARY KEY,
  applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
完成判据 · SHOW TABLES FROM nexora_platform 应返回 5 张表
1.4建 nexora_xylferp DB,从 xylferp 生产迁数据
# 备份龙凤当前 DB
mysqldump -u<user> -p xylferp | gzip > xylferp_$(date +%Y%m%d).sql.gz

# 恢复到 nexora_xylferp
gunzip -c xylferp_*.sql.gz | mysql -u<user> -p nexora_xylferp

# 中心库注册龙凤租户
INSERT INTO nexora_platform.tenants (slug, name, db_dsn, aid, bid, pt_type)
VALUES ('xylferp', '龙凤', 'mysql://...@.../nexora_xylferp', 1, 0, 1);
完成判据 · SELECT COUNT(*) FROM nexora_platform.tenants WHERE slug='xylferp' = 1
· SELECT COUNT(*) FROM nexora_xylferp.is_menu 数量和原 xylferp DB 一致
1.5改造 BaseController 从中心库读租户上下文

原 xylferp 的 aid/bid/pt_type 从 token 回填。改造后:token 里带 tenant_slug,BaseController 先查中心库 tenants 拿到 db_dsn + aid/bid/pt_type,再切到对应租户库。

完成判据 · 用龙凤租户 token 请求 /api/user/info,返回数据来自 nexora_xylferp
· 用一个错误 tenant_slug 请求,返回 401
1.6抽第一个可选模块 aliyun-image(样板)

从 xipu_erp_php 复制 Aliyunimagesearch.php + Aliyunoss.php 两个 controller,放到 modules/aliyun-image/,包括路由/菜单/权限声明。龙凤租户默认不启用。

完成判据 · grep -r "Aliyunimagesearch" app/controller/ 应无输出(不在主干)
· SELECT * FROM tenant_modules WHERE tenant_id=1 AND module='aliyun-image' 应返 0 或 enabled=0
· 登录龙凤租户前台,搜"图搜"应搜不到菜单
1.7部署 Nexora 运行环境

选一台服务器(建议内网 dev 环境优先),部署 nginx + PHP + nexora-erp 代码,前端部署 nexora-erp-web。域名先用 nexora-dev.internal

完成判据 · curl -sI http://nexora-dev.internal/api/health 返回 200
· 前台用龙凤租户账号登录,能看到采购 / 销售 / 库存 / 门店 / 报表 / 零售所有主功能菜单
1.8落地运维套件最小可用版(见 §09)

写 4 个脚本:tenant-onboard.sh / migrate-all.sh / backup-tenants.sh / monitor.sh。放到 nexora-erp/bin/

完成判据 · bin/tenant-onboard.sh test 能建出 nexora_test DB 并注册到 tenants(用完删掉)
· bin/backup-tenants.sh 能在 /backup/tenant/xylferp/ 下生成 .sql.gz

Phase 1 出口标准(all 通过才进 Phase 2)

  • 功能验证:龙凤客户/内部人员用龙凤租户账号登录 Nexora,批发 + 零售所有关键单据可以走通一遍(采购单/销售单/入库出库/盘点/报表)
  • 数据一致:同一操作在 Nexora 和原 xylferp 生产上得到相同结果(抽 5 个业务场景对比)
  • 模块隔离:grep -rE "aliyun|Aliyun" app/controller/ 主干目录零命中
  • 租户切换:同样的接口传不同 tenant_slug token,返回不同租户库的数据
  • 运维套件:tenant-onboard / migrate-all / backup 三个脚本各跑通一次
07 / Phase 2

Phase 2 · 摆件 + 妈祖并入

PHASE 2A 摆件迁入(验证加租户流程)

目标 · 用 Phase 1 的运维套件把摆件作为第 2 个租户接入,证明「新增租户 = 一条命令」成立。

任务清单

2A.1用 tenant-onboard 脚本开摆件租户
bin/tenant-onboard.sh baijian --name "摆件" --aid 2
完成判据 · SELECT * FROM nexora_platform.tenants WHERE slug='baijian' 返回 1 行 · status=active
· SHOW DATABASES LIKE 'nexora_baijian' 存在
2A.2从 baijian_php 生产迁入历史业务数据(如需要)
mysqldump -u<user> -p baijian > baijian.sql
mysql -u<user> -p nexora_baijian < baijian.sql
# 校验 aid 一致
UPDATE nexora_baijian.is_user SET aid=2 WHERE aid != 2;  -- 全部对齐到租户 id
完成判据 · 主要业务表行数和原 baijian DB 一致
· SELECT DISTINCT aid FROM nexora_baijian.is_user 只返回 2
2A.3摆件租户切换 · diff 主干缺失能力

用摆件账号登录 Nexora 前台,把主要业务流程走一遍。如果发现主干缺失能力(baijian 独有 0 个 controller,理论应完美复用),记录并补主干。

完成判据 · 摆件核心业务(采购/销售/库存)全部可用
· 修 1 个 bug(在 Nexora 主干)后,龙凤 + 摆件两租户都验证生效
· 整个流程 15 分钟内跑通(证明加租户已经工业化)

Phase 2A 出口

  • 2 个租户(龙凤 + 摆件)在同一份 Nexora 部署上并存,互不影响
  • tenant-onboard 脚本工业化:15 分钟内能加一个新租户

PHASE 2B 妈祖深度并入(主战场)

目标 · 把妈祖 pyerp_php 的 58 个独有 controller 分拆成 3 个可选模块(ymetal / myhw-shop / gold-price 里其一)+ 主干扩展,把 pt_type 三层租户合入主干。妈祖客户切换到 Nexora,老 pyerp_php 冻结。

任务清单

2B.1抽 ymetal 模块(贵金属)

把 pyerp_php 里 50 个 Ymetal*.php controller + 相关 model + 40+ 张 Ymetal_* 表 migrations 全部搬到 modules/ymetal/。定义 Facade。清理主干里对 Ymetal 类的直接引用。

完成判据 · grep -rE "Ymetal|ymetal" app/(主干目录)零命中
· ls modules/ymetal/controller/ | wc -l = 50
· ls modules/ymetal/migrations/*.sql | wc -l ≥ 40
2B.2抽 myhw-shop 模块(妈祖商城对接)

把 pyerp_php 里 Epuser / Eprole / Epservice / Epxipu* 系列 controller 搬到 modules/myhw-shop/

完成判据 · grep -rE "^use.*Ep(user|role|service|xipu)" app/ 零命中
2B.3评估 Xipu.php(pyerp 里跨接喜铺的代码)

pyerp 里的 Xipu.php 里查 erp_category 表 —— 是否仍在生产使用?

  • 如仍用:合入 modules/myhw-shop/ 或单独模块 myhw-xipu-bridge
  • 如废弃:主干移除,归档决策
完成判据 · 决策记录在 modules/DECISIONS.md · Xipu.php 归位
2B.4合入 pt_type 三层租户到主干

pyerp 独有的 pt_type(产品线维度,1/2/3/4)合入主干 BaseController 的上下文注入。所有租户获得 pt_type 能力(默认值 1)。

完成判据 · 主干 BaseController grep define('pt_type' 有 1 处
· 龙凤 + 摆件两租户 pt_type 默认 1,不影响原有功能
2B.5合入 pyerp 的 BaseController / common.php 差异

pyerp 的 BaseController.phpcommon.php md5 已与其他家不同。做 3-way diff,把有价值的公共逻辑(sqlAuth / getBaseRoot / frameScope / erpPostInput 等)合入主干,妈祖专属逻辑放模块。

完成判据 · 主干 app/common.php 覆盖 pyerp / xylferp / xipu 三家的公共函数需求
· modules/myhw-shop/common.php 只放妈祖专属
2B.6开妈祖租户 + 迁入数据
bin/tenant-onboard.sh pyerp --name "妈祖" --aid 1 \
  --modules ymetal,myhw-shop

# 生产 pyerp DB → nexora_pyerp(选低峰,冻结原库)
mysqldump -u<user> -p pyerp | gzip > pyerp_$(date +%Y%m%d).sql.gz
gunzip -c pyerp_*.sql.gz | mysql -u<user> -p nexora_pyerp
完成判据 · SELECT COUNT(*) FROM nexora_platform.tenant_modules WHERE tenant_id=(SELECT id FROM tenants WHERE slug='pyerp') = 2
· SELECT * FROM nexora_pyerp.Ymetal_原料库存 LIMIT 1 能查到数据(模块 migrations 生效)
2B.7妈祖客户切换(选低峰窗口)
  • 冻结原 pyerp_php 生产(nginx 加维护页)
  • 跑最后一次增量数据同步
  • DNS/反代切到 Nexora
  • 观察 48 小时
完成判据 · 切换窗口 ≤ 2 小时
· 切换后 48 小时内 妈祖客户零投诉
· 关键单据(采购/销售/贵金属日结/门店流水)当天正常
失败信号(触发回滚) · 妈祖客户 30 分钟内多次报错
· Ymetal 模块任何日结/结算流程出错
· 数据串到其他租户库(SELECT DISTINCT aid FROM nexora_pyerp.is_sell 出现非 1 的值)

Phase 2 出口标准

  • 3 个租户(龙凤 + 摆件 + 妈祖)全部在同一份 Nexora 主干代码 + 一租户一库跑
  • ymetal 模块可切换:摆件租户前台无任何贵金属菜单/接口/表
  • 妈祖客户业务无中断 · 老 pyerp_php 冻结成 read-only 归档
  • 主干代码 grep -rE "Ymetal|Ep(user|role|service)" app/ 零命中
08 / Phase 3

Phase 3 · 手机端 + 商城 + Kotlin变薄 + 囍铺择机

PHASE 3A Kotlin 后端变薄

目标 · sprin-boot-kotlin219 从 52 个 controller 精简到 ≤15,只保留 WebSocket / 打印下发 / 消息推送 / 定时任务。业务 CRUD 全部迁到 Nexora PHP 主干。
3A.1审计 52 个 Kotlin controller 分类
cd sprin-boot-kotlin219
grep -rl "@RestController" --include="*.kt" | while read f; do
  echo "== $f =="
  grep -E "@GetMapping|@PostMapping" "$f" | head -20
done > kotlin-audit.md

每个 controller 分类:KEEP(WS / 打印 / 推送 / job)/ MIGRATE(业务 CRUD,迁到 PHP)。

完成判据 · 审计报告分类完成 · 每个 controller 有明确 tag
· KEEP 数 ≤ 15 · MIGRATE 数 = 52 - KEEP
3A.2MIGRATE 类 controller 的 PHP 对等实现

对每个 MIGRATE controller,在 Nexora 主干或对应模块里实现相同的接口(路径 + 参数 + 返回结构)。

完成判据 · 对每个迁移接口写 diff 对比测试(Kotlin 版 vs PHP 版返回相同 JSON)
· 通过率 100%
3A.3app_xiao_shou_kotlin 切流量到 PHP 版接口

修改 SERVER_LIST,把囍铺/周锦记的 backend 从 kotlin 改为 php,请求走 Nexora 主干。

完成判据 · 抓包确认手机端所有业务请求打到 Nexora 主干
· Kotlin 后端只收 WS 长连接 + 打印 + 推送
3A.4Kotlin 后端下线业务 controller

MIGRATE 类 controller 从 Kotlin 后端删除 · 只保留 KEEP 类。

完成判据 · grep -rl "@RestController" sprin-boot-kotlin219 | wc -l ≤ 15
· Kotlin 后端连续跑一周,业务无异常
3A.5Kotlin 只读原则:禁止写业务表

Kotlin 后端要写数据时,通过 HTTP 调 Nexora PHP 主干 API。DB 连接改成只读账号。

完成判据 · Kotlin DB 连接串使用 read-only 账号
· 尝试 INSERT/UPDATE 应报权限错误
3A.6sprin-boot-kotlin219-xylf 合入主线

xylf 分叉只有 2 commits,cherry-pick 差异合入 sprin-boot-kotlin219,归档 -xylf。

完成判据 · -xylf repo GitLab 标 archived

PHASE 3B 手机端归口

目标 · 把 pyerp_app 的业务功能重写进 app_xiao_shou_kotlin,统一为 Nexora 手机端。kotlinapi_uniapp 直接归档(已被 app_xiao_shou_kotlin 覆盖)。
3B.1app_xiao_shou_kotlin 加租户切换到 Nexora

SERVER_LIST 里"妈祖"/"周锦记" 等 host 改为 Nexora 主干域名。

完成判据 · App 里选任一租户登录,请求打到 Nexora
3B.2把 pyerp_app 独有业务功能重写进 app_xiao_shou_kotlin

pyerp_app 独有页面/组件(妈祖手机端专属)逐个移植。

完成判据 · 妈祖客户用 app_xiao_shou_kotlin 打包的 App 能覆盖原 pyerp_app 所有功能
· 妈祖用户测试反馈无缺失
3B.3归档 pyerp_app + kotlinapi_uniapp

GitLab 上把两个 repo 标 archived。

完成判据 · 两 repo GitLab 状态 = archived · README 里指向 app_xiao_shou_kotlin

PHASE 3C 妈祖商城 + mall-sync 模块

目标 · pyerp_shop_php 作为唯一商城基座接入 Nexora · 通过 mall-sync 模块的 3 条 API 通信,消除商城对 ERP 库的直查。
3C.1建 mall-sync 模块

modules/mall-sync/ 定义 3 条 API:

  • POST /mall-sync/products.sync — ERP → 商城推商品/SKU/价格
  • POST /mall-sync/orders.push — 商城 → ERP 推订单
  • GET /mall-sync/stock.query — 商城 → ERP 查实时库存
完成判据 · 3 个 endpoint 用 curl 测通
· Postman collection 记录到 modules/mall-sync/docs/
3C.2pyerp_shop_php 改造为走 mall-sync API

移除 pyerp_shop_php 里对 ERP 库表的直查 · 全部改为 curl mall-sync API。

完成判据 · grep -rE "Db::connect|Db::name.*is_" pyerp_shop_php/application/ 应零命中
· 商城 + ERP 可以部署到不同服务器
3C.3合入 nexora-mall(可选)

如果决定把商城代码也纳入 nexora group,创建 nexora/nexora-mall · 从 pyerp_shop_php clone · 否则 pyerp_shop_php 原样保留在 xipugold。

完成判据 · 决策记录在 modules/mall-sync/DECISION.md

PHASE 3D 囍铺择机

目标 · Phase 3 末评估囍铺状态,做二选一决策。
3D.1评估囍铺客户状态

查最近 30 天囍铺业务活跃度(登录用户数 / 单据量 / 客户投诉) + 客户业务方向。

  • 方案 A · 合并:囍铺仍活跃 → 抽出 gold-price 模块(3 方法)+ 启用 aliyun-image 模块(Phase 1 已抽) · 用 tenant-onboard 开囍铺租户 · 迁数据 · 切流量
  • 方案 B · 自然退役:囍铺客户业务萎缩 → 老 xipu_erp_php 原样跑到自然退役 · 归档时打 tag retired
完成判据 · 决策记录在 DECISION-xipu.md

Phase 3 出口标准(战略完成)

  • 3-4 个租户全部在 Nexora 主干代码 + 一租户一库跑(囍铺看 3D 结果)
  • Kotlin 后端 controller 数 ≤ 15,只做实时/推送/打印
  • 手机端只有一份 App 打包(app_xiao_shou_kotlin)
  • 妈祖商城通过 mall-sync API 通信,商城代码无直查 ERP 库
  • 老 xipugold group repo 全部归档 read-only(除 xipu_erp_php 视 3D 决定)
  • 莱雅 6 个 repo(laiya_* + lyshop_wx + hjhs)全部 GitLab 标 archived
09 / 运维套件

一租户一库的必备基础设施

一租户一库的代价 = 运维成本随客户数线性增长。必须把每个环节脚本化。Phase 1 就要落地最小可用版,后续 Phase 持续增强。

9.1 tenant-onboard(新租户开通)

位置:nexora-erp/bin/tenant-onboard.sh

tenant-onboard.sh <slug> \
  --name "<中文名>" \
  --aid <租户 id> \
  [--modules module1,module2,...]

# 一次做 4 件事:
# 1. 建 nexora_<slug> DB
# 2. 跑核心模块 migrations
# 3. 按 --modules 跑可选模块 migrations
# 4. 中心库 tenants + tenant_modules 注册

验证:执行完 30 秒内新租户能登录,前台菜单只显示启用的模块。

9.2 migrate-all(migrations 分发)

位置:nexora-erp/bin/migrate-all.sh

migrate-all.sh [--dry-run]

# 遍历中心库 tenants,对每个租户 DB:
# 1. 检查 schema_migrations 表
# 2. 执行未跑过的 migration
# 3. 失败即停,输出报错(不允许"跑了一半就走人")
# 4. 只跑该租户启用模块的 migrations

验证:主干加 1 张新表,跑 migrate-all,所有租户 DB 都出现该表。

9.3 backup-tenants(备份轮询)

位置:nexora-erp/bin/backup-tenants.sh(cron 每晚 03:00)

backup-tenants.sh

# 遍历中心库 tenants,每个租户:
# 1. mysqldump | gzip → /backup/tenant/<slug>/<date>.sql.gz
# 2. 保留策略:daily 7 天 · weekly 4 周 · monthly 6 月
# 3. 校验:大小比昨天缩水 >20% 报警
# 4. 校验:gunzip -t 能完整解压

验证:任一租户 ls /backup/tenant/<slug>/ 有当日备份文件。

9.4 monitor(部署监控)

位置:nexora-erp/bin/monitor.sh(cron 每 5 分钟)

monitor.sh

# 按租户视角检查:
# - 各租户 DB 可连接
# - 各租户前台 /api/health 返回 200
# - 备份状态(最近 24h 内有成功备份)
# - 慢查询(> 1s)计数
# - 磁盘用量 > 80% 报警
# 异常发飞书告警

验证:手动停一个租户 DB,5 分钟内收到告警。

10 / 明确不做的事

负范围 · 防止scope creep

执行时任何"顺便把 X 也做了"的问题,以本节为准。不在 §04 总表 + §06-08 路线图里的东西,默认不做。

关于弃用的仓库

  • 莱雅商城(laiya_php / laiya_wx / laiya_jxs_wx / laiya_lss_wx / lyshop_wx)· 客户已不用 · 不写归档脚本 · 不写数据导出 · 不跟客户沟通 · GitLab 标 archived 即完成
  • hjhs 黄金回收 · 独立 CRMEB 200+ controller · 不做合并 · 不做 SSO · 不做任何对接
  • baijian_shop_php · 客户已停 · 不做迁移 · 不做数据备份

关于囍铺

  • Phase 1-2 不动囍铺任何代码 · 客户业务不中断
  • Phase 3D 前 · 不做囍铺前端 diff · 不做囍铺数据库 schema diff
  • Phase 3D 决策方案 B(自然退役)· 不投入任何合并成本

关于数据迁移

  • Phase 1-3 期间 · 不做真实数据双向同步 · 新 Nexora 库和老库物理独立
  • 数据迁移只在客户切换时做单向 dump/restore
  • 不做增量同步中间件 · 不做双写
  • 迁移完成后的回迁策略:合并完 + 稳定 3 个月后再评估

关于手机端

  • Phase 1-2 手机端不动 · 老 App / 老后端原样跑
  • 不做客户 App 强制升级 · 不做旧版本兼容性回退
  • 不做 iOS 上架 / 华为渠道打包相关(维持原状)

关于代码风格与技术债

  • 不做代码风格重构 · 不做 ThinkPHP 6 → 7/8 升级 · 不做 Vue 2 → 3 升级
  • 不做 UI 层重设计 · 保持原有前端交互
  • 不做测试覆盖率补齐(除非验收标准明确需要)
  • 不做性能优化(除非影响业务可用性)

关于外部集成

  • 不做 SSO 打通(除 Nexora 内 PHP ↔ Kotlin 之间必须)
  • 不做飞书 OAuth 集成 · 不做微信开放平台账号打通
  • 不做第三方 ERP / SaaS 对接
11 / Phase 2B v2 修复方案

妈祖「以龙凤为准」合并方案

🔧 v9 新增章节

v8 Phase 2B "妈祖深度并入" 假设主干 login 能覆盖妈祖,实际执行时因 schema 分歧失败。v9 明确「以龙凤为准 · 妈祖用户逻辑弃用」的修复策略,并给出可执行的三阶段 checklist。

11.1 战略决策记录

DATE: 2026-07-06 · DECISION-MAKER: mike · CONFIRMED: 客户方接受

「以龙凤为准,妈祖也合并到龙凤里面来,只保留一份,如果用户相关的有冲突,那就放弃妈祖的用户部分的逻辑。使用龙凤的」

11.2 保留 vs 弃用清单

基于 pyerp_app(274 vue)+ pyerp_shop_wx/h5/php(5600 文件)+ pyerp_php/eperp SPA + pyerp_php/app 后端 四层真实引用扫描

✅ 保留 46 个 controller · 归入 4 模块

模块 Controller 数 Controller 清单(节选)
ymetal(贵金属) 34 Ymetalapi / Ymetalbusiness / Ymetalcategory / ...
myhw-shop(电商桥接) 7 Epbusiness/Epmetal/Eprole/Epservice/Epuser/Epxipunumshop/Epxipunumstoreorder
mall-sync(商城通信) 3 已有
myhw-shop 追加(v9 新增) 2 AppReport + AppStat(妈祖 App 首页统计,pyerp_app 依赖)

❌ 弃用 12 个孤儿 controller

6 单据草稿模板:Bortmp · Bretmp · Buytmp · Selltmp · Sortmp · Sretmp

3 手机端遗留:Menuappinfo · Apiadmin · Prints

3 定时任务/店铺关联:StockApi · SyncApi · Usershop

2 电商菜单遗留:Epmenu · Epusershop

弃用理由:上述 12 个 controller 在妈祖 SPA / App / 商城 / 后端均零引用,是历史遗留死代码。

同名 52 controller 处理方针

11.3 用户体系迁移策略

11.4 妈祖生态"暂不动"清单

理由:降低单次改造风险 · 商城/App 通过 mall-sync 桥接 · 不影响妈祖客户

应用 处置
pyerp_shop_php / _h5 / _wx(商城客户端) 不动 · 通过 mall-sync 与 Nexora ERP 通信
pyerp_app(妈祖员工手机 App) 暂不动 · 保留独立打包 · 只需前端换 X-Tenant-Slug + 走 Nexora 主干 login

11.5 三阶段执行清单

Step 时长 产出 验证
Step 1
Diff 52 同名 controller
2-4h 妈祖独有改造清单 · 判断迁到 ymetal 或直接丢 生成 diff 报告 md 文档
Step 2
主干代码改造
半天 ① AppReport/AppStat 迁 myhw-shop 模块
② 12 孤儿 controller 从主干删除
③ 妈祖贵金属字段改造迁 ymetal 模块
php lint + syntax check
Step 3
妈祖租户数据迁移
半天 ① archive is_user
② 建 27 新账号 + mapping 表
③ 启用 ymetal + myhw-shop + mall-sync
④ 端到端 UI 验证
妈祖 admin 用新密码登进 Nexora dashboard · vision 判读看到「龙凤菜单 + 贵金属模块」

11.6 成功判据(必过)

  1. 妈祖客户 admin 在 https://nexora-erp.nexora.restry.cn/?tenant=pyerp 用新密码登进 dashboard
  2. 侧边栏出现:龙凤 7 大顶级菜单(账户/成品/销售/会员/资产/报表/首页)+ 「贵金属」模块 + 「电商对接」模块
  3. 老业务表(is_ymerp_* / is_ep_*)数据完整可查 · 老单据"创建人"字段能显示员工名或 "(离职) 张三" fallback
  4. 妈祖手机 App(pyerp_app)登录后能拿 AppReport/AppStat 数据(通过 myhw-shop 模块新暴露的接口)
  5. 妈祖商城(pyerp_shop_wx)客户下单能通过 mall-sync 推到 Nexora ERP 生成销售单