多租户 ERP · API 项目手册(ThinkPHP 8)
本文档是 API 项目(api.mpkerp.com) 的完整手册:框架学习、代码导览、开发规范、部署运维、数据库迁移。
| 你是什么角色 | 看哪些部分 |
|---|---|
| 刚接手项目的新人 | 第一部分(项目介绍)→ 第二部分(搭环境跑起来)→ 第三部分(框架速成) |
| 要给系统加功能的开发者 | 第四部分(代码导览)→ 第五部分(开发 SOP,照着模板抄) |
| 要上线 / 维护服务器的运维 | 第六部分(部署运维)→ 第七部分(数据库迁移)→ 附录 A(历史迁移记录) |
第一部分 · 项目介绍
一、这是什么
一套多租户的 ERP 系统 API,服务法医试剂 / 检测行业的企业(客户、招标、试剂盒、订单、库存、出入库)。
三个组成部分:
- API 平台
api.mpkerp.com—— ThinkPHP 8 写的后端,所有业务逻辑在这里。 - 前端
erp.mpkerp.com—— 纯 HTML + JS + PHP 页面,通过 API 拿数据,自身不碰数据库。 - 数据库 —— 一台 MySQL 服务器,包含「平台库」+ 每个租户独立一个「租户库」。
二、整体架构
┌───────────────────────────────────────┐
│ 三端用户(同一套业务) │
│ 网页端 ────── 手机端 ────── 桌面端 │
└───────────────────┬───────────────────┘
│ HTTPS + JSON(跨域已开)
▼
┌─────────────────────────────┐
│ API 平台 api.mpkerp.com │ ← ThinkPHP 8
│ (多租户路由中枢) │
└──────────────┬──────────────┘
│ 按 tenant_code 动态切库
┌──────────────┼──────────────┐
▼ ▼ ▼
租户A独立库 租户B独立库 租户C独立库
(erp_mpkerp) (erp_xxx) (erp_yyy)核心设计(本项目最值钱的地方):
- 多租户:多个企业共用一套系统,但每家独立一个数据库,互不干扰(数据天然隔离)。
- 租户注册表:平台库
api_mpkerp_com.platform_tenant表记录每个租户的tenant_code(如erp、cpa)和它自己的数据库连接信息(库名、账号、密码、主机、端口)。 - 三端通:网页 / 手机 / 桌面共用同一套 API,前端只存一个 token。
三、技术栈
| 层 | 技术 | 说明 | |
|---|---|---|---|
| --- | --- | --- | |
| 后端框架 | ThinkPHP 8(TP8) | 国内主流 PHP 框架,MVC | |
| 数据库 | MySQL 8 | 每租户一库,统一 utf8mb4 | |
| ORM | think-orm ^3 \ | ^4 | TP 自带,Db::name() / 模型两种用法 |
| 运行时 | PHP >= 8.0 | 建议 8.1 及以上 | |
| 依赖管理 | Composer | composer.json 声明依赖 | |
| Web 服务器 | Nginx / Apache | 网站根目录指向 public/ | |
| 前端 | 原生 HTML + JS(无框架) | assets/lib/mpk.lib.min.js 封装 API 调用 |
四、目录结构(先说最重要的)
api.mpkerp.com/
├── .env ★ 环境变量(数据库账号密码等),启动时自动读取,绝不提交 git
├── composer.json ★ 项目清单:依赖、命名空间(app\ 映射到 app/ 目录)
├── think ★ 命令行入口(php think xxx)
│
├── app/ ★★★ 你的业务代码全在这
│ ├── controller/ 控制器(接收请求、返回响应)—— 19 个
│ ├── model/ 模型(每个模型对应一张表)—— 3 个
│ ├── middleware/ 中间件(请求前后拦截)—— 3 个
│ ├── service/ 服务层(复杂业务逻辑,如租户切换)—— 9 个
│ ├── common.php ★ 全局函数(项目自定义辅助函数)
│ ├── BaseController.php 控制器基类(统一成功/失败返回)
│ ├── middleware.php 全局中间件注册表
│ └── ExceptionHandle.php 异常处理(出错时的兜底)
│
├── config/ ★★ 配置文件(database.php 数据库连接最关键)
├── route/ ★★ 路由定义(URL → 控制器方法)
├── public/ ★★ Web 根目录(唯一对外开放的目录)
│ ├── index.php 框架统一入口
│ ├── root.php 根路径分发器(开发 → 运维面板,生产 → 欢迎页)
│ ├── manual.php 本文档的网页版入口
│ ├── ops/ 开发运维面板(查看日志 / API 清单 / 接口试调)
│ └── robots.txt
├── runtime/ 运行时缓存 + 日志(apiLog 写的日志在这里)
├── vendor/ 框架本体 + 依赖库(composer 管理,永远别手动改)
└── extend/ 自写扩展类库(本项目没用到)五、三个库分别放什么
平台库 api_mpkerp_com ← 只有 1 张核心表 platform_tenant(租户注册表)
└── platform_tenant tenant_code / db_name / db_host / db_port / db_user / db_pass / web_domain ...
租户库 erp_mpkerp_com ← 真正放业务数据,共 26 张表
├── 系统表 sys_user / sys_role / sys_token / sys_dept / sys_pstn / sys_dict
├── 基础表 base_prvc / base_city / base_locus / base_exps / base_supp / base_pduc
└── 业务表 tender_main|item / kit_main|manu|locus / order_* / stock_* / stoin_* / stout_*数据库备份文件在仓库根目录 erp.mpkerp.db/:
api_mpkerp_com.sql—— 平台库erp_mpkerp_com.sql—— 租户库(模板租户erp)
第二部分 · 本地开发环境搭建(ThinkPHP 8 的设置与使用)
目标:在一台新电脑上,把 API 跑起来、能登录、能调接口。
一、环境要求
| 软件 | 版本 | 检查命令 |
|---|---|---|
| --- | --- | --- |
| PHP | >= 8.0 | php -v |
| Composer | 最新 | composer -V |
| MySQL | 8.x | mysql --version |
| 扩展 | pdo_mysql、mbstring | php -m |
PHP 需要开启的扩展:pdo_mysql、mbstring、openssl(TP8 必需)。
二、安装依赖
在项目根目录(api.mpkerp.com/)执行:
composer install- 会自动拉取 ThinkPHP 8 框架和所有依赖到
vendor/。 - 如果未来修改了
composer.json,重新执行composer update。
三、配置 .env(两套数据库连接)
项目自带 .example.env 模板,复制一份命名为 .env:
Copy-Item .example.env .env.env 里有两组连接配置,这是本项目最重要的配置:
# ① 默认连接 = 租户库连接(运行时会被 TenantService 按租户动态覆盖,见第四部分·二)
DB_HOST=127.0.0.1
DB_NAME=erp_mpkerp_com
DB_USER=erp_mpkerp_com
DB_PASS=你的密码
DB_PORT=3306
# ② 平台库连接 = 多租户路由中枢(只读 platform_tenant 注册表)
PLATFORM_DB_HOST=127.0.0.1
PLATFORM_DB_NAME=api_mpkerp_com
PLATFORM_DB_USER=api_mpkerp_com
PLATFORM_DB_PASS=你的密码
PLATFORM_DB_PORT=3306
# 调试开关
APP_DEBUG=true # 开发必须 true;生产必须 false
SQL_TRACE=false # true 时在页面/日志打印 SQL,调试用记住:.env 绝不提交 git(里面有数据库密码)。四、建库并导入数据
用 phpMyAdmin 或命令行:
# 平台库
mysql -u root -p < erp.mpkerp.db/api_mpkerp_com.sql
# 租户库
mysql -u root -p < erp.mpkerp.db/erp_mpkerp_com.sql然后按第二部分·三里的账号建 MySQL 用户并授权(生产账号最小权限规范见第六部分·三)。
五、启动开发服务器
TP8 自带内置服务器(免配 Nginx):
php think run
# 默认监听 http://127.0.0.1:8000浏览器访问:
http://127.0.0.1:8000/→ 开发环境会跳转到运维面板(public/ops/)http://127.0.0.1:8000/manual.php→ 本手册的网页版http://127.0.0.1:8000/auth/login(POST)→ API 接口
六、验证安装(登录接口通)
curl -X POST http://127.0.0.1:8000/auth/login `
-H "Content-Type: application/json" `
-d '{"tenant_code":"erp","username":"admin","password":"你的密码"}'返回 {code:200, msg:..., data:{token: "erp:xxx", user: {...}}} 即成功。
七、开发常用命令
php think run # 启动开发服务器
php think clear # 清缓存(改路由/配置不生效时先跑这个)
php think make:controller Xxx # 生成控制器骨架(本项目的控制器大多手写)
php think route:list # 打印全部路由八、调试三件套
APP_DEBUG=true:页面直接显示错误堆栈。SQL_TRACE=true:数据库操作会把 SQL 打印出来。runtime/log/:apiLog()写的日志按天分文件,如runtime/log/202608/23.log。
第三部分 · ThinkPHP 8 框架速成(新手必读)
这章讲 TP8 的通用概念,每节都指向本项目对应代码,边看文档边开代码最有效。
一、一次请求怎么走完的(生命周期)
浏览器/手机/桌面
│ POST https://api.mpkerp.com/auth/login
▼
public/index.php ← 唯一入口文件(Web 根目录只有 public 对外开放)
│
▼
think\App 启动 ← 读 .env、加载配置、注册路由
│
▼
路由匹配 ← route/app.php 里找 "auth/login" → 谁处理
│
▼
中间件(如果该路由挂了) ← 本项目:Cors(跨域)、Auth(登录鉴权)、DeleteGuard(生产删除防护)
│
▼
控制器方法 ← app/controller/Auth.php 的 login()
│ (操作数据库 → 组装数据)
▼
返回 JSON 响应 ← {code, msg, field, data}一句话理解:入口 → 路由 → 中间件 → 控制器 → 数据库 → 返回 JSON。
二、路由(URL 怎么找到代码)
路由文件:route/app.php
// 公开接口(不需要登录)
Route::post('auth/login', 'auth/login');
// 需要登录的接口:统一挂 Auth 中间件
Route::group('api', function () {
Route::get('user/list', 'user/list');
Route::post('tender/create', 'tender/create');
// ...
})->middleware(Auth::class);要点:
'auth/login'= 控制器Auth类的login方法(小写自动映射到app/controller/Auth.php)。- 路由里
Auth::class是中间件,不是控制器。分组下的接口全部要先过登录校验。 - 本项目 URL 统一是
/模块/动作风格,例如POST /tender/create。
三、控制器(接收请求、干活、返回)
namespace app\controller;
use app\BaseController;
use think\facade\Db;
use think\Response;
class Tender extends BaseController
{
// GET /tender/list
public function list(): Response
{
$data = Db::name('tender_main')->order('tender_id', 'asc')->select()->toArray();
return $this->success($data, '获取成功');
}
}要点:
- 每个控制器都继承
BaseController,从而拿到$this->success()/$this->error()统一返回方法,以及$this->request请求对象。 - 方法必须返回
Response($this->success()内部就是返回 json)。 - 控制器只做「收参数 → 校验 → 调数据 → 返回」,复杂逻辑尽量放 service 或模型。
四、数据库访问(两种方式)
方式 A:Db 门面(快捷,不用建模型) —— 本项目绝大多数接口用它
use think\facade\Db;
Db::name('tender_main')->select(); // 查所有
Db::name('tender_main')->where('tender_id', $id)->find(); // 查一条
Db::name('tender_main')->insert([...]); // 新增
Db::name('tender_main')->where('tender_id', $id)->update([...]);// 修改
Db::name('tender_main')->where('tender_id', $id)->delete(); // 删除Db::name('xxx')会自动找到config/database.php里默认连接(mysql)对应的库。- 注意本项目默认连接是「租户库」,运行时会被动态切换(见第四部分·二 多租户)。
方式 B:模型(ORM,更"面向对象") —— 本项目用于用户/角色/Token
use app\model\SysUser;
$user = SysUser::where('username', $username)->find(); // 查用户
$user->save(['last_login' => $now]); // 更新
SysToken::create([...]); // 新增模型文件长这样(app/model/SysUser.php):
class SysUser extends Model
{
protected $name = 'sys_user'; // 表名
protected $pk = 'user_id'; // 主键
protected $createTime = 'created_at'; // 自动写入创建时间
protected $updateTime = 'updated_at'; // 自动写入更新时间
}两种方式的区别(新手容易懵):
Db::name():直接操作,像写 SQL,灵活。Model:一个类对应一张表,可以自定义业务方法。- 选择标准:简单查表用
Db,涉及用户/权限/Token 这类核心实体建议用Model。
五、门面 Facade(think\facade\XXX 是什么)
一句话:静态方式调用内部服务。TP8 把很多功能做成了"门面",让你不用 new 就能用:
use think\facade\Db; // 数据库
use think\facade\Log; // 日志
use think\facade\Config; // 配置
use think\facade\Route; // 路由
use think\facade\Cache; // 缓存例如 Db::name()、Log::write(),本质是访问框架的单例服务,但写起来像静态方法,省事。
六、中间件(请求的"关卡")
中间件在请求到达控制器之前执行,也可以放行后再做点事。本项目有三个:
| 中间件 | 作用 | 挂载位置 |
|---|---|---|
| --- | --- | --- |
Cors | 跨域处理:允许所有域名访问,返回 Access-Control-* 头 | 全局(app/middleware.php) |
Auth | 登录鉴权:校验 Authorization: Bearer {token},按 token 前缀切租户库,通过后挂载 $request->authUser / authToken / authTenant | 需要登录的路由分组 |
DeleteGuard | 生产环境删除防护:APP_DEBUG=false 时,所有以 /delete 结尾的请求直接返回 403,防止误删 | 全局 |
DeleteGuard 判定规则(很重要):
APP_DEBUG = true(开发环境):不拦截,方便调试。APP_DEBUG = false(生产环境):拦截所有/xxx/delete请求 → 403「生产环境已禁用删除操作」。- 所有删除接口路由都以
delete结尾(当前 23 个),无其它接口以delete结尾,不会误伤。
七、配置文件速查(config/)
| 文件 | 内容 |
|---|---|
| --- | --- |
database.php | ★ 两个连接:mysql(默认=租户库,运行时被覆盖)+ platform(平台库) |
app.php | 应用级配置(debug、时区、异常处理等) |
cors.php | 跨域规则(域名白名单等) |
route.php | 路由全局配置 |
middleware.php | 全局中间件注册表(Cors、DeleteGuard 在这里) |
log.php | 日志配置 |
cache.php | 缓存配置 |
session.php / view.php / lang.php / filesystem.php / trace.php / console.php | 各自对应功能 |
八、app 根目录速查
| 文件 | 作用 |
|---|---|
| --- | --- |
BaseController.php | 控制器基类:统一 success() / error() 返回 |
common.php | 全局函数(启动时自动加载) |
AppService.php | 应用服务注册(本项目基本没动) |
ExceptionHandle.php | 异常兜底:把异常转成统一 JSON |
middleware.php | 全局中间件列表 |
九、全局函数与统一响应格式
全局函数在 app/common.php,从旧项目迁移而来,函数名与逻辑保持一致:
| 函数 | 作用 |
|---|---|
| --- | --- |
jsonReturn(code, msg, field, data) | 统一 JSON 返回,抛异常立即中断(等价旧项目的 exit) |
apiLog(msg, type) | 写日志到 runtime/log/ |
apiCheckRequired($input, ['字段' => '中文名']) | 必填校验,缺失直接 400 |
apiCheckInt(val, label, key, required) | 整数校验 |
apiCheckStr(val, label, key, required, max) | 字符串校验(去空格、限长) |
apiCheckDecimal(val, label, key) | 小数/数字校验 |
apiCheckDate(val, label, key) | 日期校验(返回 Y-m-d) |
统一响应格式(全站所有接口一致,前端按这个解析):
{
"code": 200, // HTTP 风格:200=成功,400=参数错误,401=未登录,403=无权限/禁用,404=不存在,405=方法错误,500=服务器错误
"msg": "获取成功", // 提示文字(前端可直接弹出)
"field": "", // 出错时标记是哪个字段(前端定位用)
"data": {} // 数据主体
}第四部分 · 本项目代码导览(API 全貌)
一、app 四个子目录
app/
├── controller/ ← 19 个:接口动作层,一层一个文件
│ 主从表合并为一个控制器(如 Kit 管 kit_main + kit_locus + kit_manu)
├── model/ ← 3 个:数据表映射(SysUser / SysToken / SysRole)
├── service/ ← 9 个:复杂业务逻辑
│ TenantService(租户中枢)/ BillService(出入库明细)/ OrderService /
│ TenderService / StockService / StoinService / StoutService /
│ PducService / KitService
└── middleware/ ← 3 个:请求拦截(Cors / Auth / DeleteGuard)二、多租户机制(本项目灵魂,必须理解)
租户识别两种入口:
| 场景 | 租户怎么来 |
|---|---|
| --- | --- |
| 登录 | 前端提交 tenant_code → TenantService::findByCode() 查平台库 → 切库校验用户 |
| 已登录请求 | Token 明文格式 {tenant_code}:{64位随机} → 中间件按冒号前部分切库 |
| 旧 Token 兼容 | 纯 64 位 hex 无冒号 → 视为 erp 租户(老用户不重登) |
切库原理(app/service/TenantService.php 核心三行):
// TenantService::switchDb() 核心逻辑:
$default = Db::getConfig('default', 'mysql'); // 1. 拿默认连接名
Config::set($database, 'database'); // 2. 整体改配置(不支持点号多级)
Db::connect($default, true); // 3. force=true 强制重建连接- 切库后,后续所有
Db::name()/ 模型查询自动落到该租户库。 - 平台库查询必须显式
Db::connect('platform')->name('platform_tenant')。
平台库 platform_tenant 关键字段:
| 字段 | 说明 |
|---|---|
| --- | --- |
tenant_code | 对外代号,唯一(uk_tenant_code) |
web_domain | 前端域名,唯一(uk_web_domain) |
db_name | 独立库名,唯一(uk_db_name) |
db_host / db_port / db_user / db_pass | 连该租户库的连接信息(可指向任意主机,这就是将来数据库迁移的突破口) |
tenant_status | 1 启用 / 0 停用(只查启用) |
expire_at | 到期日,NULL=永久 |
三、全部控制器与接口(按业务域)
3.1 认证与组织权限
Auth — 登录 / 登出 / 当前用户 / 改密
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| Login | POST /auth/login | 登录:tenant_code + username + password → {user, role, token, tenant_code} |
| Logout | POST /auth/logout | 退出:删 token |
| Info | GET /auth/info | 当前登录用户信息 |
| RePwd | POST /auth/repwd | 修改密码(校验旧密码) |
User — 用户管理(sys_user)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /user/list?keyword= | 用户列表(返回时隐藏 password 字段) |
| Create | POST /user/create | 新增用户(用户名唯一、密码加密) |
| Update | POST /user/update | 修改用户(可重置密码、改角色) |
| Delete | POST /user/delete | 删除用户 |
Role / Dept / Pstn — 角色 / 部门(树形)/ 岗位
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /role/list · /dept/list · /pstn/list | 列表 |
| Create | POST /role/create · /dept/create · /pstn/create | 新增 |
| Update | POST /role/update · /dept/update · /pstn/update | 修改 |
| Delete | POST /role/delete · /dept/delete · /pstn/delete | 删除(有下级/在用不可删) |
Perm — 操作权限点(sys_perm,岗位维度)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /perm/list | 权限点列表(含允许岗位 pstn_codes,按分组排序) |
| Sync | POST /perm/sync | 同步权限点清单(前端 PERM_DEFS upsert,仅元数据不覆盖已配置岗位;body: {list:[{perm_key,perm_name,perm_group}]}) |
| Update | POST /perm/update | 更新允许岗位(body: {perm_id, pstn_codes},逗号分隔,逐项校验 sys_pstn 存在性) |
说明:权限点 = 页面按钮/操作,编码「模块.动作」;「允许岗位」由组织架构-岗位权限页配置;
总管理员 ADMIN 前端 can() 全开;未配置的权限点默认放行(保守不误藏)。
Dict — 系统字典(sys_dict,全局枚举,供各业务状态/类型字段翻译)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /dict/list?type=&status= | 字典列表(下拉选项数据) |
| Create | POST /dict/create | 新增(类型 + 值联合唯一) |
| Update | POST /dict/update | 修改 |
| Delete | POST /dict/delete | 删除 |
3.2 基础档案
Prvc — 省份(base_prvc)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /prvc/list?keyword= | 省份列表 |
| User | POST /prvc/user | 分配省级负责人 |
| Create | POST /prvc/create | 新增省份(名称唯一,可选负责人/排序/状态) |
| Update | POST /prvc/update | 修改省份(名称唯一) |
| Delete | POST /prvc/delete | 删除;名下存在区域(base_city)不可删 |
City — 区域(base_city)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /city/list?prvc_id= | 区域列表(可按省份筛选) |
| User | POST /city/user | 分配区域负责人 |
| Create | POST /city/create | 新增区域(省份存在校验 + 同省内名称唯一) |
| Update | POST /city/update | 修改区域(同省内名称唯一) |
| Delete | POST /city/delete | 删除;已被客户/招标引用不可删 |
Exps — 物流公司(base_exps)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /exps/list?keyword= | 物流公司列表(出库单发货时用) |
| Create | POST /exps/create | 新增 |
| Update | POST /exps/update | 修改 |
| Delete | POST /exps/delete | 删除 |
Supp — 供应商(base_supp)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /supp/list?keyword= | 供应商列表(采购入库时用) |
| Create | POST /supp/create | 新增 |
| Update | POST /supp/update | 修改 |
| Delete | POST /supp/delete | 删除 |
Pduc — 产品档案(base_pduc)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /pduc/list?keyword=&gen_type=&use_type= | 产品列表(可筛选) |
| Create | POST /pduc/create | 新增产品(编码唯一、金额小数校验) |
| Update | POST /pduc/update | 修改 |
| Delete | POST /pduc/delete | 删除;已被订单/出入库引用不可删 |
Locus — 位点(base_locus 基因座基础信息总表)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /locus/list?keyword=&locus_type= | 位点列表(可筛选类型) |
| Create | POST /locus/create | 新增位点(locus_type/chr_type/locus_level 枚举白名单校验) |
| Update | POST /locus/update | 修改位点(locus_length 小数校验) |
| Delete | POST /locus/delete | 删除;已被试剂盒位点(kit_locus)引用不可删 |
Client — 客户档案(base_client)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /client/list?keyword= | 客户列表(名称/电话搜索) |
| Create | POST /client/create | 新增客户 |
| Update | POST /client/update | 修改客户 |
| Delete | POST /client/delete | 删除;已有订单不可删 |
3.3 业务单据
Tender — 招标项目(tender_main + tender_item 主从表)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /tender/list?keyword=&status= | 招标列表 |
| Detail | GET /tender/detail?tender_id= | 详情(主表 + 明细) |
| Create | POST /tender/create | 新增主表(市场部发布) |
| Update | POST /tender/update | 修改主表 |
| Delete | POST /tender/delete | 删除主表(级联删明细) |
| ItemList | GET /tender/item/list?tender_id= | 明细列表 |
| ItemCreate | POST /tender/item/create | 新增明细(销售分两次添加:招标时 / 中标后反馈) |
| ItemUpdate | POST /tender/item/update | 修改明细 |
| ItemDelete | POST /tender/item/delete | 删除明细 |
业务说明:招标主表由市场部发布,明细由销售在招标时添加一次、中标后再继续添加(前端有单独的"反馈"按钮),所以主表接口和明细接口是分开的。
Order — 订单(order_main + order_item 主从表)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /order/list?keyword=&status= | 订单列表 |
| Detail | GET /order/detail?order_id= | 详情(主表 + 明细) |
| Create | POST /order/create | 新增订单(生成单号,明细金额联动主表) |
| Update | POST /order/update | 修改主表(user_id 可改项目负责人,缺省保持原值) |
| Delete | POST /order/delete | 删除(被出库单引用不可删,级联删明细) |
| ItemList / ItemCreate / ItemUpdate / ItemDelete | /order/item/* | 明细 CRUD |
Stoin — 采购入库(stoin_main + stoin_item 主从表)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /stoin/list?keyword=&status= | 入库单列表 |
| Detail | GET /stoin/detail?stoin_id= | 详情(主表 + 明细) |
| Create | POST /stoin/create | 新增入库单(生成单号,联动更新库存台账) |
| Update | POST /stoin/update | 修改主表 |
| Delete | POST /stoin/delete | 删除(已过账/被引用不可删,级联删明细) |
| ItemList / ItemCreate / ItemUpdate / ItemDelete | /stoin/item/* | 明细 CRUD |
Stout — 销售出库(stout_main + stout_item 主从表)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /stout/list?keyword=&status= | 出库单列表 |
| Detail | GET /stout/detail?stout_id= | 详情(主表 + 明细,明细带订单单价 order_price) |
| Create | POST /stout/create | 新增出库单(生成单号,联动扣减库存) |
| Update | POST /stout/update | 修改主表 |
| Delete | POST /stout/delete | 删除(已过账不可删,级联删明细) |
| ItemList / ItemCreate / ItemUpdate / ItemDelete | /stout/item/* | 明细 CRUD |
Stock — 库存(stock_main 台账 + stock_log 流水)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /stock/list?keyword=&expire_status=&stock_status= | 台账列表(货品+批次唯一) |
| Create | POST /stock/create | 新增台账 |
| Update | POST /stock/update | 修改台账 |
| Delete | POST /stock/delete | 删除台账(不删流水) |
| ItemList | GET /stock/log/list?stock_id=&pduc_id=&stock_type=&ref_type= | 流水列表(只增不改不删) |
| ItemCreate | POST /stock/log/create | 手工盘点调整(stock_type=5,事务联动更新台账) |
3.4 试剂盒域
Kit — 试剂盒(kit_main + kit_locus + kit_manu)
| 方法 | 路由 | 功能 |
|---|---|---|
| --- | --- | --- |
| List | GET /kit/list?keyword=&sys_type=&status= | 试剂盒列表 |
| Detail | GET /kit/detail?kit_id= | 详情(主表 + 位点 + 厂家) |
| Create | POST /kit/create | 新增试剂盒(型号唯一) |
| Update | POST /kit/update | 修改主表 |
| Delete | POST /kit/delete | 删除(被订单引用不可删,级联删位点/厂家) |
| LocusList / LocusCreate / LocusUpdate / LocusDelete | /kit/locus/* | 试剂盒位点 CRUD |
| ManuList / ManuCreate / ManuUpdate / ManuDelete | /kit/manu/* | 竞品厂家 CRUD |
四、数据库 26 张表速览
| 分类 | 表 | 用途 |
|---|---|---|
| --- | --- | --- |
| 系统 | sys_user | 用户 |
| 系统 | sys_role | 角色(role_auth 存权限字符串) |
| 系统 | sys_token | 登录 token(只存 SHA256 哈希) |
| 系统 | sys_dept / sys_pstn | 部门 / 岗位 |
| 系统 | sys_dict | 数据字典(全局枚举,供各业务状态/类型字段翻译) |
| 基础 | base_prvc / base_city | 省份 / 区域 |
| 基础 | base_locus | 位点(基因座)标准库 |
| 基础 | base_exps | 物流公司 |
| 基础 | base_supp / base_pduc | 供应商档案 / 产品档案 |
| 业务 | tender_main / tender_item | 招标项目主表 / 明细 |
| 业务 | kit_main / kit_locus / kit_manu | 试剂盒 / 试剂盒基因座 / 竞品厂家 |
| 业务 | order_main / order_item / base_client | 订单主表 / 明细 / 客户档案 |
| 业务 | stock_main / stock_log | 库存台账 / 库存流水 |
| 业务 | stoin_main / stoin_item | 采购入库主表 / 明细 |
| 业务 | stout_main / stout_item | 销售出库主表 / 明细 |
26 张表全部已有对应 API(19 个控制器全覆盖),表结构可直接由对应接口维护。
五、接口 URL 速查(抄 URL 用)
| 控制器 | 接口 URL | 说明 |
|---|---|---|
| --- | --- | --- |
Auth | POST /auth/login · POST /auth/logout · GET /auth/info · POST /auth/repwd | 认证 |
User | /user/list · /user/create · /user/update · /user/delete | 用户管理 |
Role | /role/list · /role/create · /role/update · /role/delete | 角色 |
Dept | /dept/list · /dept/create · /dept/update · /dept/delete | 部门(树形) |
Pstn | /pstn/list · /pstn/create · /pstn/update · /pstn/delete | 岗位 |
Perm | /perm/list · /perm/sync · /perm/update | 操作权限点(岗位维度) |
Dict | /dict/list · /dict/create · /dict/update · /dict/delete | 系统字典 |
Prvc | /prvc/list · /prvc/user · /prvc/create · /prvc/update · /prvc/delete | 省份 + 负责人 |
City | /city/list · /city/user · /city/create · /city/update · /city/delete | 区域 + 负责人 |
Exps | /exps/list · /exps/create · /exps/update · /exps/delete | 物流公司 |
Supp | /supp/list · /supp/create · /supp/update · /supp/delete | 供应商 |
Pduc | /pduc/list · /pduc/create · /pduc/update · /pduc/delete | 产品档案 |
Locus | /locus/list · /locus/create · /locus/update · /locus/delete | 位点(基因座) |
Client | /client/list · /client/create · /client/update · /client/delete | 客户档案 |
Tender | /tender/list · /tender/detail · /tender/create · /tender/update · /tender/delete + /tender/item/list · /tender/item/create · /tender/item/update · /tender/item/delete | 招标主从表 |
Order | /order/list · /order/detail · /order/create · /order/update · /order/delete + /order/item/* | 订单主从表 |
Stoin | /stoin/list · /stoin/detail · /stoin/create · /stoin/update · /stoin/delete + /stoin/item/* | 采购入库主从表 |
Stout | /stout/list · /stout/detail · /stout/create · /stout/update · /stout/delete + /stout/item/* | 销售出库主从表 |
Stock | /stock/list · /stock/create · /stock/update · /stock/delete + /stock/log/list · /stock/log/create | 库存台账 + 流水 |
Kit | /kit/list · /kit/detail · /kit/create · /kit/update · /kit/delete + /kit/locus/* · /kit/manu/* | 试剂盒 + 位点 + 厂家 |
六、关键业务设计
- 主从表模式:Tender / Order / Stoin / Stout / Kit 都是"主表 + 明细"两个表,合并在一个控制器里,接口是
/模块和/模块/item/*两组。 - 单号自动生成:业务单据(订单/入库/出库)新增时自动生成单号。
- 删除保护:删除时先查引用(被下游单据引用不可删);生产环境还有 DeleteGuard 兜底,所有
/delete接口直接 403。 - 审计日志:增删改操作都写
apiLog,并记录created_by(当前登录用户)。 - 库存流水只增不改:库存变动(入库/出库/盘点)全部记入
stock_log流水,台账由流水联动,保证可追溯。 - 唯一约束:省份名、同省内区域名、产品编码、用户用户名、租户 tenant_code/web_domain/db_name 等都有唯一校验。
- 外部键校验:所有外键字段(client_id、supp_id、prvc_id 等)更新前先查目标存在。
七、前端怎么调 API
前端是纯 HTML + JS(无框架),API 客户端在 erp.mpkerp.com/assets/lib/mpk.lib.min.js:
const API_BASE = 'https://api.mpkerp.com'; // 独立 API 服务器地址
apiRequest('tender/list', { method: 'GET' }); // GET 自动带 token
apiRequest('auth/login', { method: 'POST', body: {...} }); // POST 传对象自动 JSON
apiToken.get() / apiToken.set(token) / apiToken.clear(); // token 存 localStorage
getCurrentUserCache(); // 获取当前用户(有缓存)要点:
- 登录成功后把
data.token存下来(apiToken.set()),之后所有请求自动带Authorization: Bearer {token}。 - 后端返回
code != 200时,前端自动弹出msg;401 自动跳登录页。 - 手机端 / 桌面端(无浏览器环境)不受跨域影响,逻辑一致。
第五部分 · 日常开发 SOP(以后怎么弄)
照着模板抄,改改表名和字段就行。所有新代码都要保持全站风格:统一返回、参数校验、审计日志。
一、加一个"列表 + 详情"接口(最简单)
场景:给已有表 foo_main 加列表接口。五步:
① 建控制器 app/controller/Foo.php:
namespace app\controller;
use app\BaseController;
use think\facade\Db;
use think\Response;
class Foo extends BaseController
{
// GET /foo/list?keyword=
public function list(): Response
{
$input = $this->request->get();
$query = Db::name('foo_main')->alias('f');
if (!empty($input['keyword'])) {
$query->whereLike('f.foo_name', '%' . trim($input['keyword']) . '%');
}
$list = $query->order('f.foo_id', 'desc')->select()->toArray();
return $this->success($list, '获取成功');
}
}② 加路由 route/app.php(登录分组里):
Route::group('api', function () {
// ... 现有路由
Route::get('foo/list', 'foo/list');
})->middleware(Auth::class);③ 验证:带 token 调 GET /foo/list,看返回。
④ 写日志:只读接口可不写;增删改必须 apiLog()。
⑤ 加详情(如果有):GET /foo/detail?foo_id=,主从表就再拼明细。
二、加一组 CRUD(增删改查)
新建 = 校验(apiCheckRequired)→ 查重(唯一字段)→ insert → 返回新 ID。
修改 = 先 find 确认存在 → 查重(排除自己)→ update → 返回。
删除 = 先查引用(有下游引用就拒绝)→ delete → 返回。
参考现成模板:app/controller/Exps.php(最简单的纯 CRUD)、app/controller/Locus.php(带枚举白名单校验)。
三、加一张新表(完整流程)
- SQL 建表:统一
utf8mb4,带COMMENT注释,主键xxx_id自增,加created_by / created_at / updated_at。 - 导入测试库:
mysql -u root -p < 新表.sql。 - 写控制器:按第五部分·一 / 第五部分·二模板。
- 加路由:
route/app.php。 - 需要下拉选项就加字典:往
sys_dict插入对应类型(dict/list?type=xxx前端直接拿)。 - 同步备份:把新表 SQL 合并进
erp.mpkerp.db/erp_mpkerp_com.sql(保持备份库是模板库)。 - 更新本手册:第四部分·四 表速览、第四部分·三 控制器导览、第四部分·五 URL 速查。
四、接入新租户(Checklist,可复用)
- [ ] 1. 复制模板库:
CREATE DATABASE erp_新租户code_com ...→ 导入erp_mpkerp_com.sql - [ ] 2. 建独立账号并授权(只授权该库)
- [ ] 3. 向
platform_tenant插入一行(tenant_code / db_* / web_domain 均唯一) - [ ] 4. 清空
sys_token(旧 token 在新库不存在,直接失效) - [ ] 5. 重置管理员密码(或保留 md5 值迁移)
- [ ] 6. API 代码无需改动,
TenantService按 tenant_code 自动切库 - [ ] 7. 前端
assets/lib/mpk.lib.min.js的API_BASE指向https://api.mpkerp.com - [ ] 8. 登录传
tenant_code→ 验证列表数据落在新库
五、常见坑(重点背下来)
5.1 数据库
- 编码统一
utf8mb4,排序规则统一,避免乱码与 JOIN 报错。 tenant_code / web_domain / db_name三个唯一索引别丢(平台库路由依赖)。.env绝不提交 git(含数据库密码);用.example.env当模板。- 每租户独立账号、最小权限;平台库账号生产建议只读。
5.2 API / 框架
- 改路由/控制器不生效 → 清缓存:
php think clear(TP 会缓存路由)。 Db::name()查到错库 → 一定是租户没切对:检查 token 前缀、switchDb是否执行。Config::set()第二参数不支持点号多级(如database.connections.mysql.database),
必须整体读数组、修改后整体写回(TenantService::switchDb 已示范)。
- 跨域由
Cors中间件全局处理,一般不用动。
5.3 安全
- 登录失败统一提示"用户名或密码错误",防用户名枚举。
- Token 库中只存 SHA256,明文只在客户端和内存。
- 每个租户独立库 → A 租户 token 在 B 库查不到,天然隔离。
- 密码目前是 md5(历史兼容),新系统建议逐步换
password_hash()并平滑过渡。
第六部分 · 部署与运维(当前:一台服务器)
一、当前部署拓扑
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 前端 erp.mpkerp.com API api.mpkerp.com │
│ (HTML/JS/PHP 静态页) ←→ (ThinkPHP 8 + Nginx/Apache) │
│ │ │ │
│ └──────────── HTTPS ────────────┘ │
│ ▼ │
│ MySQL(同一台机器) │
│ ├── 平台库 api_mpkerp_com │
│ └── 租户库 erp_mpkerp_com(及其他租户库) │
└─────────────────────────────────────────────────────────────────┘要点:
- API、前端、MySQL 都在一台服务器。
- 前端不碰数据库,只通过 HTTPS 调 API(跨域已开,域名为
*.mpkerp.com)。 - API 通过两套连接访问数据库:平台库(
.env的PLATFORM_DB_*)+ 租户库(.env兜底 +platform_tenant表逐租户覆盖)。
二、上线步骤(新服务器 / 重装)
- 准备环境:PHP 8.0+(开 pdo_mysql/mbstring/openssl)、MySQL 8、Nginx/Apache、Composer。
- 放代码:
api.mpkerp.com/上传到服务器,composer install --no-dev(生产不要 dev 依赖)。 - 配置 .env:
APP_DEBUG=false,填真实数据库账号(见第二部分·三)。 - 导入数据库:
erp.mpkerp.db/两个 sql 导入。 - 配置 Web 服务器:网站根目录指向
public/,配置伪静态(TP8 标准 rewrite 规则)。 - 验证:
- POST /auth/login 返回 token;
- 带 token 访问 /tender/list 正常;
- 跨域正常(前端能调通)。
- 清缓存:
php think clear(首次部署必做)。
三、生产安全清单
- [ ]
APP_DEBUG=false(不然错误堆栈会暴露数据库结构) - [ ]
.env不在 git 仓库里、文件权限收紧(如 600) - [ ] 数据库账号最小权限:
- 租户库:每租户独立账号,只授权自己的库;
- 平台库:独立账号,生产建议只读 platform_tenant 的 SELECT;
- 绝不给 API 账号 SUPER / GRANT 权限。
- [ ] DeleteGuard 在生产自动拦截所有
/delete接口(无需额外配置,靠APP_DEBUG=false触发) - [ ] 定期备份(见第六部分·四)
- [ ] 登录失败统一提示,防用户名枚举
四、日常备份与日志
备份(建议每日):
# 平台库
mysqldump -u api_mpkerp_com -p api_mpkerp_com --default-character-set=utf8mb4 > api_mpkerp_com_备份.sql
# 租户库
mysqldump -u erp_mpkerp_com -p erp_mpkerp_com --default-character-set=utf8mb4 > erp_mpkerp_com_备份.sql查日志:
- 应用日志:
runtime/log/,按天分文件(apiLog()写的都在这里)。 - Web 服务器错误:Nginx/Apache 的 error log。
- 数据库慢查询:MySQL slow query log。
第七部分 · 数据库迁移方案(将来)
当前 API、前端、MySQL 都在一台服务器。将来系统变大,最常见的需求就是把 MySQL 搬到独立数据库服务器 / 云 RDS。
好消息:本项目的多租户设计天生支持数据库分布在任意主机——每个租户的连接信息都存在platform_tenant表里(含db_host / db_port),平台库连接在.env里。迁移只需要改这两处"指向",代码一行不用动。
一、什么时候需要迁移
| 场景 | 判断信号 |
|---|---|
| --- | --- |
| 数据库服务器太慢 / CPU 打满 | 业务高峰期 API 响应明显变慢 |
| 磁盘快满 | 数据库文件占用越来越大 |
| 需要高可用 / 自动备份 | 想上云 RDS、主从、快照备份 |
| 安全合规 | 数据库要求与 Web 分离、内网访问 |
二、核心原理:两套连接怎么指
API 进程启动时读 .env:
┌─ PLATFORM_DB_* → 平台库(api_mpkerp_com,存 platform_tenant 注册表)
└─ DB_* → 默认兜底租户库(实际每个租户的连接信息在 platform_tenant 表里)
登录/请求时 TenantService:
1. 平台库(按 .env 的 PLATFORM_DB_* 连接)查 platform_tenant
2. 取出该租户的 db_host/db_port/db_user/db_pass/db_name
3. switchDb() 把默认连接改指向租户库 → 后续查询落到租户库所以"数据库搬到哪里"= 改两个地方:.env(平台库)+ platform_tenant 表(每个租户库)。迁移后 API 代码零改动。
三、场景 A:同一台机器换 MySQL 实例 / 端口 / 账号
最轻量的场景(不搬机器,只换库实例或账号):
- 在新 MySQL 实例上建库并导入(步骤见第七部分·四的备份/导入)。
- 改
.env:DB_*和PLATFORM_DB_*指向新实例 / 新账号。 - 若
platform_tenant表里各租户的db_host / db_port / db_user / db_pass与原来不同,也要 UPDATE 该表。 php think clear→ 验证登录与列表。
四、场景 B:数据库搬到独立服务器 / 云 RDS(重点)
4.1 前置准备
- 新数据库服务器安装 MySQL 8,开放 API 服务器 IP 的白名单(云 RDS 在安全组/白名单里加 API 服务器公网 IP)。
- 确认 API 服务器到新库的网络连通(
telnet 新库IP 3306)。
4.2 迁移步骤
① 全量备份(在旧服务器)
mysqldump -u root -p --all-databases --default-character-set=utf8mb4 --single-transaction > 全量备份.sql
# 或按库分别导:
mysqldump -u api_mpkerp_com -p api_mpkerp_com --default-character-set=utf8mb4 > api_mpkerp_com.sql
mysqldump -u erp_mpkerp_com -p erp_mpkerp_com --default-character-set=utf8mb4 > erp_mpkerp_com.sql② 在目标服务器建库导入
mysql -u root -p < api_mpkerp_com.sql
mysql -u root -p < erp_mpkerp_com.sql
# 多租户就重复导入每个租户库③ 建账号并授权(在目标服务器,账号规范见第六部分·三)
-- 平台库账号
CREATE USER 'api_mpkerp_com'@'API服务器IP' IDENTIFIED BY '密码';
GRANT SELECT ON api_mpkerp_com.* TO 'api_mpkerp_com'@'API服务器IP';
-- 每个租户库账号
CREATE USER 'erp_mpkerp_com'@'API服务器IP' IDENTIFIED BY '密码';
GRANT ALL ON erp_mpkerp_com.* TO 'erp_mpkerp_com'@'API服务器IP';④ 改 API 服务器的 .env(平台库指向新服务器)
PLATFORM_DB_HOST=新数据库服务器IP
PLATFORM_DB_PORT=3306
PLATFORM_DB_USER=api_mpkerp_com
PLATFORM_DB_PASS=新密码⑤ 改 platform_tenant 表(每个租户指向新服务器)
UPDATE platform_tenant
SET db_host = '新数据库服务器IP',
db_port = 3306,
db_user = 'erp_mpkerp_com',
db_pass = '新密码'
WHERE tenant_code = 'erp'; -- 每个租户一行,都改注意:platform_tenant 表在新库里(已随 ② 导入),直接在目标服务器上执行 UPDATE 即可。⑥ 切换与验证
php think clear清缓存。- 停旧库写入(或直接切,见第七部分·七 回滚)。
- 验证清单见第七部分·六。
4.3 迁移后架构
API 服务器 数据库服务器(独立 / 云 RDS)
┌────────────────┐ ┌──────────────────────────────┐
│ api.mpkerp.com │ ←────→ │ MySQL 8 │
│ erp.mpkerp.com │ 3306 │ ├── api_mpkerp_com(平台库)│
└────────────────┘ │ └── erp_mpkerp_com(租户库)│
└──────────────────────────────┘五、场景 C:前端 / API 分离(可选)
如果将来前端流量大,想单独部署:
- 把
erp.mpkerp.com目录复制到新前端服务器(纯静态 + PHP 轻页面)。 - 改
assets/lib/mpk.lib.min.js里的API_BASE指向 API 域名。 - 改 API 的
Cors配置允许新前端域名(见config/cors.php)。 - 前端不碰数据库,无其他改动。
六、迁移后验证清单
- [ ]
POST /auth/login返回{user, role, token, tenant_code} - [ ] 带
Authorization: Bearer {token}访问受保护接口正常 - [ ] 多个租户分别登录,确认数据落在各自的库(跨库隔离仍生效)
- [ ] 列表/详情/新增/修改都试一遍(覆盖主从表:如
tender/detail) - [ ] 网页端跨域正常、手机端无 Origin 也正常
- [ ]
runtime/log/正常写入日志 - [ ] 平台库账号只读权限下,登录路由仍正常(只读 SELECT)
七、回滚方案
迁移失败随时可回滚(因为代码没动,只改了两处"指向"):
.env的PLATFORM_DB_*改回旧库。platform_tenant的db_host / db_*改回旧值(或直接在旧库上改,因为旧库数据还在)。php think clear→ 验证。
生产建议:迁移前先做全量备份(第七部分·四 ①),迁移后保留旧库至少一个备份周期再清理。
附录
A. 旧项目迁移历史(原散装 PHP → ThinkPHP 8 多租户)
这份是当初项目从旧代码迁移到新框架的历史记录,与第七部分"将来数据库迁移"不同。
留存备用:再次迁移其他旧项目 / 老接口时对照。
A.1 迁移背景
旧项目(erp.mpkerp.com 时代):
- 接口都是独立 PHP 文件:
api/get-all-tender-main.php、create-tender-main.php等。 - 公共函数在一个文件:
api/api-common.php(jsonReturn、校验函数等)。 - 原生 PDO + SQL 字符串拼接,直连单库,无框架、无路由、无统一鉴权。
新项目(api.mpkerp.com,ThinkPHP 8):
- 统一入口
public/index.php,路由集中在route/app.php。 - 控制器继承
BaseController,中间件统一鉴权(Bearer Token)。 - 多租户:平台库(注册表)+ 每租户一个独立业务库。
- 统一返回
{code, msg, field, data}。
迁移三原则:
- URL 风格统一:
/模块/动作。 - 返回格式兼容:
{code, msg, field, data},旧前端尽量不改。 - Token 兼容:旧 token(纯 64 位 hex 无前缀)自动视为
erp租户,老用户不重登。
A.2 旧接口 → 新接口对照(模板)
| 旧项目文件 | 新路由 | 控制器方法 | 说明 |
|---|---|---|---|
| --- | --- | --- | --- |
api/get-all-tender-main.php | GET /tender/list | Tender::list | 列表 |
api/create-tender-main.php | POST /tender/create | Tender::create | 新增 |
api/update-tender-main.php | POST /tender/update | Tender::update | 修改 |
api/delete-tender-main.php | POST /tender/delete | Tender::delete | 删除 |
api/api-common.php | app/common.php | jsonReturn / apiLog / apiCheck* | 公共函数 |
| 各文件里的登录校验 | app/middleware/Auth.php | — | 统一鉴权 |
A.3 迁移五步套路(把旧接口搬到新框架)
- 建控制器:
app/controller/Xxx.php,命名空间app\controller,继承BaseController。 - 加路由:
route/app.php;需要登录的放 Auth 中间件分组。 - 搬逻辑:原生 PDO + SQL 拼接 →
Db::name('表')查询构造器;exit/json→ 返回值。 - 加校验:
apiCheckRequired / apiCheckInt / apiCheckDecimal / apiCheckDate。 - 统一返回:
return $this->success($data, '获取成功');;出错return $this->error('提示', 400, 'field');。
A.4 关键兼容点(迁移时最容易踩)
- 返回结构:
{code, msg, field, data};jsonReturn()通过抛HttpResponseException立即中断。 - Token:新格式明文
{tenant_code}:{64位hex};库中只存 SHA256。旧格式(纯 64 位 hex 无冒号)→ 视为erp租户。 - 密码:
sys_user.password存 md5(旧数据直接兼容,未做二次迁移)。 - 登录:
tenant_code缺省时自动用LEGACY_TENANT_CODE(erp),旧前端不用传。 - 鉴权:旧项目每个文件自己查 token → 新项目中间件统一做,通过后挂载
$request->authUser / authToken / authTenant。
B. 学习资料
- ThinkPHP 8 官方文档:https://doc.thinkphp.cn/v8_0/
- 本项目必读文件(按顺序):
1. route/app.php —— 看接口全貌
2. app/controller/Auth.php —— 看登录
3. app/middleware/Auth.php —— 看鉴权
4. app/service/TenantService.php —— 看多租户核心
5. config/database.php —— 看两个连接
C. 版本与维护记录
| 日期 | 内容 |
|---|---|
| --- | --- |
| 2026-08 | 手册按"项目介绍 → 搭建 → 框架速成 → 代码导览 → 开发 SOP → 部署运维 → 数据库迁移 → 附录"重排 |