Files
ulthon_admin/extend/base/admin/service/InitDataAlignServiceBase.php
augushong 553047025c refactor(install): 种子退役,migrate:run 一条命令完成建表与初始数据
新增 InitDataAlignService 六域对齐(超管/角色/权限节点/配置/快捷入口/商城演示)与 20260909130000 迁移;SystemAdmin 快照以字面量哈希入库;删除种子三件与安装锁依赖,seeds 目录留 .gitkeep;README/tests/规则等 11 处说明同步。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-09 14:38:10 +00:00

263 lines
9.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @internal-framework
*
* 此文件为框架核心服务
*
* 用途:初始化数据对齐服务(六大数据域按快照对齐,migrate:run 唯一安装路径)
* 维护者:框架维护者
*
* 背景:种子机制退役后,adminInitData/*.php 快照是初始数据的唯一数据源,
* 由本服务随数据型 migration 安装(migrate:run 是文档化安装命令,phinxlog
* 保证幂等)。取代原 AdminInitServiceBase 的种子初始化。
*
* 对齐语义(保守,尊重用户自定义):
* - 超管 :id=1 不存在才插入;密码永不覆盖(软删行存在同样视为已存在,不复活)
* - 角色 :按显式 id 逐行只插(含游客权限 id=6)
* - 权限节点:快照为空数组,直接跳过(该表由 auth 注解动态扫描生成)
* - 配置 :按 (group, name) check-then-insert(表无唯一键,纯应用层判重),
* 只插缺失不覆盖 value;发生过插入则清一次 sysconfig 缓存
* - 快捷入口:按 href 只插缺失;软删行存在视为已存在,不复活
* - 商城演示:分表守卫(cate 空则插分类;goods 空则插商品并注入分类 id;
* tag 空则插标签),三守卫相互独立
*
* 硬性实现约束:auth/quick/admin 三表均有 delete_time,存在性检查必须用
* Db::name 原生查询绕过软删除作用域——否则软删行被判「缺失」→ INSERT →
* 主键冲突崩溃或复活用户删除的行。沿用 MenuAlignServiceBase 已验证模式。
*/
namespace base\admin\service;
use think\console\Output;
use think\facade\Db;
class InitDataAlignServiceBase
{
/**
* @var Output|null 命令行输出(migration 场景传入,便于追踪对齐过程)
*/
protected $output = null;
public function __construct($output = null)
{
$this->output = $output;
}
/**
* 按快照对齐全部初始数据域(幂等,可重复执行).
*
* @return void
*/
public function align(): void
{
$this->writeln('开始对齐初始化数据(超管/角色/权限节点/配置/快捷入口/商城演示)');
$this->alignAdmin();
$this->alignAuth();
$this->alignAuthNode();
$this->alignConfig();
$this->alignQuick();
$this->alignMall();
$this->writeln('初始化数据对齐完成');
}
/**
* 超管对齐:id=1 不存在才插入,密码永不覆盖.
*/
protected function alignAdmin(): void
{
$snapshot = include __DIR__ . '/adminInitData/SystemAdmin.php';
$now = time();
foreach ($snapshot as $row) {
// 原生查询不过滤软删除:软删的超管行仍占用主键,视为已存在,不复活不覆盖
$exists = Db::name('system_admin')->where('id', $row['id'])->find();
if (!empty($exists)) {
$this->writeln(' - 超管 id=' . $row['id'] . ' 已存在,跳过(密码永不覆盖)');
continue;
}
$data = $row;
$data['create_time'] = $now;
$data['update_time'] = $now;
$data['delete_time'] = 0;
Db::name('system_admin')->insert($data);
$this->writeln(' - 新增超管 id=' . $row['id'] . ' ' . $row['username']);
}
}
/**
* 角色对齐:按显式 id 逐行只插(快照含管理员 id=1 与游客权限 id=6).
*/
protected function alignAuth(): void
{
$snapshot = include __DIR__ . '/adminInitData/SystemAuth.php';
$now = time();
foreach ($snapshot as $row) {
// 原生查询不过滤软删除:软删的角色行仍占用主键,视为已存在,不复活
$exists = Db::name('system_auth')->where('id', $row['id'])->find();
if (!empty($exists)) {
$this->writeln(' - 角色 id=' . $row['id'] . ' ' . $row['title'] . ' 已存在,跳过');
continue;
}
$data = $row;
$data['create_time'] = $now;
$data['update_time'] = $now;
$data['delete_time'] = 0;
Db::name('system_auth')->insert($data);
$this->writeln(' - 新增角色 id=' . $row['id'] . ' ' . $row['title']);
}
}
/**
* 权限节点对齐:快照为空数组,直接跳过(该表由 auth 注解动态扫描生成).
*/
protected function alignAuthNode(): void
{
$this->writeln(' - 权限节点由注解动态扫描生成,跳过快照对齐');
}
/**
* 配置对齐:按 (group, name) check-then-insert,只插缺失不覆盖 value;
* 表无唯一键,判重纯靠应用层;发生过插入则清一次 sysconfig 缓存.
*/
protected function alignConfig(): void
{
$snapshot = include __DIR__ . '/adminInitData/SystemConfig.php';
$now = time();
$inserted = 0;
foreach ($snapshot as $row) {
$exists = Db::name('system_config')
->where('group', $row['group'])
->where('name', $row['name'])
->find();
if (!empty($exists)) {
continue;
}
$data = $row;
$data['create_time'] = $now;
$data['update_time'] = $now;
Db::name('system_config')->insert($data);
++$inserted;
}
$this->writeln(' - 系统配置新增 ' . $inserted . ' 条(缺失补齐,不覆盖已有 value)');
if ($inserted > 0) {
TriggerServiceBase::updateSysconfig();
$this->writeln(' - 已刷新系统设置缓存');
}
}
/**
* 快捷入口对齐:按 href 只插缺失;软删行存在视为已存在,不复活.
*/
protected function alignQuick(): void
{
$snapshot = include __DIR__ . '/adminInitData/SystemQuick.php';
$now = time();
$inserted = 0;
foreach ($snapshot as $row) {
// 原生查询不过滤软删除:软删的快捷入口仍占用 href 语义,视为已存在,不复活
$exists = Db::name('system_quick')->where('href', $row['href'])->find();
if (!empty($exists)) {
continue;
}
$data = $row;
$data['create_time'] = $now;
$data['update_time'] = $now;
$data['delete_time'] = 0;
Db::name('system_quick')->insert($data);
++$inserted;
}
$this->writeln(' - 快捷入口新增 ' . $inserted . ' 条(按 href 判重)');
}
/**
* 商城演示对齐:分表守卫(三守卫相互独立):
* - cate 空则插分类(快照无显式 id,捕获自增 id)
* - goods 空则插商品,注入分类 id(复刻旧 initMall 逻辑)
* - tag 空则插标签(快照含显式 id 1-4)
*/
protected function alignMall(): void
{
$now = time();
// 分类表守卫:空则插入快照分类,捕获新自增 id 供商品注入
$cateId = 0;
if ($this->isTableEmpty('mall_cate')) {
$snapshotCate = include __DIR__ . '/adminInitData/MallCate.php';
foreach ($snapshotCate as $row) {
$data = $row;
$data['create_time'] = $now;
$data['update_time'] = $now;
$data['delete_time'] = 0;
Db::name('mall_cate')->insert($data);
$cateId = (int) Db::name('mall_cate')->getLastInsID();
$this->writeln(' - 新增商品分类 id=' . $cateId . ' ' . $row['title']);
}
} else {
$this->writeln(' - 商品分类表已有数据,跳过');
}
// 商品表守卫:空则插入快照商品,cate_id 注入分类 id(无快照分类时取库内首个分类)
if ($this->isTableEmpty('mall_goods')) {
if ($cateId === 0) {
$cateId = (int) Db::name('mall_cate')->order('id', 'asc')->value('id');
}
$snapshotGoods = include __DIR__ . '/adminInitData/MallGoods.php';
foreach ($snapshotGoods as $row) {
$data = $row;
$data['cate_id'] = $cateId;
$data['create_time'] = $now;
$data['update_time'] = $now;
$data['delete_time'] = 0;
Db::name('mall_goods')->insert($data);
$this->writeln(' - 新增商品 ' . $row['title'] . ' (cate_id=' . $cateId . ')');
}
} else {
$this->writeln(' - 商品表已有数据,跳过');
}
// 标签表守卫:空则插入快照标签(显式 id,快照行自带时间戳与 delete_time)
if ($this->isTableEmpty('mall_tag')) {
$snapshotTag = include __DIR__ . '/adminInitData/MallTag.php';
foreach ($snapshotTag as $row) {
Db::name('mall_tag')->insert($row);
$this->writeln(' - 新增商品标签 id=' . $row['id'] . ' ' . $row['title']);
}
} else {
$this->writeln(' - 商品标签表已有数据,跳过');
}
}
/**
* 判断表是否为空(原生查询,不受任何作用域影响).
*/
protected function isTableEmpty(string $table): bool
{
return Db::name($table)->count() == 0;
}
protected function writeln(string $message): void
{
if ($this->output !== null) {
$this->output->writeln($message);
}
}
}