chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace ywxapp\model;
|
||||
|
||||
use think\facade\Config;
|
||||
use think\facade\Db;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* BaseModel 类
|
||||
*
|
||||
* 内聚了 SchemaGuard 数据库结构自愈引擎(纯通用能力,不含任何业务表清单)。
|
||||
* 各模型在自己的 ensureSchema() 里调用本类静态方法确保「自己这张表」,
|
||||
* 建表责任与触发点绑定到模型本身,核心库不再按业务域聚合自愈。
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class BaseModel extends Model
|
||||
{
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
protected function getBaseOptions(): array
|
||||
{
|
||||
return [
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'dateFormat' => 'Y-m-d H:i:s',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将扁平的父子结构数组转换为带层级缩进的树形下拉数据
|
||||
* @param array $cate 数据集(含 id/pid 字段)
|
||||
* @param string $name 用于展示的字段名
|
||||
* @param string $lefthtml 层级缩进符号
|
||||
* @param int $pid 父级 ID
|
||||
* @param int $level 当前层级
|
||||
* @return array
|
||||
*/
|
||||
public static function cateTree($cate, $name = 'title', $lefthtml = '|— ', $pid = 0, $level = 0)
|
||||
{
|
||||
$arr = [];
|
||||
foreach ($cate as $v) {
|
||||
if (($v['pid'] ?? 0) == $pid) {
|
||||
$v['level'] = $level;
|
||||
$v[$name] = str_repeat($lefthtml, $level) . ($v[$name] ?? '');
|
||||
$arr[] = $v;
|
||||
$arr = array_merge($arr, self::cateTree($cate, $name, $lefthtml, $v['id'], $level + 1));
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
parent::__construct($data);
|
||||
$this->applyOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将子类 getOptions() 返回的模型配置落实到 think\Model 属性,
|
||||
* 使 name/strict/schema/autoWriteTimestamp/readonly 等真正生效(之前是死代码)。
|
||||
*/
|
||||
protected function applyOptions(): void
|
||||
{
|
||||
if (!method_exists($this, 'getOptions')) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->getOptions() as $key => $value) {
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
// think-orm 4.0 语义:$name 是不带前缀的表名,
|
||||
// 框架会根据 database.prefix 自动拼接完整表名。
|
||||
// 之前误写成 $this->table(含前缀语义),导致不拼前缀、
|
||||
// 模型查询裸名表而自愈建的是带前缀表,引发 1146。
|
||||
$this->name = $value;
|
||||
$this->table = null;
|
||||
break;
|
||||
case 'strict':
|
||||
$this->strict = (bool)$value;
|
||||
break;
|
||||
case 'schema':
|
||||
$this->schema = $value;
|
||||
break;
|
||||
case 'autoWriteTimestamp':
|
||||
$this->autoWriteTimestamp = $value;
|
||||
break;
|
||||
case 'createTime':
|
||||
$this->createTime = $value;
|
||||
break;
|
||||
case 'updateTime':
|
||||
$this->updateTime = $value;
|
||||
break;
|
||||
case 'readonly':
|
||||
$this->readonly = $value;
|
||||
break;
|
||||
case 'hidden':
|
||||
$this->hidden = $value;
|
||||
break;
|
||||
case 'append':
|
||||
$this->append = $value;
|
||||
break;
|
||||
case 'dateFormat':
|
||||
$this->dateFormat = $value;
|
||||
break;
|
||||
case 'deleteTime':
|
||||
$this->deleteTime = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected $tenantField = 'tenant_id';
|
||||
|
||||
// 自动添加 tenant_id 到查询和保存
|
||||
|
||||
public static function onAfterRead($model)
|
||||
{
|
||||
$user = request()->auth ?? null;
|
||||
if ($user && $model->hasField('tenant_id')) {
|
||||
if ($model->tenant_id != $user['tenant_id']) {
|
||||
abort(403, '无权访问此数据');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function onBeforeWrite($model)
|
||||
{
|
||||
$user = request()->auth ?? null;
|
||||
if ($user && $model->hasField('tenant_id') && ! $model->tenant_id) {
|
||||
$model->tenant_id = $user['tenant_id'];
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* 数据库结构自愈引擎(原 SchemaGuard,已内聚到 BaseModel)
|
||||
* 唯一事实源是 public/install/install.sql;模型通过 ensureTableFromInstall()
|
||||
* 从中提取 DDL 建表,避免 DDL 漂移。
|
||||
* ------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 表是否存在(已带前缀的完整表名)
|
||||
*/
|
||||
public static function tableExists(string $table): bool
|
||||
{
|
||||
try {
|
||||
return !empty(Db::query("SHOW TABLES LIKE '{$table}'"));
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表不存在则创建(已带前缀的完整表名 + 完整 CREATE SQL)
|
||||
*/
|
||||
public static function ensureTable(string $table, string $sql): void
|
||||
{
|
||||
try {
|
||||
if (!self::tableExists($table)) {
|
||||
Db::execute($sql);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略(如权限不足),由后续业务报错暴露
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 id 列为自增主键(老库修复:id 定义成 NOT NULL 但无 PRIMARY KEY/AUTO_INCREMENT
|
||||
* 时,模型 create() 不带 id 会报 1364 Field 'id' doesn't have a default value)。
|
||||
* @param string $table 已带前缀的完整表名
|
||||
* @param string $column 主键列名,默认 id
|
||||
*/
|
||||
public static function ensureAutoIncrementPk(string $table, string $column = 'id'): void
|
||||
{
|
||||
try {
|
||||
$cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
|
||||
if (empty($cols)) {
|
||||
return;
|
||||
}
|
||||
$col = $cols[0];
|
||||
$extra = strtolower((string)($col['Extra'] ?? ''));
|
||||
$key = strtoupper((string)($col['Key'] ?? ''));
|
||||
if (strpos($extra, 'auto_increment') !== false) {
|
||||
return; // 已是自增
|
||||
}
|
||||
$type = (string)($col['Type'] ?? 'int unsigned');
|
||||
if ($key !== 'PRI') {
|
||||
// 无主键:一并加主键 + 自增
|
||||
Db::execute("ALTER TABLE `{$table}` MODIFY `{$column}` {$type} NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY (`{$column}`)");
|
||||
} else {
|
||||
Db::execute("ALTER TABLE `{$table}` MODIFY `{$column}` {$type} NOT NULL AUTO_INCREMENT");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略(如权限不足),由后续业务报错暴露
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列不存在则追加(MySQL 不支持 ADD COLUMN IF NOT EXISTS,故先探测)
|
||||
* @param string $table 已带前缀的完整表名
|
||||
*/
|
||||
public static function ensureColumn(string $table, string $column, string $def): void
|
||||
{
|
||||
try {
|
||||
$cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
|
||||
if (empty($cols)) {
|
||||
Db::execute("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$def}");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 自愈失败不应静默吞掉,记录日志便于排查(如 ALTER 权限不足)
|
||||
try {
|
||||
\think\facade\Log::error("[BaseModel] ensureColumn failed: {$table}.{$column} - " . $e->getMessage());
|
||||
} catch (\Throwable $e2) {
|
||||
// 日志也失败则彻底忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得运行时表前缀(CLI / 多应用路由下 Db::getConfig 可能为空,需兜底到配置)。
|
||||
* 模型壳调用 ensureTableFromInstall() 时统一使用本方法取前缀,避免修错无前缀表。
|
||||
*/
|
||||
public static function currentPrefix(): string
|
||||
{
|
||||
$prefix = Config::get('database.connections.mysql.prefix', '');
|
||||
if ($prefix === '') {
|
||||
$prefix = Db::getConfig('prefix') ?: '';
|
||||
}
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 install.sql 提取指定表的 CREATE TABLE 语句并执行建表(单一事实源)。
|
||||
* @param string $prefix 运行时表前缀
|
||||
* @param string $table 不含前缀的表名(如 backend / member_profile)
|
||||
*/
|
||||
public static function ensureTableFromInstall(string $prefix, string $table): void
|
||||
{
|
||||
$p = $prefix;
|
||||
$sqlFile = root_path() . 'public/install/install.sql';
|
||||
if (!is_file($sqlFile)) {
|
||||
return;
|
||||
}
|
||||
$content = file_get_contents($sqlFile);
|
||||
$pattern = '/CREATE TABLE IF NOT EXISTS `__PREFIX__' . preg_quote($table, '/') . '`\s*\(.*?\)\s*ENGINE=[^;]*;/s';
|
||||
if (!preg_match($pattern, $content, $m)) {
|
||||
return;
|
||||
}
|
||||
$ddl = str_replace('__PREFIX__', $p, $m[0]);
|
||||
self::ensureTable($p . $table, $ddl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user