博主

48秒前在线

秃头张
保护好自己的头发
歌曲封面 未知作品

网站已运行 1 年 220 天 13 小时 47 分

Powered by Typecho & Sunny

2 online · 16 ms

Title

解决handsome时光机发布失败的各类问题

秃头张

·

Article
迁移到typecho的时候,自然而然地就选择了handsome,不为别的因为我就只买了这一款主题,那么对于我来说能用的功能都要用上,尤其是时光机的功能。

问题截图

问题描述

再微信公众号发布内容提示以下报错

♾️ text 代码:
错误码200<pre><code><h1>Serialization of 'PDO' is not allowed</h1></code></pre>
♾️ text 代码:
请求失败500 Server Error: Internal Server Error for url: https://tutouzhang.com/

在浏览器发布内容提示以下报错

♾️ text 代码:
Serialization of 'PDO' is not allowed

[scode type="red" size=""]

因为我没有自建公众号进行发布,所以只使用了这两种发布方式,均有报错,其中微信公众号发布有两个报错。

接下来看我是怎么解决这些问题的。

[/scode]

官方问题解答:评论/时光机相关

时光机来自typecho的评论功能复用而来,所以大部分问题是相似的

时光机发送微信显示“该公众号提供的服务出现故障”

  • 检查作者的博客是否正常打开 www.ihewro.com 如果可以正常打开,说明公众号服务是正常的;如果打不开,可以联系作者或者稍作等待
  • 检查你在授权平台填写的地址是不是和你的博客地址完全一致,因为最终公众号请求的地址是授权平台上填写的地址,而不是你在公众号中绑定时候输入的地址

    • 比如 www前缀、http还是https 要和你的博客地址完全一致
  • 在微信绑定新的网站之前,一定要先解除绑定,否则一旦一个微信号绑定多个网站就会导致问题,此时需要联系作者手动解除绑定

sqlite 数据库中时光机创建新标签会导致页面卡死

暂时需要通过修改typecho代码修复。

  • 打开typecho目录下的 var/Widget/Feedback.php
  • 搜索 $this->db->fetchRow($this->select()->where('coid = ?' 所在行
  • 修改前:

    ♾️ text 代码:
          $this->db->fetchRow($this->select()->where('coid = ?', $commentId)
              ->limit(1), [$this, 'push']);
  • 修改后:

    ♾️ text 代码:
          $item = $this->db->fetchRow($this->select()->where('coid = ?', $commentId)
              ->limit(1));
          $this->push($item);

评论区不能斗图不能显示图片 | 说说不能显示图片/音乐播放器/视频播放器

  • 后台 设置——评论——允许使用的HTML标签和属性 里面添加html标签
♾️ text 代码:
<img src="">
<audio class="" src="" preload>
<video src=""></video>

时光机/说说上传图片失败

  • 检查typecho目录下 usr/uploads 目录下是否有 time 文件夹,如果没有手动创建一个,然后给该文件夹777权限
  • Linux设置文件夹777权限命令:sudo chmod -R 777 ./
  • 时光机上传图片增加了图片压缩的流程,如果你的服务器环境不支持gd扩展,可能导致图片上传成功,但是时光机显示“上传失败”,可以打开主题目录下 libs/Utils.php 搜索 (new Imgcompress 注释或者删除该行代码即可

评论区无法使用markdown语法

  • 后台 设置——评论——在评论中使用Markdown语法(选中) 后台 设置——评论——允许使用的HTML标签和属性 里面添加markdown语法对应的html标签
♾️ text 代码:
<blockquote><pre><code><strong><em><h5><h6><a href title><table><thead><tr><th><tbody><td>

说说的地理位置信息不显示

地理信息中包含了一个emoji,type cho默认不支持emoji,需要修改数据库的字段类型:typecho 支持 emoji 教程(具体可以百度 typecho 支持emoji 有教程的)

时光机如何删除说说

  • 和评论管理一样,在typecho后台的管理——评论里面删除即可

无法向博客时光机发送内容

  • 授权平台填写的域名有错误,显示的错误一般有 HTTPSConnectionPool(host= xxxx',port=443):Max retries exceeded with url:/Caused by

    • 授权平台上填写的域名是 https,但是你的博客不支持ssl
    • 确保授权平台上你的博客域名和你的站点地址完全一致,比如是否有www,协议是http还是https
    • 最终是看授权平台上填写的域名,即使在微信中绑定信息输入的地址是 http://,但是在授权平台上填写的地址 https:// ,那最终还是请求的还是 https://的地址
  • 某些服务器做了ua判断,所以无法通过请求
  • 某些服务器有cc防护,禁止请求

评论失败的具体排查方式

请升级到主题的最新版本,对于低版本主题的评论失败问题不再维护

评论失败有两种情况:

  • 一种有具体的错误提示,
  • 一种没有具体的错误,提示「评论请求失败」或者「评论可能被拦截且无反馈信息」。
有具体错误提示

具体的错误提示分为下面几种:

  • 具体的php错误输出,比如提示调用某个函数不存在 Notice: Undefined function等等,一般是某个插件导致的,可以排查一下与评论相关插件(如评论邮件提醒等)
  • 「数据库查询错误 Database Query Error」,一般有两种情况:

  • 具体中文拦截原因,如「对不起, 您的发言过于频繁, 请稍侯再次发布.」,或者是其它的中文错误提示,属于正常的。

    • 在typecho的评论设置里面开启了「同一 IP 发布评论的时间间隔限制」
    • 使用了评论反垃圾插件触发了拦截规则
  • 「页面如果被缓存无法显示最新评论」,可能的情况为:

    • 使用的插件向页面插入了旧版本的jquery.js 与主题冲突,可以按下F12看下报错,如「炫彩鼠标」,需要在插件设置里面不加载jquery或者禁用插件
    • CDN缓存静态缓存了 HTML页面,导致评论功能失败(CDN缓存建议只缓存静态资源文件,如 css,js,image),因为你静态缓存了页面,页面就不会更新,怎么能显示你的评论呢!!
    • 使用了服务器缓存功能
    • 使用了数据库缓存插件
    • 使用了页面代码压缩的插件
    • 如果上述流程都排查后仍然有问题,可以切换到默认主题(切换前需要备份handsome设置数据,否则会被清空),如果默认主题不存在该问题,handsome主题存在该问题,可以联系作者看一下
没有具体的错误提示,提示「评论请求失败」或者「评论可能被拦截且无反馈信息」
  • 不要在主题中加入 <meta name="referrer" content="no-referrer" />这行代码!
  • 开了防火墙之类的拦截了post请求,一般是评论中包含了html代码,防火墙就会被拦截

(这种情况,可以在外观设置——主题增强功能里面关掉 ajax评论的开关,然后再次评论查看具体的错误信息)

如果按照上面流程排查后仍然无法解决问题,可以切换到默认主题(切换前务必在外观设置界面备份数据),如果默认主题评论正常,handsome主题不正常,可以联系我。

评论/时光机希望打开的时候显示最新的评论

如果希望打开文章/时光机默认显示最新的评论,需要在typecho 后台设置——评论,启用分页, 并且 在列出时将第一页作为默认显示。(主题强制设置了「将较新的的评论显示在前面」,也就是说第一页是最新的评论,最后一页是时间最久的评论)

时光机/评论页面如何分页

typecho后台管理——设置——评论中启用分页即可:

请输入图片描述

[scode type="blue" size=""]

我发现对我并没有什么作用,所以还是自己动手排查比较好

[/scode]

排查步骤

500的报错就不用我解释了,但是我浏览器可以访问,公众号却不行,所以我怀疑是作者的问题,后来我尝试从php8.0换到php7.4解决了这个问题。

公众号错误码200的这个报错,说明是响应成功了的,但是返回的页面是错误的, 那么我怀疑typecho或者handsome进行了返回正常状态,其实他可能还是500的报错。

浏览器的这个报错发生在尝试序列化 PDO 对象时,PDO 是 PHP 的数据库连接类,不能被序列化,

我猜想:会话管理($\_SESSION)、缓存机制、对象持久化,或某个类在保存状态时包含了 PDO。

因为这个猜想导致我禁用了所有的插件进行排查,还是没有排查出问题,所以我将矛头指向了typecho,我严重怀疑他的1.2.1的版本有所改动。

完美解决

找到var/Typecho/Db.php

粘贴以下代码进行覆盖,强烈建议备份文件

♾️ text 代码:
<?php

namespace Typecho;

use Typecho\Db\Adapter;
use Typecho\Db\Query;
use Typecho\Db\Exception as DbException;

/**
 * 包含获取数据支持方法的类.
 * 必须定义__TYPECHO_DB_HOST__, __TYPECHO_DB_PORT__, __TYPECHO_DB_NAME__,
 * __TYPECHO_DB_USER__, __TYPECHO_DB_PASS__, __TYPECHO_DB_CHAR__
 *
 * @package Db
 */
class Db
{
    /** 读取数据库 */
    public const READ = 1;

    /** 写入数据库 */
    public const WRITE = 2;

    /** 升序方式 */
    public const SORT_ASC = 'ASC';

    /** 降序方式 */
    public const SORT_DESC = 'DESC';

    /** 表内连接方式 */
    public const INNER_JOIN = 'INNER';

    /** 表外连接方式 */
    public const OUTER_JOIN = 'OUTER';

    /** 表左连接方式 */
    public const LEFT_JOIN = 'LEFT';

    /** 表右连接方式 */
    public const RIGHT_JOIN = 'RIGHT';

    /** 数据库查询操作 */
    public const SELECT = 'SELECT';

    /** 数据库更新操作 */
    public const UPDATE = 'UPDATE';

    /** 数据库插入操作 */
    public const INSERT = 'INSERT';

    /** 数据库删除操作 */
    public const DELETE = 'DELETE';

    /**
     * 数据库适配器
     * @var Adapter
     */
    private $adapter;

    /**
     * 默认配置
     *
     * @var array
     */
    private $config;

    /**
     * 已经连接
     *
     * @access private
     * @var array
     */
    private $connectedPool;

    /**
     * 前缀
     *
     * @access private
     * @var string
     */
    private $prefix;

    /**
     * 适配器名称
     *
     * @access private
     * @var string
     */
    private $adapterName;

    /**
     * 实例化的数据库对象
     * @var Db
     */
    private static $instance;

    /**
     * 数据库类构造函数
     *
     * @param mixed $adapterName 适配器名称
     * @param string $prefix 前缀
     *
     * @throws DbException
     */
    public function __construct($adapterName, string $prefix = 'typecho_')
    {
        /** 获取适配器名称 */
        $adapterName = $adapterName == 'Mysql' ? 'Mysqli' : $adapterName;
        $this->adapterName = $adapterName;

        /** 数据库适配器 */
        $adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $adapterName);

        if (!call_user_func([$adapterName, 'isAvailable'])) {
            throw new DbException("Adapter {$adapterName} is not available");
        }

        $this->prefix = $prefix;

        /** 初始化内部变量 */
        $this->connectedPool = [];

        $this->config = [
            self::READ => [],
            self::WRITE => []
        ];

        //实例化适配器对象
        $this->adapter = new $adapterName();
    }

    /**
     * @return Adapter
     */
    public function getAdapter(): Adapter
    {
        return $this->adapter;
    }

    /**
     * 获取适配器名称
     *
     * @access public
     * @return string
     */
    public function getAdapterName(): string
    {
        return $this->adapterName;
    }

    /**
     * 获取表前缀
     *
     * @access public
     * @return string
     */
    public function getPrefix(): string
    {
        return $this->prefix;
    }

    /**
     * @param Config $config
     * @param int $op
     */
    public function addConfig(Config $config, int $op)
    {
        if ($op & self::READ) {
            $this->config[self::READ][] = $config;
        }

        if ($op & self::WRITE) {
            $this->config[self::WRITE][] = $config;
        }
    }

    /**
     * getConfig
     *
     * @param int $op
     *
     * @return Config
     * @throws DbException
     */
    public function getConfig(int $op): Config
    {
        if (empty($this->config[$op])) {
            /** DbException */
            throw new DbException('Missing Database Connection');
        }

        $key = array_rand($this->config[$op]);
        return $this->config[$op][$key];
    }

    /**
     * 重置连接池
     *
     * @return void
     */
    public function flushPool()
    {
        $this->connectedPool = [];
    }

    /**
     * 选择数据库
     *
     * @param int $op
     *
     * @return mixed
     * @throws DbException
     */
    public function selectDb(int $op)
    {
        if (!isset($this->connectedPool[$op])) {
            $selectConnectionConfig = $this->getConfig($op);
            $selectConnectionHandle = $this->adapter->connect($selectConnectionConfig);
            $this->connectedPool[$op] = $selectConnectionHandle;
        }

        return $this->connectedPool[$op];
    }

    /**
     * 获取SQL词法构建器实例化对象
     *
     * @return Query
     */
    public function sql(): Query
    {
        return new Query($this->adapter, $this->prefix);
    }

    /**
     * 为多数据库提供支持
     *
     * @access public
     * @param array $config 数据库实例
     * @param integer $op 数据库操作
     * @return void
     */
    public function addServer(array $config, int $op)
    {
        $this->addConfig(Config::factory($config), $op);
        $this->flushPool();
    }

    /**
     * 获取版本
     *
     * @param int $op
     *
     * @return string
     * @throws DbException
     */
    public function getVersion(int $op = self::READ): string
    {
        return $this->adapter->getVersion($this->selectDb($op));
    }

    /**
     * 设置默认数据库对象
     *
     * @access public
     * @param Db $db 数据库对象
     * @return void
     */
    public static function set(Db $db)
    {
        self::$instance = $db;
    }

    /**
     * 获取数据库实例化对象
     * 用静态变量存储实例化的数据库对象,可以保证数据连接仅进行一次
     *
     * @return Db
     * @throws DbException
     */
    public static function get(): Db
    {
        if (empty(self::$instance)) {
            /** DbException */
            throw new DbException('Missing Database Object');
        }

        return self::$instance;
    }

    /**
     * 选择查询字段
     *
     * @param ...$ags
     *
     * @return Query
     * @throws DbException
     */
    public function select(...$ags): Query
    {
        $this->selectDb(self::READ);

        $args = func_get_args();
        return call_user_func_array([$this->sql(), 'select'], $args ?: ['*']);
    }

    /**
     * 更新记录操作(UPDATE)
     *
     * @param string $table 需要更新记录的表
     *
     * @return Query
     * @throws DbException
     */
    public function update(string $table): Query
    {
        $this->selectDb(self::WRITE);

        return $this->sql()->update($table);
    }

    /**
     * 删除记录操作(DELETE)
     *
     * @param string $table 需要删除记录的表
     *
     * @return Query
     * @throws DbException
     */
    public function delete(string $table): Query
    {
        $this->selectDb(self::WRITE);

        return $this->sql()->delete($table);
    }

    /**
     * 插入记录操作(INSERT)
     *
     * @param string $table 需要插入记录的表
     *
     * @return Query
     * @throws DbException
     */
    public function insert(string $table): Query
    {
        $this->selectDb(self::WRITE);

        return $this->sql()->insert($table);
    }

    /**
     * @param $table
     * @throws DbException
     */
    public function truncate($table)
    {
        $table = preg_replace("/^table\./", $this->prefix, $table);
        $this->adapter->truncate($table, $this->selectDb(self::WRITE));
    }

    /**
     * 执行查询语句
     *
     * @param mixed $query 查询语句或者查询对象
     * @param int $op 数据库读写状态
     * @param string $action 操作动作
     *
     * @return mixed
     * @throws DbException
     */
    public function query($query, int $op = self::READ, string $action = self::SELECT)
    {
        $table = null;

        /** 在适配器中执行查询 */
        if ($query instanceof Query) {
            $action = $query->getAttribute('action');
            $table = $query->getAttribute('table');
            $op = (self::UPDATE == $action || self::DELETE == $action
                || self::INSERT == $action) ? self::WRITE : self::READ;
        } elseif (!is_string($query)) {
            /** 如果query不是对象也不是字符串,那么将其判断为查询资源句柄,直接返回 */
            return $query;
        }

        /** 选择连接池 */
        $handle = $this->selectDb($op);

        /** 提交查询 */
        $resource = $this->adapter->query($query instanceof Query ?
            $query->prepare($query) : $query, $handle, $op, $action, $table);

        if ($action) {
            //根据查询动作返回相应资源
            switch ($action) {
                case self::UPDATE:
                case self::DELETE:
                    return $this->adapter->affectedRows($resource, $handle);
                case self::INSERT:
                    return $this->adapter->lastInsertId($resource, $handle);
                case self::SELECT:
                default:
                    return $resource;
            }
        } else {
            //如果直接执行查询语句则返回资源
            return $resource;
        }
    }

    /**
     * 一次取出所有行
     *
     * @param mixed $query 查询对象
     * @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     *
     * @return array
     * @throws DbException
     */
    public function fetchAll($query, ?callable $filter = null): array
    {
        //执行查询
        $resource = $this->query($query);
        $result = $this->adapter->fetchAll($resource);

        return $filter ? array_map($filter, $result) : $result;
    }

    /**
     * 一次取出一行
     *
     * @param mixed $query 查询对象
     * @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     * @return array|null
     * @throws DbException
     */
    public function fetchRow($query, ?callable $filter = null): ?array
    {
        $resource = $this->query($query);

        return ($rows = $this->adapter->fetch($resource)) ?
            ($filter ? call_user_func($filter, $rows) : $rows) :
            null;
    }

    /**
     * 一次取出一个对象
     *
     * @param mixed $query 查询对象
     * @param array|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
     * @return object|null
     * @throws DbException
     */
    public function fetchObject($query, ?array $filter = null): ?object
    {
        $resource = $this->query($query);

        return ($rows = $this->adapter->fetchObject($resource)) ?
            ($filter ? call_user_func($filter, $rows) : $rows) :
            null;
    }

    /**
     * 序列化时保存必要的配置信息,不包含连接池
     *
     * @return array
     */
    public function __serialize(): array
    {
        return [
            'adapterName' => $this->adapterName,
            'prefix' => $this->prefix,
            'config' => $this->config
        ];
    }

    /**
     * 反序列化时恢复配置并初始化适配器
     *
     * @param array $data 序列化数据
     * @return void
     */
    public function __unserialize(array $data): void
    {
        $this->adapterName = $data['adapterName'];
        $this->prefix = $data['prefix'];
        $this->config = $data['config'];
        $this->connectedPool = []; // 重置连接池,防止 PDO 被序列化

        // 重新实例化适配器
        $adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $this->adapterName);
        if (!class_exists($adapterName)) {
            throw new DbException("Adapter {$adapterName} is not available");
        }
        $this->adapter = new $adapterName();
    }
}

工作原理

  • 序列化时\_\_serialize() 返回一个不包含 $connectedPool$adapter 的数组,避免试图序列化 PDO。
  • 反序列化时\_\_unserialize() 从数据中恢复配置,重置 $connectedPool,并重新创建 $adapter。下次数据库操作(如 selectDb())会自动重新建立连接。

修改说明

  • 添加位置\_\_serialize()\_\_unserialize() 方法被添加到文件末尾,作为 Db 类的最后两个方法。
  • 功能

    • \_\_serialize():只保存 $adapterName$prefix$config,忽略 $connectedPool$adapter
    • \_\_unserialize():恢复保存的属性,重置 $connectedPool,并重新创建 $adapter 实例。
  • 兼容性:适用于 PHP 7.4+。8.0测试也没有问题。
现在已有 99 次阅读,3 条评论,0 人点赞
Author:秃头张
作者
解决handsome时光机发布失败的各类问题
当前文章累计共 19232 字,阅读大概需要 8 分钟。
甘肃-宁夏-西安
2024年8月28日 · 0评论
海棠微光操作手册
2024年6月4日 · 0评论
删除handsome相册文章的图片描述
2025年2月25日 · 4评论
Comment:共3条
发表
  1. 头像
    @
    王芳
    这篇文章详细地分享了解决 Handsome 主题时光机功能发布失败的各种问题,尤其是关于数据库配置和微信接口的常见错误。文章思路清晰,解决方案也很实用,对其他使用该主题的开发者有很大帮助。
    · iPhone · Safari · 本机地址

    👍

    💖

    💯

    💦

    😄

    🪙

    👍 0 💖 0 💯 0 💦 0 😄 0 🪙 0
  2. 头像
    @

    博主

    秃头张
    本文讨论了在使用handsome主题和Typecho时遇到的时光机发布失败问题及其解决方法。针对常见错误,如PDO序列化问题、500服务器错误等,作者通过调整PHP版本、修改数据库代码等步骤成功修复问题。文章详细分享了如何通过排查和修改代码解决多个发布和功能问题。
    · MacOS · Chrome · 本机地址

    👍

    💖

    💯

    💦

    😄

    🪙

    👍 0 💖 0 💯 0 💦 0 😄 0 🪙 0
  3. 头像
    @
    暮云清风
    这篇文章深入分析了解决handsome时光机发布失败的问题,非常实用。作者不仅详细列出了问题的排查过程,还提供了具体的解决方案,特别是对于调试过程中出现的500错误和PDO序列化问题,解决方案也很有参考价值。对于使用handsome
    · Windows · Chrome · 本机地址

    👍

    💖

    💯

    💦

    😄

    🪙

    👍 0 💖 0 💯 0 💦 0 😄 0 🪙 0
搜 索 消 息 足 迹
你还不曾留言过..
你还不曾留下足迹..
博主 不再显示
博主