* @Date: 2026-08-01 23:18:56 * @LastEditors: YwxApp * @LastEditTime: 2026-09-23 09:30:00 * @Description: * @FilePath: \ywxapp_dev\addon\docs\Addon.php * @CustomString: Copyright (c) 2026 YwxApp */ // +---------------------------------------------------------------------- // | YwxApp [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved. // +---------------------------------------------------------------------- // | Author: ywxapp // +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\docs; use think\facade\Cache; use think\facade\Db; use ywxapp\AddonBase; /** * 文档中心插件 * * 菜单与建表/种子数据由框架统一处理: * - 菜单:addon/docs/menu.json * - 数据:addon/docs/install.sql(框架 importsql 白名单执行 CREATE TABLE / INSERT) * * 插件自愈: * - install / upgrade:检测 docs_doc 关键列是否齐全,缺则 ALTER TABLE 补齐(避免老库升 500) * - enabled / disabled:清理目录树缓存 * - uninstall:物理删除 user_rule / admin_power 中的本插件记录(采用 Db::name 自动加前缀) * * @author ywxapp */ class Addon extends AddonBase { /** * 安装钩子:新装也跑一遍自愈,防止 importsql 部分失败导致缺列 */ public function install() { $this->repairSchema(); return true; } /** * 升级钩子:框架 AddonService::upgrade() 通过 callAddonHook 回调 */ public function upgrade($currentVersion = '') { $this->repairSchema(); Cache::tag('docs_tree')->clear(); return true; } /** * 卸载钩子 * * 框架 AddonService::deleteMenu() 只清理 admin_power, * 这里补清 user_rule 中本插件的前端菜单(按 name 前缀),避免残留。 * 采用物理删除,防止软删除后唯一键 name 冲突导致重装失败。 * * Db::name() 自动按 config/database.prefix 拼接表名,避免硬编码 'wxapp_' 部署陷阱。 */ public function uninstall() { Db::name('user_rule')->whereLike('name', 'docs:%')->delete(); Db::name('user_rule')->whereLike('name', 'docs/%')->delete(); Db::name('admin_power')->where('addon', 'docs')->delete(); return true; } /** * 启用钩子:清理目录树缓存 + 兜底自愈 */ public function enabled() { $this->repairSchema(); Cache::tag('docs_tree')->clear(); return true; } /** * 禁用钩子:清理目录树缓存 */ public function disabled() { Cache::tag('docs_tree')->clear(); return true; } /** * 表结构自愈:按 docs_doc 关键列补齐(幂等) * * 为什么需要: * 框架 importsql 白名单(AddonService::importsql)只允许 CREATE TABLE / INSERT, * 跳过 ALTER。已安装的 1.0.x 老库若缺 seo_* / content_md / editor_uid / audit_status * 等列,首次访问新增/编辑/导入接口会直接 500。这里在 install/upgrade/enabled * 三处兜底跑一遍 SHOW COLUMNS 比对,缺的补上。 * * 注意:使用配置前缀而非硬编码 'wxapp_' */ protected function repairSchema(): void { $prefix = (string) (config('database.connections.mysql.prefix') ?? ''); $tbl = $prefix . 'docs_doc'; // 防御:表不存在(首次安装 importsql 还未跑)直接跳过 try { $existing = Db::query("SHOW COLUMNS FROM `{$tbl}`"); } catch (\Throwable $e) { return; } $have = []; foreach ((array) $existing as $row) { if (isset($row['Field'])) { $have[(string) $row['Field']] = true; } } $needs = [ 'content_md' => "longtext COMMENT 'Markdown源码'", 'editor_type' => "tinyint(1) NOT NULL DEFAULT '1' COMMENT '编辑器类型:1富文本 2Markdown'", 'seo_title' => "varchar(200) NOT NULL DEFAULT '' COMMENT 'SEO标题'", 'seo_keywords' => "varchar(255) NOT NULL DEFAULT '' COMMENT 'SEO关键词'", 'seo_desc' => "varchar(500) NOT NULL DEFAULT '' COMMENT 'SEO描述'", 'editor_uid' => "int unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑者UID'", 'audit_status' => "tinyint(1) NOT NULL DEFAULT '1' COMMENT '审核状态:0待审 1通过 2驳回'", ]; foreach ($needs as $col => $spec) { if (!isset($have[$col])) { try { Db::execute("ALTER TABLE `{$tbl}` ADD COLUMN `{$col}` {$spec}"); } catch (\Throwable $e) { // 静默:首次 install 时表尚未 importsql 创建,异常可忽略 } } } } }