// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\blog; use addon\blog\library\BlogSchema; use think\facade\Db; use ywxapp\AddonBase; use ywxapp\service\AddonService; /** * Addon 类 * * 注意:基类是 ywxapp\AddonBase(仓库中不存在名为 addon 的类)。 * 写成 extends addon 时,钩子回调会因 class_exists 失败而被静默跳过 * (Windows 大小写不敏感可能掩盖,Linux 必然 Class not found)。 * * @author ywxapp */ class Addon extends AddonBase { /** * 安装钩子 * * 建表与种子数据由 install.sql 负责,这里补做列/索引自愈 * (install.sql 的 ALTER 会被框架白名单跳过)。 */ public function install() { BlogSchema::ensure(); return true; } /** * 卸载钩子 * * 框架 AddonService::deleteMenu() 只清理后台权限表,这里补齐: * 私有表、会员/前台菜单、插件配置、静态资源。 */ public function uninstall() { $prefix = $this->prefix(); // 1) 插件私有表(表名从 install.sql 解析,避免硬编码与遗漏) foreach ($this->tableNames() as $table) { try { Db::execute("DROP TABLE IF EXISTS `{$prefix}{$table}`"); } catch (\Throwable $e) { // 单表失败不阻断整体卸载 } } // 2) 菜单残留:框架清后台,这里补会员中心/前台 // (表名用 Db::name 自动加前缀,勿写死 wxapp_) try { Db::name('member_rule')->where('name', 'like', 'blog:%')->delete(); } catch (\Throwable $e) { // 表不存在时忽略 } // 3) 配置残留(框架卸载不清配置,重装会继承旧值) try { Db::name('common_addonconf')->where('addon', 'blog')->delete(); } catch (\Throwable $e) { // 表不存在时忽略 } AddonService::clearConfigCache('blog'); // 4) 静态资源(框架只清 public/static/addon//,清不到实际目录) $this->deleteDirectory(public_path() . 'static' . DIRECTORY_SEPARATOR . 'blog'); return true; } /** * 启用钩子(轻量自愈) */ public function enable() { BlogSchema::ensure(); return true; } /** * 禁用钩子 */ public function disable() { return true; } /** * 升级钩子(覆盖升级 / addon:manage 时由框架回调) * * install.sql 的 ALTER 会被白名单跳过,所以老库补列补索引必须放这里。 * * @param string $currentVersion 已安装版本号 */ public function upgrade($currentVersion = '') { BlogSchema::ensure(); // 清理已从 install.sql 移除的历史遗留表(老库升级时执行) BlogSchema::dropLegacyTables(); return true; } /** * 从 install.sql 解析插件私有表名(不含前缀) */ private function tableNames(): array { $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'install.sql'; if (!is_file($file)) { return []; } $content = (string) file_get_contents($file); preg_match_all('/CREATE TABLE IF NOT EXISTS\s+`__PREFIX__([a-z_]+)`/i', $content, $m); return $m[1] ?? []; } /** * 当前数据库表前缀 */ private function prefix(): string { $default = (string) config('database.default', 'mysql'); return (string) config("database.connections.{$default}.prefix", ''); } /** * 递归删除目录(卸载时清理静态资源) */ private function deleteDirectory(string $dir): void { if (!is_dir($dir)) { return; } $items = scandir($dir); if (!is_array($items)) { return; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir . DIRECTORY_SEPARATOR . $item; if (is_dir($path)) { $this->deleteDirectory($path); } else { @unlink($path); } } @rmdir($dir); } }